feat(core): modems run — the pair in the project, and a modem of another project hands its frames on (K0–K2)

Measured first: tools/java-oracle's ModemRun joins two networks by a pair
of Java modems over TCP on localhost. The modem has no card and adds no
delay of its own — a frame takes the cables on both sides, nothing more.

The core pairs the two enabled modems on a port (Swift's inProject, with
its conditions and cycle suppression) on Java's timing: no 20 ms. The
modem example of FILIUS now pings through instead of failing the core.
A modem of another project (relay) gives its frames to whoever runs the
engine and puts on its cable what comes back, with this engine's frame
and packet numbers; a frame dies after MAXIMUM_RELAY_HOPS trips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 18:12:27 +00:00
co-authored by Claude Opus 5.5
parent 6f583616f6
commit c85b0e45da
19 changed files with 1743 additions and 44 deletions
+3 -3
View File
@@ -135,7 +135,7 @@ export function watchedEngine(scenario: Scenario, snapshot: TopologySnapshot = s
/** Every frame when it first shows on a host's card (sent, or received). */
const frames = (): SeenFrame[] => {
const hosts = new Set(scenario.nodes.filter((node) => node.kind !== 'networkSwitch').map((node) => node.id));
const hosts = new Set(scenario.nodes.filter((node) => node.kind !== 'networkSwitch' && node.kind !== 'remoteLink').map((node) => node.id));
const firstSeen = new Map<number, SeenFrame>();
for (const event of engine.state.packetTraces) {
if (event.layer !== 'physical' || !hosts.has(event.nodeId) || event.frameIdentity === undefined) continue;
@@ -185,10 +185,10 @@ export interface CaptureCard {
readonly portId: string;
}
/** A scenario's cards: its hosts' ports, under the node's id for the first card and `id#index` for the others (ScenarioNetwork). */
/** A scenario's cards: its hosts' ports, under the node's id for the first card and `id#index` for the others (ScenarioNetwork); switches and modems have none. */
export function scenarioCards(scenario: Scenario): CaptureCard[] {
return scenario.nodes
.filter((node) => node.kind !== 'networkSwitch')
.filter((node) => node.kind !== 'networkSwitch' && node.kind !== 'remoteLink')
.flatMap((node) => node.ports.map((port, index) => ({ key: index === 0 ? node.id : `${node.id}#${index}`, portId: port.id })));
}