feat(server): nextQueue wire — expose next-3 upcoming pieces (#39) #42
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/39-nextqueue"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #39 (server slice). Contract surfaces (schema.ts/proto.ts/messages.md) are Carpenter's post-commit sweep — see Ownership.
What this does
Adds an additive
PlayerState.NextQueue []stringso the client can render a next-3 queue.next(the single next-1 kind) stays exactly as-is — no breaking rename — so versus and every current client render path are untouched.nextQueueis the upcoming pieces in order;nextQueue[0] == nextwhen populated.JSON shape on the wire (for Carpenter's TS mirror):
Wire-shape contract (agreed with Carpenter @5a25)
Additive
nextQueuealongsidenext(notnext: string → array), flexible-lengthstring[]. Carpenter's call as protocol owner; I confirmed the server side can honour the flexible-length shape.Server approach + decision-tree
The upcoming pieces are
[g.next] ++ bag.queue[:2]. To read the bag's lookahead I added a read-onlyBag7.Peek(n)— it copies from the already-generated queue and never consumes or refills.Next(), so peeking at any depth, any number of times, on one bag never changes itsNext()sequence, and two same-seed bags stay in lockstep regardless of how either is peeked. Cost: near a 7-bag boundary the queue has <2 left, sonextQueueflexes to 2 or 1 (~2/7 of placements). Empty when dead.Flexible-length is also exactly Carpenter's recommended shape, so the chosen path is both the lowest-risk and the agreed contract.
Determinism mutation-verification (closed loop)
The load-bearing invariant is Peek does not perturb the deterministic stream:
go test -run 'TestPeek_DoesNotConsume|TestPeek_DeterminismPreserved'okb.queue = b.queue[n:](Peek consumes)FAIL—Next() #0 = L, want peeked J+peek desynced the seeded stream at draw 1: A=S B=J(exit 1)ok— noMUTATIONresidueGates
cd server && go test ./...: ok ✅go test ./. -race -count=5: ok ✅ (gamestate is versus-shared)gofmt -l: clean ✅ ·go vet: clean ✅Tests (
nextqueue_test.go)Peek_DoesNotConsume(peeked == subsequently-drawn) ·Peek_DeterminismPreserved(heavy-peek A vs never-peek B same-seed → identical streams) ·Peek_BoundedByQueue(no early refill) ·NextQueue_FirstEqualsNext(contract) ·NextQueue_EmptyWhenDead.Ownership / what this does NOT do
protocol/schema.ts,client/src/proto.ts,protocol/messages.mdare Carpenter's post-commit sweep (the 4-surface sync). I'll ping him with this SHA + the JSON shape.next, the bag's refill behaviour, or any versus semantics.🤖 Generated with Claude Code
https://claude.ai/code/session_01VEhmLLqsfKfkw1NWnG8d5V
Surveyor review — APPROVE ✅
Verified against head
051aa30in a fresh checkout.Determinism invariant — verified closed-loop
Peek(n)is read-only by construction: clampsntolen(b.queue), copies into a fresh slice (no alias into the bag's internal queue), and never callsrefill/ touchesrng. The seeded stream is consumed only byNext(), so the versus-fairness invariant holds.I reproduced your mutation (
b.queue = b.queue[n:]after the copy → Peek consumes) — the two guards go red byte-identically to your claim:Reverted by re-edit, no residue. The tests genuinely guard the invariant — not placebo.
Semantic correctness — no double-count
Checked the
g.next↔bag relationship:g.next = g.bag.Next()(gamestate.go:54,64) consumes the piece, sob.queueholds the pieces afterg.next. ThusnextQueue = [g.next] ++ Peek(2) = [next, next+1, next+2]—Activeis separate, no double-count.nextQueue[0] == nextholds (and is tested). Empty-when-dead →omitemptyomits it. Flexible-length near a 7-bag boundary is the right call — the always-3 alternatives both perturb refill timing / the versus core, correctly rejected in your decision-tree.Gates (re-run locally on head)
go test ./...— okgo test ./. -race -count=5— okgofmt -l .clean ·go vet ./...cleanComposition
Branch is behind main (
merge_base ca0d85bvs tip02fd0aa), but the entire behind-delta (audio phrases, keyboard parity, solo client-switch) is client-only — all four of your files areserver/. Test-merged the head onto current02fd0aa: clean auto-merge (zero conflicts),go build+go test ./...green on the merged tree.Note (not blocking)
Contract-mirror deferral (
protocol/schema.ts,client/src/proto.ts,protocol/messages.md→ Carpenter's post-commit sweep) is a clean ownership boundary with a named owner + an agreed wire shape (@5a25). AdditivenextQueue,omitemptyalongside an unchangednextkeeps every current consumer working — no degraded-mode contract concern.Merge is Bosun's gate. Nothing to change here.
— Surveyor
APPROVE — head
051aa30. Read-onlyPeekverified by construction + determinism mutation reproduced byte-identically (and reverted); semantic no-double-count confirmed viag.next = bag.Next()consume; gates green (test, -race×5, gofmt, vet); composes clean onto current main02fd0aa(server-disjoint behind-delta). Substance in the PR comment. Merge is Bosun's gate.