M1-03B
Delta snapshots, protocol entries, WebSocket endpoint
Evidence
From logs/evidence/M1-03B/. Click a thumbnail for the full image.
Logs and data (1)
snapshot-bench.json · 3 kB · open
{
"task": "M1-03B",
"what": "snapshot bytes per client (movement share, WebSocket header included) vs NETCODE_1000.md Bandwidth math; tick + all snapshots on one thread",
"build": "release",
"ticks": 400,
"ackDelayTicks": 3,
"everyoneWalks": true,
"criterion": "pass = max client <= 32 KB/s and every packet <= the budget; tick + snapshot time is reported (withinP99Target) but only meaningful on an idle machine",
"pass": true,
"scenes": [
{"id": "quiet-field-25", "players": 400, "monsters": 0, "areaM": 640, "radiusM": 90, "interestSetAvg": 23.1, "sentEntitiesAvg": 23.1, "kBpsPerClient": {"mean": 1.33, "max": 1.86, "docMovement": 1.8, "hardCap": 32}, "maxPacketBytes": 178, "packetBudgetBytes": 1596, "deferredPerClientTick": 0.000, "tickPlusSnapshotsMs": {"p99": 198.476, "max": 207.876, "threads": 1, "withinP99Target": false}, "pass": true},
{"id": "busy-hunting-70", "players": 400, "monsters": 0, "areaM": 380, "radiusM": 90, "interestSetAvg": 57.6, "sentEntitiesAvg": 57.6, "kBpsPerClient": {"mean": 2.65, "max": 3.41, "docMovement": 6, "hardCap": 32}, "maxPacketBytes": 352, "packetBudgetBytes": 1596, "deferredPerClientTick": 0.000, "tickPlusSnapshotsMs": {"p99": 122.699, "max": 166.809, "threads": 1, "withinP99Target": false}, "pass": true},
{"id": "crowded-town-300", "players": 1000, "monsters": 0, "areaM": 226, "radiusM": 70, "interestSetAvg": 245.0, "sentEntitiesAvg": 148.0, "kBpsPerClient": {"mean": 9.40, "max": 10.30, "docMovement": 14, "hardCap": 32}, "maxPacketBytes": 647, "packetBudgetBytes": 1596, "deferredPerClientTick": 0.000, "tickPlusSnapshotsMs": {"p99": 564.774, "max": 788.338, "threads": 1, "withinP99Target": false}, "pass": true},
{"id": "channel-1000p-4000m", "players": 1000, "monsters": 4000, "areaM": 1024, "radiusM": 90, "interestSetAvg": 119.1, "sentEntitiesAvg": 119.0, "kBpsPerClient": {"mean": 4.80, "max": 6.07, "docMovement": 6, "hardCap": 32}, "maxPacketBytes": 580, "packetBudgetBytes": 1596, "deferredPerClientTick": 0.000, "tickPlusSnapshotsMs": {"p99": 219.464, "max": 276.793, "threads": 1, "withinP99Target": false}, "pass": true},
{"id": "town-crowd-1000p-4000m", "players": 1000, "monsters": 4000, "areaM": 256, "radiusM": 70, "interestSetAvg": 254.7, "sentEntitiesAvg": 149.8, "kBpsPerClient": {"mean": 9.57, "max": 10.40, "docMovement": 14, "hardCap": 32}, "maxPacketBytes": 648, "packetBudgetBytes": 1596, "deferredPerClientTick": 0.000, "tickPlusSnapshotsMs": {"p99": 520.932, "max": 710.750, "threads": 1, "withinP99Target": false}, "pass": true}
]
}
Tests
$ cargo run --quiet --release -p zoen-zone --bin bench-snapshot -- --out logs/evidence/M1-03B/snapshot-bench.json$ pnpm --filter @zoen/wiki exec playwright test --only-changedNotes
2026-10-10 09:46 UTC · Split: parts 1-2 done here, WebSocket endpoint -> M1-03C (roadmap card added; std-only RFC 6455 because new crates need the owner's OK). Built: packages/protocol/zoen.schema.json (zoen-protocol/1, protocolVersion 1: hello 1, join 2, input 3, snapshot 4 v1, ack 5, ping 6, pong 7) + crates/zoen-protocol (no deps, hand-written reference codec; C# codegen later): bits.rs LSB-first BitWriter/BitReader, msg.rs fixed LE messages + MESSAGES table, snapshot.rs (19 B header incl. exact own x/y + lastInputSeq, then leaves/updates/enters bitstream; 9-bit baseline index, 4-bit field mask, pos 1-bit mode 10-bit signed delta or 18-bit absolute, yaw 8, anim 6, hp 10, entity id 16), ClientView decoder with frame ring; tests: schema.rs (ids/names/directions/fields/versions/header/bits vs schema), roundtrip.rs (bits, messages, 400-tick random snapshots vs any kept baseline incl. lost packets, 20k corrupt inputs fail cleanly). services/zone snapshot.rs: ClientReplica per client = nearest maxSentPerSnapshot (150) of the interest set, Quake-3 baseline = newest acked frame within 32-frame history else full snapshot, leaves always, enters (nearest first) then due changes (priority accumulator += band Hz per tick, due at tickHz, highest first, reset on send) within a 1,600 B packet; zone tracks facing (yaw8 from move input). tests/snapshot.rs: decoded == sent every tick, sent values == truth, near entities fresh, set == nearest 150; rates exactly 180/90/36 sends in 180 ticks (20/10/4 Hz); lost acks (30%) + lost packets (10%) + 60 silent ticks -> older baselines then full snapshot; town crowd within budget; tests/snapshot_no_alloc.rs 0 allocations (50 clients, 100 ticks). netcode.json snapshot group (doc: bits 18/10/8/6/9, rates 30/60/90 m at 20/10/4 Hz, 32 KB/s cap, scene targets; proposed with note: positionQuantumMm 16, hpBits 10, entityIdBits 16, wsHeaderBytes 4, baselineFrames 32, animStates idle/run; KB = 1000 B). protocol.test.mjs checks schema vs netcode.json. Evidence logs/evidence/M1-03B/snapshot-bench.json (pnpm zone:snapbench, everyone walking, acks 3 ticks late): KB/s per client mean/max quiet-25 1.33/1.86 (doc movement 1.8), busy-70 2.65/3.41 (doc 6), crowded town 300 -> 148 sent 9.4/10.3 (doc 14), full channel 4.8/6.07, town 1000p+4000m 9.57/10.4; max packet 648 B of 1,600; all under 32 KB/s. NOT measured: tick+snapshot p99 — the Mac had load average ~180 during the run (p99 120-560 ms, meaningless; sample profile shows normal hot spots: build, encode, interest radius query); re-run pnpm zone:snapbench on an idle machine (M1-03C or the M1-04 soak) for the card's <= 15 ms p99 criterion. Relevance multipliers (target/attacker/party/boss/telegraph) wait for those systems; height not encoded (2D sim).
