Sprint 4 Design Pack — GWP-247
Story Narrative
Section titled “Story Narrative”A handful of in-flight cartridge specs (Depthcharge intel scan ordering, ICE Breaker decoy fan-out, Bareterm contract roll, Null cartridge corruption draws) need a deterministic shuffle that respects the LFSR seed. Today, cart authors can call lfsr-next and roll their own Fisher-Yates in Lisp — but that costs Fe arena cycles per draw, gets verbose, and risks 14 cartridges each authoring a slightly different shuffle. The cleaner answer is one canonical primitive that does Fisher-Yates with the active LFSR and returns a fresh list. ADR-0005 originally flagged this as an open question because the C signature mutates an array in-place and Lisp is functional — the resolution is to expose a list → list wrapper that performs the shuffle internally and hands back a new arena-allocated list, leaving the input list intact (idiomatic Lisp). This is small in code but unblocks shuffle-shaped requirements across multiple launch carts and closes the only remaining ADR-0005 unknown that touches FFI scope.
Acceptance Criteria (expanded)
Section titled “Acceptance Criteria (expanded)”- Lisp signature:
(lfsr-shuffle list) → list- Returns a freshly-allocated list with the same elements in shuffled order.
- Input list is not mutated (pragmatic Lisp idiom).
- Element identity is preserved (cells are not deep-copied; the new list points at the same cell objects).
- Determinism: Given a fixed seed (
(lfsr-seed N)), repeated calls to(lfsr-shuffle '(a b c d))produce identical output ordering across runs and across emulator/device targets. - Implementation: Lives in
src/nosh_lisp_bridge.caslisp_lfsr_shuffle. Uses Fe arena allocation only (nomalloc). Internally calls existingstdlib_lfsr_next/stdlib_lfsr_rangefromnosh_stdlib.c— the LFSR core itself is not modified. - Test coverage (
tests/test_lisp_lfsr_shuffle.c):- Empty list → empty list (no error).
- Single-element list → identical single-element list.
- Two-element list with fixed seed → predictable output.
- Long list (≥16 elements) with fixed seed → byte-identical output across two consecutive runs (regression guard).
- Input list is intact after the call (mutation guard).
- Documentation: ADR-0005 amendment-log entry under a new dated section noting “lfsr-shuffle takes/returns a list; does not mutate; closes Known Unknown #1.” The contract row in §“Procedural Generation (LFSR)” is updated in-place to reflect the new signature.
Edge Cases (≥2)
Section titled “Edge Cases (≥2)”- Empty list / single-element list. Both must return without invoking
lfsr-next(no LFSR state perturbation for trivial inputs). Tests guard this explicitly because a naive Fisher-Yates may divide by zero or advance the PRNG unnecessarily. - Very large list vs. arena pressure. Cart authors might call
lfsr-shuffleon the full mission board (~64 entries) or a corruption table (~256 entries). Implementation must allocate the new list within the active Fe arena and document the cost: each shuffle costsO(n)arena cells. If the active arena is the per-mission-instance arena, it resets at mission boundary — fine. If a cart calls it from a long-lived handler context, repeated shuffles can grow arena footprint. Note for engineering: add a comment in the bridge function pointing at this risk and document the recommended pattern in the amendment-log entry: “prefer to shuffle once per phase, bind to a let, not in tight redraw loops.” - Calling
lfsr-shufflebeforelfsr-seed. Per existing LFSR convention, the LFSR has a default seed at boot (deterministic but uninteresting). Document that carts wanting reproducibility must call(lfsr-seed N)first; this is the same contract aslfsr-next/lfsr-range. No new error case introduced.
Engineering Hand-off Notes
Section titled “Engineering Hand-off Notes”- Files (additive only):
kn86-emulator/src/nosh_lisp_bridge.c— addlisp_lfsr_shuffleand register the binding alongside the otherlfsr-*primitives.kn86-emulator/tests/test_lisp_lfsr_shuffle.c— new file; add toSOURCES/add_executableinkn86-emulator/CMakeLists.txt(test target).docs/adr/ADR-0005-ffi-surface.md— amendment log entry + in-place contract update on the row at line 130.
- Do NOT touch:
src/nosh_stdlib.c(existing LFSR core is correct), Fe vendor sources. - TDD: brief the C engineer to use
test-driven-developmentskill — writetest_lisp_lfsr_shuffle.cfirst (red), implementlisp_lfsr_shuffleuntil green, refactor. - Expected size: ~30–50 LOC of C bridge + ~80–120 LOC of test. Single PR.
- Validation:
cd kn86-emulator/build && ctest -R lfsr_shuffleshould pass;ctestoverall should remain green. - ADR amendment-log dated header: follow the 2026-04-24 precedent already in ADR-0005 (Bitmap-FFI bounds retarget) —
### YYYY-MM-DD — lfsr-shuffle wrapper finalizedwith a Rationale paragraph and the Known-Unknown-#1 closure note.
Open Questions for Josh
Section titled “Open Questions for Josh”None. Task body and ADR-0005 between them fully specify the work. Engineering can dispatch directly.