Skip to content

Commit c669fd9

Browse files
committed
migration: VMNetwork + VMMessageBus integer brains (wave 3)
VMNetwork: the Tier-5 causal-ordering core (6 exports: tick, sync, merge_timestamp, merge_register, can_undo, route_kind) — pure i32 arithmetic + causal-undo gate + last-writer-wins register reconciliation + channel-ordinal routing (CHANNEL band NET:0/COVERT:1/LOCAL:2, -1 sentinel). Matches the host contract in VMNetworkCoprocessor.res:41-56. G2 251/251, G4 clean. VMMessageBus: message-routing dispatch over two closed enum bands (9 exports) — messageTarget band (Console..DevicePort 0..7) with relays_to_partner / is_coop_channel / is_covert_channel, and the coopEvent band (0..6) with event_sends_to_client (every kind except PortData). classifyPort string parsing stays host-side; the host passes the integer message-target ordinal. G2 84/84, G4 clean. Both re-decomposed: drop module state (globalClock cell, output dicts), drop async, explicit Int params, enums as integer bands, all string ops host-side. Oracles independently reimplemented from the .res semantics. https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s
1 parent 84826f8 commit c669fd9

4 files changed

Lines changed: 423 additions & 0 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
3+
//
4+
// VMMessageBus -- the message-routing co-processor, the pure-integer core
5+
// extracted from idaptik src/app/multiplayer/VMMessageBus.res (the bridge between
6+
// VM I/O ports and game/co-op events). Per the DESIGN-VISION ("AffineScript is
7+
// the brain, JS/Pixi the senses; only primitives cross the wasm boundary"), the
8+
// host keeps every STRING and every mutable buffer: the Dict<portName,[Int]>
9+
// output buffer (writePortOutput/readPortOutput, res:53-82), the coopOutbox
10+
// array, the inbound handler refs, and -- decisively -- ALL the string work in
11+
// classifyPort (res:96-114: the `port == "console"` literal compares and the
12+
// `String.startsWith(port, "covert:")` prefix test). String.startsWith on the
13+
// topic prefix is a host SENSE. The host parses the port string into an integer
14+
// message-kind code and the kernel does the integer dispatch over that code.
15+
// Every export is i32 -> i32 (scalar ABI).
16+
//
17+
//## Why this split, not a port of classifyPort / flushCoopOutbox
18+
// classifyPort is end-to-end string work (literal `==` and prefix `startsWith`)
19+
// -- exactly what AffineScript's canonical face cannot do soundly (broken string
20+
// subscript / string ==). So the host runs that parse and emits the matched
21+
// message-target ORDINAL. What survives as the integer brain are the routing
22+
// DECISIONS the message-target consumers and flushCoopOutbox make over the
23+
// closed enum bands: "is this target relayed to the co-op partner over the
24+
// WebSocket?", "is it a co-op channel / a covert channel?", and the per-coop-
25+
// event-kind "does this event call the multiplayer client?" dispatch
26+
// (flushCoopOutbox res:136-161, whose only non-sending arm is PortData). Those
27+
// are integer truth tables over the two enums; the string parse that produces
28+
// the ordinal is the host sense feeding them.
29+
//
30+
//## messageTarget encoding (the header contract for the JS host)
31+
// The host parses a port string to exactly one of these ordinals, in the
32+
// declaration order of the `messageTarget` variant (res:86-94), and hands the
33+
// kernel the ordinal -- never the string:
34+
// code target port form (classifyPort branch)
35+
// 0 Console "console"
36+
// 1 Display "display"
37+
// 2 Firewall "firewall"
38+
// 3 CoopSync "coop:sync"
39+
// 4 CoopChat "coop:chat"
40+
// 5 CoopItem "coop:item"
41+
// 6 CovertLinkChannel "covert:<id>" (String.startsWith host sense)
42+
// 7 DevicePort any other port (the else arm)
43+
// The encoding is LOSSLESS over its closed 0..7 band. A target ordinal outside
44+
// 0..7 is not a message target: is_valid_target reports 0 and clamp_target
45+
// returns the OUT-OF-BAND SENTINEL -1 -- never an in-band code, so out-of-band
46+
// input cannot be confused with a real target (sentinel, not a clamp; assail
47+
// PA-AFF-001 stays clean).
48+
//
49+
//## coopEvent encoding (the header contract for the JS host)
50+
// The host tags each outgoing co-op event with one of these ordinals, in the
51+
// declaration order of the `coopEvent` variant (res:120-127):
52+
// code event payload kind
53+
// 0 VMExecuted {deviceId, instruction, args}
54+
// 1 VMUndone {deviceId}
55+
// 2 StateSync {deviceId, registers}
56+
// 3 PortData {port, values} -- routed via VM state sync, NOT sent
57+
// 4 CovertLinkFound {connectionId}
58+
// 5 CovertLinkActivated {connectionId}
59+
// 6 ChatSent {message}
60+
// flushCoopOutbox sends every kind to MultiplayerClient EXCEPT PortData (res:148:
61+
// `| PortData(_) => ()` -- "Port data goes through VM state sync"). That single
62+
// exception is the only non-trivial integer decision in the drain loop.
63+
64+
// The number of message-target classes in the routing taxonomy.
65+
pub fn message_target_count() -> Int { 8 }
66+
67+
//## Whether a host integer names a defined message target. 1 = valid, 0 = out of band.
68+
pub fn is_valid_target(code: Int) -> Int {
69+
if code < 0 { return 0; }
70+
if code > 7 { return 0; }
71+
1
72+
}
73+
74+
//## Canonicalise a host target ordinal: identity on a valid 0..7 code, the
75+
// out-of-band SENTINEL -1 otherwise. -1 is not an in-band code, so an
76+
// unrecognised target can never be confused with a real one (sentinel, not a
77+
// clamp). Mirrors the closed `messageTarget` band of classifyPort.
78+
pub fn clamp_target(code: Int) -> Int {
79+
if is_valid_target(code) == 1 { code } else { -1 }
80+
}
81+
82+
//## Is this message target relayed to the co-op partner over the WebSocket?
83+
// The routing decision the co-op relay makes: only the three co-op channels --
84+
// CoopSync(3), CoopChat(4), CoopItem(5) -- cross to the partner via the
85+
// multiplayer client; Console/Display/Firewall (local game systems) and
86+
// CovertLinkChannel/DevicePort (handled by the in-VM covert link / device port
87+
// plumbing) stay local. Returns 1 = relay, 0 = local. Out-of-band target -> 0.
88+
pub fn relays_to_partner(target: Int) -> Int {
89+
if target == 3 { return 1; } // CoopSync
90+
if target == 4 { return 1; } // CoopChat
91+
if target == 5 { return 1; } // CoopItem
92+
0
93+
}
94+
95+
//## Is this target one of the co-op channels (coop:sync / coop:chat / coop:item)?
96+
// The closed CoopSync..CoopItem sub-band (3..5) of messageTarget. Lets the host
97+
// distinguish a co-op channel from the covert/device/local targets without a
98+
// second string parse. Returns 1 = co-op channel, 0 = not. Out-of-band -> 0.
99+
pub fn is_coop_channel(target: Int) -> Int {
100+
if target < 3 { return 0; }
101+
if target > 5 { return 0; }
102+
1
103+
}
104+
105+
//## Is this target the covert-link data channel ("covert:<id>")?
106+
// CovertLinkChannel(6) alone. The `String.startsWith(port, "covert:")` branch of
107+
// classifyPort, read as its ordinal. Returns 1 = covert channel, 0 = not.
108+
pub fn is_covert_channel(target: Int) -> Int {
109+
if target == 6 { 1 } else { 0 }
110+
}
111+
112+
// The number of co-op event kinds in the outbox taxonomy.
113+
pub fn coop_event_count() -> Int { 7 }
114+
115+
//## Whether a host integer names a defined co-op event kind. 1 = valid, 0 = out of band.
116+
pub fn is_valid_event(kind: Int) -> Int {
117+
if kind < 0 { return 0; }
118+
if kind > 6 { return 0; }
119+
1
120+
}
121+
122+
//## Does this co-op event kind get sent to the multiplayer client on outbox drain?
123+
// THE flushCoopOutbox decision (res:136-161): the drain loop forwards every
124+
// event kind to MultiplayerClient EXCEPT PortData(3), whose data is routed via
125+
// VM state sync instead (res:148). So the predicate is "valid kind AND not
126+
// PortData". Returns 1 = send to client, 0 = do not. An out-of-band kind is not
127+
// a defined event and returns 0 (fails safe to "do not send").
128+
pub fn event_sends_to_client(kind: Int) -> Int {
129+
if is_valid_event(kind) == 0 { return 0; } // not a defined event kind
130+
if kind == 3 { return 0; } // PortData: routed via VM state sync, not sent
131+
1
132+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
3+
//
4+
// affine-parity config for VMMessageBus.affine (idaptik VM message-routing
5+
// kernel; scalar i32 ABI). Each oracle re-derives the rule straight from
6+
// VMMessageBus.res in plain JS, INDEPENDENTLY of the .affine, so a codegen
7+
// regression surfaces as a differential mismatch.
8+
//
9+
// Oracle re-derivation (rule <- source site), independent of the .affine:
10+
// messageTarget band: Console..DevicePort = 0..7 (res:86-94 declaration order)
11+
// coopEvent band: VMExecuted..ChatSent = 0..6 (res:120-127 declaration order)
12+
// is_valid_target = 0 <= t <= 7
13+
// clamp_target = valid ? t : -1
14+
// relays_to_partner = t in {CoopSync 3, CoopChat 4, CoopItem 5} ? 1 : 0
15+
// (the co-op channels are the partner-relayed ones; the
16+
// local/covert/device targets are not)
17+
// is_coop_channel = 3 <= t <= 5 ? 1 : 0
18+
// is_covert_channel = t == CovertLinkChannel(6) ? 1 : 0 (the startsWith "covert:" arm)
19+
// event_sends_to_client = (validEvent && kind != PortData(3)) ? 1 : 0
20+
// (flushCoopOutbox res:136-161 sends every kind but PortData, res:148)
21+
22+
const validTarget = (t) => Number.isInteger(t) && t >= 0 && t <= 7;
23+
const isValidTarget = (t) => (validTarget(t) ? 1 : 0);
24+
const clampTarget = (t) => (validTarget(t) ? t : -1);
25+
// Co-op channels (3,4,5) are the only targets relayed to the partner.
26+
const relaysToPartner = (t) => (t === 3 || t === 4 || t === 5 ? 1 : 0);
27+
const isCoopChannel = (t) => (t >= 3 && t <= 5 ? 1 : 0);
28+
const isCovertChannel = (t) => (t === 6 ? 1 : 0);
29+
30+
const validEvent = (k) => Number.isInteger(k) && k >= 0 && k <= 6;
31+
const isValidEvent = (k) => (validEvent(k) ? 1 : 0);
32+
// flushCoopOutbox forwards every event kind to the client EXCEPT PortData(3).
33+
const eventSendsToClient = (k) => (validEvent(k) && k !== 3 ? 1 : 0);
34+
35+
// Sweep the full messageTarget band plus out-of-band, and the full coopEvent
36+
// band plus out-of-band, so every in-band arm and both boundary guards fire.
37+
const TARGET = [-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
38+
const EVENT = [-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9];
39+
40+
export default {
41+
affine: "VMMessageBus.affine",
42+
cases: [
43+
{
44+
name: "message_target_count()",
45+
export: "message_target_count",
46+
args: [],
47+
oracle: () => 8,
48+
},
49+
{
50+
name: "is_valid_target over target band + out-of-band",
51+
export: "is_valid_target",
52+
args: [{ values: TARGET }],
53+
oracle: isValidTarget,
54+
},
55+
{
56+
name: "clamp_target over target band + out-of-band",
57+
export: "clamp_target",
58+
args: [{ values: TARGET }],
59+
oracle: clampTarget,
60+
},
61+
{
62+
name: "relays_to_partner over target band + out-of-band",
63+
export: "relays_to_partner",
64+
args: [{ values: TARGET }],
65+
oracle: relaysToPartner,
66+
},
67+
{
68+
name: "is_coop_channel over target band + out-of-band",
69+
export: "is_coop_channel",
70+
args: [{ values: TARGET }],
71+
oracle: isCoopChannel,
72+
},
73+
{
74+
name: "is_covert_channel over target band + out-of-band",
75+
export: "is_covert_channel",
76+
args: [{ values: TARGET }],
77+
oracle: isCovertChannel,
78+
},
79+
{
80+
name: "coop_event_count()",
81+
export: "coop_event_count",
82+
args: [],
83+
oracle: () => 7,
84+
},
85+
{
86+
name: "is_valid_event over event band + out-of-band",
87+
export: "is_valid_event",
88+
args: [{ values: EVENT }],
89+
oracle: isValidEvent,
90+
},
91+
{
92+
name: "event_sends_to_client over event band + out-of-band",
93+
export: "event_sends_to_client",
94+
args: [{ values: EVENT }],
95+
oracle: eventSendsToClient,
96+
},
97+
],
98+
};
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
3+
//
4+
// VMNetwork -- the causal-ordering co-processor, the pure-integer core extracted
5+
// from idaptik src/app/multiplayer/VMNetwork.res (Tier 5 multi-VM networking).
6+
// Per the DESIGN-VISION ("AffineScript is the brain, JS/Pixi the senses; only
7+
// primitives cross the wasm boundary") and the SHAPE-A note on the host binding
8+
// (VMNetworkCoprocessor.res / VMNetworkCoprocessorBridge.js), the model never
9+
// lives in wasm: the host keeps the deviceVMs dict, the instructionLog, the
10+
// per-device register files and the WebSocket transport, and -- critically --
11+
// does ALL string work itself. The host alone parses the wire port string
12+
// ("net:<ip>:<sub>", "covert:<id>", everything else), the device IPs, the
13+
// instruction text. The kernel sees only scalars: a Lamport clock value, a
14+
// register cell with its write timestamp, the consumed/undone flags, and a
15+
// pre-decoded port-channel ordinal. Every export is i32 -> i32 (scalar ABI).
16+
//
17+
//## Why this split, not a port of VMNetwork.res
18+
// VMNetwork.res entangles (a) the Lamport arithmetic (tick = time+1, sync =
19+
// max(local,remote)+1 -- res:61-89), (b) the causal-undo gate (refuse undo when
20+
// the last instruction's output was consumed by another VM -- res:240-256),
21+
// (c) the cross-VM register reconciliation the header advertises as "last-
22+
// writer-wins register reconciliation", and (d) a great deal of string-keyed
23+
// routing (routePortMessage's String.startsWith("net:")/("covert:") branch,
24+
// res:150-188, plus the Dict-by-deviceId/IP plumbing). Only (a)(b)(c) are
25+
// integer decisions; (d) is a STRING sense and stays host-side. The host
26+
// decodes the port prefix to a channel ordinal BEFORE calling the kernel, so no
27+
// string ever crosses. This file is the verified core of the six kernels the
28+
// host binding (VMNetworkCoprocessor.res:41-56) already contracts for, with the
29+
// canonical names the bridge JS requires (VMNetworkCoprocessorBridge.js:99-107:
30+
// tick / sync / merge_timestamp / merge_register / can_undo / route_kind).
31+
//
32+
//## Channel-ordinal encoding (the header contract for the JS host)
33+
// The host decodes the routed-port prefix to exactly one of these ordinals
34+
// (mirror of VMNetworkCoprocessorBridge.js CHANNEL, and of routePortMessage's
35+
// prefix branch) and hands route_kind the ordinal, never the string:
36+
// ordinal channel meaning (routePortMessage branch)
37+
// 0 NET "net:<ip>:<sub>" -> deliver to target device by IP
38+
// 1 COVERT "covert:<id>" -> deliver through the covert link
39+
// 2 LOCAL (any other port) -> no cross-VM routing
40+
// The encoding is LOSSLESS over its closed 0..2 band: three channels map to
41+
// three routing classes with no collision. A prefix ordinal outside 0..2 is
42+
// not a routed channel: route_kind returns the OUT-OF-BAND SENTINEL -1 (declared
43+
// here, never an in-band class, so no in-band collision is introduced -- this is
44+
// a sentinel, not a clamp; assail PA-AFF-001 stays clean).
45+
46+
//## tick -- advance the Lamport clock by one on a local VM event.
47+
// The integer core of VMNetwork.tick (res:61-74): the host owns the clock cell;
48+
// the kernel only computes the advanced stamp `time + 1`. Causal ordering needs
49+
// every local event to strictly increase the clock, so this is unconditional.
50+
pub fn tick(time: Int) -> Int {
51+
time + 1
52+
}
53+
54+
//## sync -- merge a remote clock on receive: max(local, remote) + 1.
55+
// The integer core of VMNetwork.sync (res:76-89): on receiving a remote stamp,
56+
// the merged clock is one past the later of the two, so the local clock never
57+
// runs backwards and always dominates anything it has seen (Lamport receive
58+
// rule). The host owns the clock cell; the kernel computes the merged stamp.
59+
pub fn sync(local: Int, remote: Int) -> Int {
60+
if local > remote { local + 1 } else { remote + 1 }
61+
}
62+
63+
//## merge_timestamp -- the surviving Lamport stamp of a reconciled register cell.
64+
// The header's "last-writer-wins register reconciliation": when two VMs hold the
65+
// same register, the cell that survives is the LATER write, so its stamp is the
66+
// max of the two write stamps. Ties keep the local stamp (they are equal, so the
67+
// choice is immaterial to the value -- see merge_register's strict-greater gate).
68+
pub fn merge_timestamp(local_ts: Int, remote_ts: Int) -> Int {
69+
if remote_ts > local_ts { remote_ts } else { local_ts }
70+
}
71+
72+
//## merge_register -- last-writer-wins register value.
73+
// Companion to merge_timestamp (host binding VMNetworkCoprocessor.res:49-50):
74+
// the reconciled register takes the REMOTE value only when the remote write is
75+
// strictly later (remote_ts > local_ts); on a tie or an older remote write the
76+
// local value stands. Strict-greater (not >=) makes the tie deterministic and
77+
// keeps the local writer authoritative for its own concurrent writes.
78+
pub fn merge_register(local_val: Int, local_ts: Int, remote_val: Int, remote_ts: Int) -> Int {
79+
if remote_ts > local_ts { remote_val } else { local_val }
80+
}
81+
82+
//## can_undo -- the causal-undo gate.
83+
// The integer core of undoOnDevice's refusal predicate (res:240-256): undo of
84+
// the last instruction is permitted iff its output was NEITHER consumed by
85+
// another VM's RECV NOR already undone. The host passes the two record flags as
86+
// 0/1 (instr.consumed ? 1 : 0, instr.undone ? 1 : 0) and refuses undo when this
87+
// returns 0 (res:252: `canUndo(...) != 1`). Returns 1 = may undo, 0 = refuse.
88+
// Any flag value other than 0 is treated as "set" (truthy), so a stray non-0/1
89+
// host encoding fails safe to refusal rather than fabricating permission.
90+
pub fn can_undo(consumed: Int, undone: Int) -> Int {
91+
if consumed != 0 { return 0; }
92+
if undone != 0 { return 0; }
93+
1
94+
}
95+
96+
//## route_kind -- classify a routed port by its decoded channel ordinal.
97+
// The integer core of routePortMessage (res:150-188) AFTER the host has parsed
98+
// the port string into a channel ordinal. It canonicalises the routing class for
99+
// a defined channel (identity on the closed 0..2 band: NET->0, COVERT->1,
100+
// LOCAL->2) and reports the out-of-band SENTINEL -1 for any prefix that is not a
101+
// routed channel. -1 is not an in-band class, so an unrecognised prefix can
102+
// never be confused with a real routing target (sentinel, not clamp).
103+
pub fn route_kind(prefix: Int) -> Int {
104+
if prefix < 0 { return -1; } // out of band: not a routed channel
105+
if prefix > 2 { return -1; } // out of band: not a routed channel
106+
prefix // NET=0 / COVERT=1 / LOCAL=2: identity routing class
107+
}

0 commit comments

Comments
 (0)