Skip to content

Sprint 4 Design Pack — GWP-247

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.

  • 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.c as lisp_lfsr_shuffle. Uses Fe arena allocation only (no malloc). Internally calls existing stdlib_lfsr_next / stdlib_lfsr_range from nosh_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.
  1. 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.
  2. Very large list vs. arena pressure. Cart authors might call lfsr-shuffle on 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 costs O(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.”
  3. Calling lfsr-shuffle before lfsr-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 as lfsr-next / lfsr-range. No new error case introduced.
  • Files (additive only):
    • kn86-emulator/src/nosh_lisp_bridge.c — add lisp_lfsr_shuffle and register the binding alongside the other lfsr-* primitives.
    • kn86-emulator/tests/test_lisp_lfsr_shuffle.c — new file; add to SOURCES/add_executable in kn86-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-development skill — write test_lisp_lfsr_shuffle.c first (red), implement lisp_lfsr_shuffle until 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_shuffle should pass; ctest overall 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 finalized with a Rationale paragraph and the Known-Unknown-#1 closure note.

None. Task body and ADR-0005 between them fully specify the work. Engineering can dispatch directly.