libs/game/coedit (zero deps, so merge logic is testable without a socket or
a VM). Not a CRDT: transactions are host-serialized into an append-only
generation history, and a conflict is answered by handing the author the new
base so THEY re-derive their intent.
- Transaction carries the author's whole intended file, not a patch: the
diff against its declared base is derived host-side, so a stale or
malformed patch can never be applied — and an AI writes whole files anyway
- diff3 over lines with LCS anchors. Merge::Conflict deliberately carries NO
merged text: a half-merged game file that still parses is worse than an
honest rejection. Conflict -> Rebase{new base + per-generation summary of
what landed underneath}. An edit already present in the tip is refused as
NoChange rather than appended as an empty generation
- Leases are advisory as designed: a test asserts a submit SUCCEEDS while
another author holds the lease. They shape who chooses to edit; they never
gate the log. TTL expiry means a crashed author cannot lock a region
- Wire: coedit is reliable-channel ONLY — a test signs a valid submission,
sends it by datagram, and asserts it is ignored, so no datagram can
rewrite the game. Every response is addressed, never broadcast
- Arcade bridge routes in exactly one place; local agent and remote authors
share the queue and the rules, and a test asserts the local agent gets
rebased identically when it loses a race. Remote players map to
AuthorId(player+1) so a client holding player id 0 cannot impersonate the
host's agent
- 200-round deterministic fuzz: 4 authors submit against deliberately stale
bases, rebase, resubmit — asserting linear append-only numbering, no
generation claiming a base from the future, and that replaying accepted
diffs from generation 0 reproduces the head exactly
Two real bugs found building it: validate used `?` on the base lookup, so an
unknown base returned "no refusal" instead of UnknownBase; and
MAX_COEDIT_SOURCE (512 KiB) exceeded MAX_FRAME_BYTES (256 KiB), so the host
would have accepted a source it could never hand back inside a Rebase,
stranding the next author on an answer that never arrives. Both now 192 KiB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
from_tcp_stream wrapped the socket in a BufReader that was dropped on
return, so body bytes read ahead into it vanished and handle_post blocked
forever on bytes that no longer existed — one wedged thread per request
that sent headers and body in the same TCP segment. Browsers split the two,
which is why nothing noticed. The function now owns its buffer, reads to
\r\n\r\n, and returns the remainder alongside the headers for handle_post
to consume first. The websocket upgrade path had the identical exposure (a
frame pipelined with the upgrade was silently dropped) and consumes the
same prefix now; EOF mid-head returns instead of spinning to the 4096-line
guard. New tests cover headers+body in ONE write (the case that hung, with
the connection held open afterwards so a regression blocks rather than
passing on EOF), the split case, and a plain GET.
game_net: Intent::Authoring{text} + MAX_AUTHORING_TEXT so a keyless client
in a hosted room can route a creation request to the host's agent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Harvest-and-rebuild from xr/src/net after the adversarial audit (verdict:
transport promote-with-fixes, authority model rewrite).
- Authority: explicit Host/Client roles. Client->host Join/Input/Intent/
Leave/Ping; host->client Welcome/StateBatch/Event/Bye/Pong. No per-object
authority field and no takeover messages — authority theft (audit H-11)
is unrepresentable, not merely blocked
- Auth: self-contained SHA-256/HMAC; every datagram and frame is
magic|version|sender|payload|mac, verified BEFORE any peer state is read
or written. Closes seq-window poisoning, spoofed kick, address hijack
- Endpoints are pumped, not threaded: nothing blocks, so the connect-flood
stall (H-5) cannot occur and a full session runs deterministically in one
test process. The host only ever accepts, never initiates
- Harvested: LZ4 frame codec (check-before-allocate), partial-tail drain,
budgeted read/write loops, MTU batching, peer/config shapes. Frame cap
4MiB -> 256KiB now that XR alignment payloads are gone
- Per-entity sequencing (a stale datagram drops only its stale members),
rejoin seq reset, player cap, peer timeout, snapshot-based mid-join
- Measured 6 clients x 60Hz x 200 entities: 2880 pps, 3.13 MB/s (~25 Mbit
up) — Quest WiFi viable without delta encoding yet
- Hostile suite: one test per audit attack + 3000-mutation fuzz that must
never panic or wedge the host. 24/24 green
- micro_serde: String::de_bin no longer panics on invalid UTF-8, checked
offset arithmetic, Vec::de_bin rejects counts the buffer cannot back and
never sizes allocation from the wire (11/11)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>