Skip to content

Make the running game pedagogy-first: lesson cards, spaced retry, Codex, and sound - #31

Merged
shpwrck merged 96 commits into
mainfrom
claude/codequest-opus-improvements-0069d2
Sep 25, 2026
Merged

shpwrck merged 96 commits into
mainfrom
claude/codequest-opus-improvements-0069d2

Conversation

@shpwrck

@shpwrck shpwrck commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

The last commit (#30) made the design skill pedagogy-first. This PR makes the running game pedagogy-first, adds sound and a screen-reader transcript, and fixes 104 adversarially verified defects from three independent reviews: 36 in the pre-existing code, 26 + 42 in this branch's own work.

What a player sees now

  • Every answer teaches. Each choice carries a rationale. After a miss, a lesson card explains the misconception behind your pick, then why the answer holds.
  • Guessing and position don't win. Previously 11 of 12 generated answers sat in slot 1, and the cursor starts there. Choices are now shuffled per question, and every retry moves them.
  • Misses come back with real spacing. A retry always arrives exactly three questions later, possibly in the next batch. A same-launch fix counts as relearning, and only a later-launch success counts as mastery evidence.
  • Mastery is honest and visible. Runes per concept lens reflect recent accuracy, and they show cracked while a review is due. The new Oracle Codex shows mastery and lets you self-test on pending lessons, with the answer sealed until you press A.
  • Difficulty deepens concepts. Levels move from purpose and roles, to flows and tradeoffs, to invariants plus PREDICT transfer questions. Batches adapt to your weakest lens and never repeat.
  • Every screen is authored. The Datafall wait shows the true failure reason and recalls past lessons. Level-ups and game over end in a learning debrief.
  • Sound, accessibility, robustness. Engine-owned four-voice chip sound with a volume wheel, reduced motion inside the framebuffer, and a screen-reader transcript of every screen. Cartridge loading runs off the UI thread, timeouts kill the whole process tree, prompts go over stdin, and saves are refused rather than overwritten when corrupt.

Measured live on this repository

Before After
Claude (Sonnet): accepted questions 8/12, surface-level 69% raw, 95% with the repair pass
Codex (gpt-5.6-sol): accepted questions untested 78/78
Correct answer in slot 1 11/12 spread across all slots, then shuffled
PREDICT transfer questions at level 4+ 0 at least 2 per batch
Codex requests hitting the 120 s timeout — 1 in 7, fixed with provider-aware deadlines

Screens (real app, live Claude questions)

Lesson card after a miss Codex remembers it
lesson codex

How it was built and verified

  • Parallel streams in isolated worktrees, integrated here: learning model, question pipeline, lesson flow, Codex, sound, shell hardening, engine and backend fixes, transcript, design-panel improvements, mutation sweep, performance and property tests.
  • Three adversarial review passes (10, 12 and 4 lenses), with two skeptics per finding. Every confirmed finding was fixed with a regression test, and checkers mutated each fix to prove its test fails without it.
  • Local gates: cargo fmt, clippy -D warnings, the full Rust suite (300+ tests, including seeded property tests), Node contracts (including a fake-DOM harness that runs the real main.js), and the manifest validator.
  • The real release build was driven under Xvfb with live Claude generation: batteries, native folder picker, lesson card, Codex.
  • CI: Linux verify, a new Windows/macOS clippy and test job, and three-platform package build plus launch gates.

Test plan

  • fmt / clippy -D warnings / cargo test / npm test / validator: green locally
  • Native 240×160 previews of every scene inspected
  • Real app end to end with live questions (Xvfb)
  • Live question-quality eval for both providers (scripts/eval-questions.sh)
  • CI on this PR, including the new Windows/macOS test job
Full release notes

Release notes: claude/codequest-opus-improvements-0069d2 vs main

Scope: 56 commits (git log main..HEAD), 46 files, about +23,300 / -2,300 lines.
New modules: learning.rs, questions.rs, repo_context.rs, provenance.rs,
audio.rs, engine/transcript.rs, question_eval.rs, src/speaker.js,
src/device-shell.js. package.json still reads version 0.2.4. This branch has
not been tagged or released.

Highlights

  • Every answer comes with a lesson card. After you commit an answer, the
    choices are replaced by an explanation. On a miss it shows why your pick was
    wrong, then why the correct answer holds. The card waits for A or Start
    (e417b41).
  • Missed questions come back. A miss returns three questions later, and a
    batch cannot finish until its misses are retried. A miss also carries over to
    later runs and launches until you answer it correctly (e417b41, d2729b1).
  • Answer position no longer gives the answer away. Choices are shuffled per
    question and per attempt, and every retry moves every choice (4067e07,
    e417b41).
  • The Oracle Codex. A new menu option shows mastery for each lens as three
    runes that light at 1, 3 and 5 pieces of evidence, and pages through every
    lesson you have earned (a3746df).
  • The game has sound. A four-voice chip sound engine covers the whole sound
    ledger. A volume wheel (or V) steps through MUTE/LOW/MID/HIGH, and the
    setting is remembered (8b9f00f).
  • Screen-reader support and reduced motion. An engine-written transcript of
    every screen goes to a live region. When the system asks for reduced motion,
    decorative motion in the framebuffer stops (4cc9b71, 4446aae, 4ca8249).
  • Questions ask about concepts, not trivia, and most of them now pass
    validation.
    In a live run, 11 of 12 generated questions were accepted,
    against 2 of 12 before calibration (f09c96e).

How the game teaches now

  • Five concept lenses. Every generated question is tagged purpose, roles,
    flows, invariants or tradeoffs, and carries a rationale for each choice
    (payload v2, d2729b1).
  • Lesson card. The player's pick is shown in red with its misconception,
    then the answer in green with its rationale. - and + markers repeat the
    verdict without relying on colour. The footer shows the lens and its three
    live runes. A 45-tick input hold comes first, then the card waits for a fresh
    A or Start (e417b41).
  • Spaced retry. A miss you survive inserts a review copy RETRY_GAP (3)
    questions later, never past the end of the current batch. Later batch ends
    shift to make room. A correct review shows REDEEMED and is recorded apart from
    a first-try success. A miss that breaks the last ward goes to game over
    without scheduling a retry (e417b41).
  • The retry rule is on screen. The header reads TRIAL 2/7 and grows with
    each miss. A returning question is labelled RETRY in amber. The miss card
    says BACK IN N, UP NEXT or NEXT RUN. A plain success reads CLEAR SIGHT,
    so REVIEW only means a retry (97dd4bb). An open short batch counts toward six
    (310f878).
  • Lens mastery. Mastery is recorded per lens and persisted. Lighting a rune
    is the loudest moment in a run: it gets its own banner (ROLES RUNE I), a
    blinking footer rune and a LensWake cue that outranks every score cue.
    Score thresholds were renamed INSIGHT I/II/III, so the word RUNE on a
    banner always means a lens (97dd4bb).
  • Difficulty changes the concepts, not the pressure. Level 1 focuses on
    purpose and roles, levels 2-3 on flows and tradeoffs, and levels 4+ on
    invariants, with PREDICT transfer questions. The prompt enforces this; the
    acceptance filter does not (see Known limitations). Each request names your
    weakest lens and your earlier question stems (d2729b1, 451419d).
  • Debriefs.
    • The Aftermath screen shows a learning ledger: 1ST TRY a/b,
      REDEEMED NN, REVIEW NN or ALL CLEAR, and the lens that rose most, or
      SEE CODEX while reviews are open.
    • The Ascension screen says ASCENDS only when you cross a presentation tier
      and DEEPENS within one. It recaps the batch and names the lenses of the
      next batch.
    • The legacy renderers get the same rows (2bc7d29, 53ded42).
  • Recall practice while you wait. During the Oracle wait, the Datafall
    header gains a line that cycles journal lessons every 4 s (outstanding misses
    first), or shows why generation failed, with RETRY IN NS (bcc682e).
  • Leaving takes two presses. B during an active question arms
    B AGAIN:LEAVE for 90 ticks. B does nothing on the lesson card (e417b41).

Question generation

  • Richer, anonymized brief. repo_context.rs builds a deterministic,
    size-limited brief from:

    • the README and design notes
    • a CODEQUEST.toml summary
    • doc-comment and signature skeletons from up to 30 components, labelled
      COMPONENT n

    Path-like words and tracked file and directory names are redacted. Vendored,
    generated, test, binary, huge and symlinked files are skipped (d2729b1).
    Redaction now also catches engine.rs:42, Cargo.toml's, _engine.rs_,
    engine.rs::wrap, and marked directories such as src-tauri. Hidden HTML,
    reST and AsciiDoc comments, link definitions and TOML front matter are
    removed (ec5cc9d).

  • Anti-trivia filter. The filter now matches phrases and word shapes, not
    bare nouns. WHY WRITE THE SAVE FILE ATOMICALLY? passes. Which-file,
    where-does, count, commit, branch, author, version, hash and path questions
    are still rejected (d2729b1).

  • More tolerant parsing. The parser finds the question array inside prose
    or code fences and recovers finished questions from a truncated reply. Bad
    questions are dropped one at a time, so the rest of a mixed batch survives
    (d2729b1).

  • Calibrated limits.

    • Before calibration, a live run (Claude Sonnet, levels 1 and 4) accepted
      2 of 12 questions, because choices ran 32-45 characters against a
      31-character limit.
    • The prompt now asks for choices of 2-5 words and at most 28 characters, and
      questions of at most 100 characters.
    • On re-run, 11 of 12 were accepted. The one rejection was a correctly
      caught location question. Answers were spread across all four slots, there
      were three PREDICT questions at level 4, and the level focus held: 5 of 6
      questions were purpose/roles at level 1, and 6 of 6 were invariants/tradeoffs
      at level 4.
    • For comparison, the pre-branch generator accepted 8 of 12 surface-level
      questions and put 11 of 12 answers in the first slot (f09c96e).
  • Rationale cap from display geometry. RATIONALE_MAX_CHARS is 70, derived
    from the 34-column, 3-row panel with a proof in its doc comment. The old
    90-character allowance let through rationales that could not fit (ec5cc9d).

  • One repair pass. Questions that fail only on display mechanics (length,
    missing why, unknown lens, non-ASCII) are sent back once with every violation
    and its measurement. The repair uses only what is left of the 120 s budget
    and is skipped when less than 10 s remains. Trivia and malformed questions
    are never repaired (3bb18f3).

  • Prompts go to the CLI on stdin, not on the command line. claude -p and
    codex exec - receive the prompt on stdin, so the Windows command-line limit
    no longer trims the brief. The limit on prompt size is now MAX_PROMPT_BYTES
    (1016c5d).

  • Full batches. Batches always hold six questions: a short delivery tops up
    the open batch. Lower-level saved batches play first (50d6856, 390e428).

Sound

  • Sound is derived from observable state. A per-tick AudioSnapshot is
    compared with the previous tick to drive four chip voices (two pulses, a
    triangle and noise). Gameplay code never calls audio. At most one cue starts
    per tick, and scene loops are cut on the tick their scene exits. Nothing
    sounds while the device is off or booting (8b9f00f).
  • Original compositions for the whole ledger. They include:
    • chronicle ticks and an opening motif that grows to four voices
    • title and menu loops
    • a Datafall ambience with a voice per tier
    • answer cues, a last-ward heartbeat, and tiered reward cadences (8b9f00f)
    • Codex page turns, a climbing cadence for a redeemed review, and a warning on
      the first B of a leave (390e428)
    • LensWake (97dd4bb)
  • Speaker (src/speaker.js). Plays notes through WebAudio: pulse duty as
    periodic waves and noise from a 15-bit LFSR. The audio context starts only on
    a user gesture (8b9f00f).
  • Timing fixes.
    • The tick clock now re-anchors outside a 20-120 ms lead window. Before, an
      engine overrun made every later note clamp to poll time.
    • A cut that arrives too late still ends its voice, so long loop notes no
      longer ring past their cut.
    • Noise brightness above the rate ceiling becomes a highpass, so the archival
      tick and the hi-hat no longer sound identical (35d7fea).
  • Muting loses no information. Every answer, threshold and status stays on
    screen when muted (README).

Accessibility

  • Reduced motion. The shell forwards prefers-reduced-motion, at startup
    and on change, through engine_set_reduced_motion. Decorative motion
    freezes: blinks stay lit, heroes stop bobbing, starfields stop. A test
    asserts an identical 720-tick scene timeline in both modes (4ca8249). New
    lesson lines, rune blinks and the recall line are also static under reduced
    motion (bcc682e, 97dd4bb). There is no in-game toggle.

  • Screen-reader transcript. A third engine output channel describes every
    screen in plain language:

    • credits, opening beats, title and menu
    • hero creation and Datafall status
    • the question with its choices in display order and the focused choice
    • the lesson card, leave confirmation and level-up
    • game over, the Codex and quests

    It is derived from the renderers' own helpers, so the words match the pixels.
    It is published only when it changes and polled every 150 ms into a hidden,
    polite live region (4cc9b71).

  • Only what changed is announced. Pressing Down no longer re-reads the whole
    question, and each Datafall catch no longer re-reads the screen. A new screen
    is read whole, while small in-place changes are read on their own (4446aae).

  • Keyboard and focus (shell).

    • Enter on a focused shell control activates that control instead of sending
      START.
    • The FRONT/BACK toggle no longer keeps mouse focus.
    • Both trays are labelled modals: the device goes inert, Tab is trapped, and
      focus is restored on close and after rebuilds.
    • A visible aria-live device status strip carries messages from outside the
      trays (4068303).
  • Lesson card colour. Contrast is tested against the panel fill, and the
    -/+ markers do not rely on hue. The recall line and debrief text meet
    at least 4.5:1 (e417b41, bcc682e, 2bc7d29).

Reliability and fixes

Question requests and batches

  • A failed first request, made before the battery was verified, is now retried
    behind the opening instead of leaving the Oracle waiting (50d6856).
  • A stale or superseded question reply no longer overwrites the loading state
    or a newer batch; requests carry a sequence number (50d6856).
  • Short deliveries no longer leave a level-up after fewer than six questions
    (50d6856).
  • Prefetch no longer re-requests a level already in the queue (50d6856).
  • A new Initiate run no longer opens on leftover Oracle-bound questions
    (390e428).
  • Review copies no longer count toward batch fullness. Before, a short batch
    padded by reviews closed early (a8b5669).
  • A regenerated question stem of an earlier miss now arrives as a review, and
    the same question is never queued twice (a8b5669).
  • The Oracle now reports why generation failed (TIMED OUT, RATE LIMITED, LOGIN
    NEEDED, REJECTED BATCH, and so on) instead of retrying silently (bcc682e).

Runs and scene flow

  • A new run no longer replays questions answered earlier in the session, and it
    re-queues outstanding misses as reviews (50d6856).
  • Entering the Oracle or quiz from any outside route starts a fresh run, so a
    stale run index, level or score cannot carry over (a8b5669, 50d6856).
  • Quiz B works without a live run. The level-up prompt follows the manifest's
    can_signal timing, and the quiz menu focuses BEGIN again (50d6856).
  • A review that outlives its run or launch keeps rotating its choices instead of
    restarting at attempt 1 (a8b5669).
  • The header no longer reads TRIAL 2/2 on an open short batch (310f878).
  • The legacy footer's retry note no longer runs into the ward banner as one
    phrase (310f878).

Git, cartridges and saves

  • A git call that times out, is missing, or fails no longer reads as NOT A GIT
    REPOSITORY and forgets the cartridge. Each case reports its own reason. The
    check runs under LC_ALL=C so the refusal is recognized in any locale
    (91b80c0, 6be1aca).
  • A corrupt save is reported as CARTRIDGE SAVE IS CORRUPT and left untouched.
    Before, it was read as empty and overwritten (91b80c0).
  • Batch writes and answer records no longer lose each other: saves use an
    atomic read-modify-write under the save lock, and answers are written on a
    background thread (d2729b1).
  • Cartridge loads and ejects no longer freeze the UI. Git and save work runs off
    the UI thread, and every git query has a 15 s timeout (1016c5d).
  • Power-on waits for an in-flight load, eject, or folder picker
    (WAIT FOR THE CARTRIDGE). The slot refuses changes while power is on, and a
    second eject click is ignored (6be1aca).
  • A failed load keeps its rack entry unless the folder is no longer a Git
    repository. Recycling a card clears a saved slot that points at it (4068303,
    6be1aca).

Providers and processes

  • A grandchild process holding the pipes can no longer outlive a timeout: calls
    run in their own process group and time out by killing the whole tree
    (killpg, or taskkill /T /F) (1016c5d).
  • Aborting a quest now stops its whole process tree, and the slot is freed when
    the shell exits (a8b5669, a3dc60e).
  • The device now shows the real cause of a provider failure instead of the Codex
    banner or CLI version. Only error: and fatal: lines count as a cause
    (91b80c0, e263e5d).
  • On Windows, the standalone Codex installer and npm .cmd shims are now found
    (1016c5d).

Device shell

  • Held Space or Enter toggles power once, and held cartridge keys insert once
    (4068303).
  • A power-on joins an in-flight readiness probe instead of starting a second
    CLI call (4068303).
  • The real power-on failure (for example CODEX CLI UNAVAILABLE) now appears on
    the device, the CHECK BATTERIES guide, the rear tab and the tray (4068303).
  • An engine error no longer erases the saved battery choice, and power-off
    clears a stale CHECKING state (4068303).
  • An early P no longer leaves a stale NO BATTERIES error, and startup can no
    longer undo a power-on (6be1aca).
  • A background branch refresh no longer rebuilds the rack in the middle of a
    drag (4068303).
  • A touchpad swipe turns the volume wheel one detent instead of about thirty
    (6be1aca).

Credits and docs

  • GPL-family repositories are no longer credited to the FSF, and Apache
    repositories are no longer credited to the [yyyy] [name of copyright owner]
    placeholder. A contributor with two addresses no longer takes two author
    slots (0ca60fa).
  • The reference manifests no longer promise that every recorded question is
    skipped forever (fd76ce6).
  • The design brief and designer skill now match the runtime (451419d).

Developer experience

  • Tests.
    • Rust #[test] functions went from 103 on main to 302, counted by
      git grep. Eight carry #[ignore]; the live provider tests are among them.
    • npm test runs 12 Node contract files, up from 9. New:
      device-shell-robustness, device-audio and device-transcript.
    • tests/shell-harness.mjs runs the real shell adapter against a fake DOM, a
      manual clock and a scripted Tauri bridge.
    • tests/contract-css.mjs checks the reduced-motion CSS.
    • Previously vacuous checks were broken on purpose to prove they now fail
      (6be1aca).
  • CI. The Linux verify job is unchanged. A new verify-native job runs
    cargo clippy -D warnings and cargo test on windows-latest and
    macos-latest, under Git Bash on Windows, with a Swatinem cache. The Windows-
    and macOS-only code was never linted before (7f26eaa).
  • Question-quality eval. question_eval.rs is an ignored test run through
    scripts/eval-questions.sh <claude|codex> <levels> [repo]. It runs the app's
    real generation path and prints ACCEPT or REJECT for each candidate with the
    violated limits, then answer-position, lens, focus-lens and PREDICT tallies
    and the acceptance rate. CQ_EVAL_ROUNDS and CQ_EVAL_TIMEOUT_SECS tune it.
    It was updated for the repair-era parser (7f26eaa, 200171e).
  • LF checkouts. .gitattributes pins text files to LF and marks
    .rgb/.rgba and other assets as binary. Before, npm test could not pass
    on core.autocrlf=true Windows checkouts (07e5c1f).
  • Modules. The question domain moved out of lib.rs into the Tauri-free
    questions.rs, and provenance parsing into provenance.rs (d2729b1,
    0ca60fa).

Compatibility

  • Save format. SCHEMA_VERSION stays 1.
    • Legacy saves load without conversion, including Claude-era batch keys and
      plain-string choices without rationales. Those questions play without
      lesson rationales.
    • Existing answered_questions load as retired questions.
    • New fields (missed_questions, per-lens mastery, question_attempts,
      question_batch_levels) are optional (d2729b1, a8b5669, 50d6856).
  • Behaviour change: misses are no longer retired. A wrong answer used to
    retire its question. Now it stays queued for review until redeemed
    (d2729b1).
  • Behaviour change: corrupt saves are refused. An unparseable save now
    refuses to load instead of being silently replaced. An empty file still
    starts a fresh save (91b80c0).
  • Manifests. The codex handler, the open-codex signal and the
    oracle-codex template are optional. Menus without the route keep
    RETURN TO TITLE, and schema v1 still loads (a3746df). The repository's own
    CODEQUEST.toml and docs/examples/CODEQUEST.toml now use these, and
    main has no codex handler. Because unknown handlers refuse the cartridge,
    builds from main would reject these updated manifests (inferred from the
    code, not tested).
  • Provider CLIs. Prompts now go to stdin (claude -p with no positional
    prompt, codex exec ... -). This depends on each CLI reading stdin in those
    modes (1016c5d).
  • Shell to engine. New Tauri commands: engine_audio, engine_transcript,
    engine_set_reduced_motion. engine_set_cartridge and cartridge_branch
    are now async. The browser demo returns no transcript and cannot add
    cartridges from disk (it shows RUN IN TAURI).

Known limitations

  • The acceptance filter does not enforce the level's focus-lens share (at
    least 4 of 6) or the PREDICT count; both are only asked for in the prompt
    (oracle-quiz.md, Open decisions).
  • The measured acceptance numbers come from one small live run (12 questions,
    Claude Sonnet, this repository). They are not a benchmark across providers or
    repositories.
  • Sound is chosen by trusted handler. No manifest field can select or change it,
    and manifest audio templates are future work.
  • Reduced motion follows only the system preference; there is no in-game
    toggle.
  • The continuity pass (slice 6) is still in progress. A dedicated explanation
    when AI is disabled is also future work.
  • On Windows, taskkill /T reaches helper processes only while the direct child
    is still running (91b80c0).
  • Score, wards, hero identity and tier do not persist across launches; only
    learner progress does (open decision).
  • Windows packages are unsigned. macOS packages are only ad-hoc signed and need
    a Developer ID and notarization for public distribution (README).
  • The README's AppImage example still names 0.2.3.

🤖 Generated with Claude Code

shpwrck and others added 30 commits September 25, 2026 09:57
core.autocrlf=true checkouts gave the Node contract tests CRLF sources,
and their multiline patterns failed (npm test could not pass on Windows).
Pin text to LF on every platform and mark the embedded .rgb/.rgba plates
and other assets binary so content sniffing can never rewrite their bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Introduce learning.rs, a Tauri- and Bevy-free contract for concept lenses,
per-choice rationales, answer evidence, per-lens mastery thresholds, the
lesson journal, and a deterministic choice-presentation order that spreads
provider-first answers uniformly and moves every choice on each retry.

QuizQuestion now carries its lens, rationales, and review flag;
CartridgeSpec carries the lesson journal and mastery; the answer recorder
receives full AnswerEvidence instead of bare question text. Behavior is
unchanged until the question pipeline and engine consume the new fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Committing an answer now opens a lesson card instead of auto-advancing.
The four choice frames give way to one composed panel: a miss shows the
player's pick in red with its rationale (the misconception it reveals),
then the correct answer in green with its rationale; a success shows the
answer and why it holds. "-" and "+" markers repeat the verdict without
relying on hue. The trial panel footer names the question's lens with its
three live mastery runes and shows A:CONTINUE once the existing 45-tick
input hold ends; A or Start then continues. The legacy renderer gets the
same card over its choice rows. Native layout tests cover worst-case copy
(31-character choices, three 34-column rationale rows) for containment,
sibling disjointness, and contrast against the panel fill, and write the
oracle-lesson-correct / oracle-lesson-missed previews.

Choices are drawn in learning::presentation_order, keyed by the
normalized question identity and the attempt number in this run (review
questions start at attempt 1). run.selected is a display slot mapped to
the source choice at commit, so the provider's answer position no longer
predicts the answer and every retry moves every choice.

A survivable miss inserts a review copy RETRY_GAP (3) questions later,
never past the current batch end, and shifts every later batch end, so a
batch cannot complete before its misses are retried. Level-up still
fires exactly at the shifted end; a ward-breaking miss goes to game over
without scheduling a retry. A correct review shows REDEEMED unless an
Insight Rune crossing takes precedence. Every commitment records lens
evidence into cartridge.mastery and upserts the cartridge lesson journal
by question identity, clearing `outstanding` on a later correct attempt.

B during an active question now arms a 90-tick in-scene confirmation
(B AGAIN:LEAVE); a second B inside the window leaves, any other button
or the timeout disarms it, and B is inert on the lesson card. Existing
flow tests that relied on the 45-tick auto-advance now press A after the
hold, and answer by locating the shuffled answer slot. The Oracle quiz
brief and README describe the new flow and resolve both open decisions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chronicle card credited every GPL-family repository to the Free
Software Foundation (the notice at the top of the license text itself) and
every Apache-2.0 repository to "[yyyy] [name of copyright owner]" from the
license appendix, because the first line starting with "copyright" in
LICENSE won and LICENSE was read before NOTICE.

Move provenance parsing into a pure provenance module: prefer COPYRIGHT and
NOTICE files over license texts (and read COPYING), reject license-steward
notices inside GNU license texts, template placeholders, enumerated "(c)"
clauses, and prose such as "copyright notice" or "copyright holders".

Author credits now come from "shortlog -sn" and merge one person across
spellings case-insensitively, ranked by total commits, so a contributor with
two addresses no longer takes two of the three slots. The timeline reads
root commits and the newest commit instead of formatting every commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The running game now makes learning evidence visible. A new read-only
`codex` scene handler shows every concept lens with three mastery runes
that wake at exactly 1, 3, and 5 pieces of evidence (first-try successes
plus redeemed misses) and an amber count of that lens's lessons awaiting
review. Each later page rereads one journal lesson: counter, lens and its
runes, the question, the correct answer, and the rationale, with REVIEW
PENDING in amber for outstanding lessons. Left/Up/L and Right/Down/R page
with wrap; A and Start are deliberately inactive; B emits back. An empty
journal says NO LESSONS YET and explains how to fill it.

The quiz menu may now emit a new `open-codex` signal. Its second option
reads OPEN THE CODEX only when the current menu scene routes that signal,
and otherwise keeps RETURN TO TITLE, so manifests without the route behave
exactly as before; B still returns to the title. With the route, the menu
subtitle summarizes the journal (LESSONS 07  REVIEW 02 / ALL CLEAR). The
built-in quiz template and both CODEQUEST.toml copies add quiz-menu ->
codex -> quiz-menu, a review-lessons mechanic, and a codex-frame art entry.

The new `oracle-codex` visual template composites the mastery page inside
the chronicle plate's archive frame and lesson pages over the trial
chamber; cartridges without it get a plain NAVY/INK renderer. Native
layout tests measure plate interiors, keep worst-case copy contained and
disjoint, and check contrast against both panel fills and the brightest
plate pixel under directly drawn copy. Rendered-frame tests cross the exact
rune breakpoints. The fast validator, schema reference, design brief, and
asset README list the new handler, signal, and template. The Oracle preview
test now expects 15 distinct frames (13 plus two Codex layouts), and the
dogfood template count is 12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix nine audited shell defects without moving gameplay state into JS:

- Enter on a focused shell control activates it instead of becoming START;
  the FRONT/BACK toggle no longer keeps mouse focus.
- Held Space/Enter toggles power once; held cart keys insert once; an
  insert in flight blocks another; a power-on joins an abandoned cycle's
  in-flight readiness probe instead of spawning another CLI call.
- The real power-on failure (for example CODEX CLI UNAVAILABLE) is shown
  on the device, on the CHECK BATTERIES guide, on the rear battery tab,
  and in the battery tray, which now guide installed-but-failed packs.
- A failed cartridge load keeps its rack entry unless the folder is no
  longer a Git repository; startup faults are shown, not swallowed, and
  an engine error no longer erases the saved battery choice.
- Provider checks are scoped to their power generation, and power-off
  clears a stale CHECKING state.
- One visible aria-live device status strip carries messages raised
  outside the trays; tray message lines stay rendered as live regions,
  and a failed startup leaves a persistent DEVICE FAULT.
- Both trays are labelled modals: the device goes inert, focus moves in,
  Tab is trapped robustly, focus is restored on close and after rebuilds.
- Background branch refresh relabels rack cards in place instead of
  rebuilding the tray mid-drag.
- The browser demo explains RUN IN TAURI when ADD FROM DISK is used.

Pure helpers live in src/device-shell.js and src/cartridge-library.js and
are pinned by tests/device-shell-robustness-contract.mjs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	src-tauri/src/engine.rs
One flow now pins the contract between the lesson card and the Codex: a
miss is journaled as a pending review with the correct answer and its
rationale, returns after the retry gap, is redeemed (clearing the pending
review and recording redemption in the lens), and the Codex pages through
exactly the journal the quiz wrote without changing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every entry in the Oracle sound ledger was still "Proposed" and the game was
silent. Sound now follows the same rule as rendering: it is a pure function of
observable state. A new audio module describes a constrained four-voice chip
(two duty-selectable pulses, a triangle-like wave, and noise). Each tick the
engine builds an AudioSnapshot of what the player can observe and the
AudioDirector diffs it against the previous tick to start cues, scene-owned
loops, and scripted opening beats. Gameplay code never calls audio, and new or
unknown scenes are silent until the ledger names them.

Notes carry the absolute engine tick they start on. Voices are monophonic and a
zero-volume note is a cut, so a scene's loop ends on the exact tick the scene
exits and a new cue cancels the previous cue instead of stacking. At most one
cue starts per tick, chosen by priority, so one input edge can never produce
two cues. Loops stay at or below chip volume 5 while every cue peaks above it.
Nothing sounds while the device is off or booting.

The compositions are original and cover the whole ledger: dry archival ticks
on the chronicle, an opening whose motif grows from one source pulse to the
four-voice Oracle cadence and tails before the title, restrained title and
menu loops, per-row trait timbres and an Oracle-motif bind cadence, a Datafall
ambience with two, three, and four voices by presentation tier, data, bug,
charge-rune, seal-break, retry, ready, and leave cues, one commit-and-result
cue per answer (rune, flow-stage, and ward variants), a last-ward heartbeat, a
batch telegraph and tier-grown reward cadence inside the 60-tick hold, a
defeat fall and score hold, and an Initiate replay cadence.

The director runs as a Bevy system after advance_game. EngineRuntime copies
its notes into a bounded 512-note queue each frame, and the new engine_audio
Tauri command drains it as { tick, notes }. The shell's new speaker.js module
maps ticks onto the WebAudio clock (60 ms lookahead, re-anchoring beyond
150 ms of drift), plays pulse duty through periodic waves and noise through a
15-bit LFSR buffer, and starts its audio context only on a user gesture. The
device gains a knurled volume wheel on the power-switch edge of both faces,
a three-LED detent meter, V to cycle MUTE, LOW, MID, and HIGH, persistence in
localStorage, and a VOLUME row on the rear label.

Rust tests cover silence while off or booting, one cue per input edge through
scripted engine input, loop cuts on the exit tick, loops yielding to cues,
tier-grown arrangements, the opening arc, cue priority and non-stacking,
determinism, the queue bound, and the engine_audio payload shape.
tests/device-audio-contract.mjs checks the speaker against a recording
WebAudio stand-in and keeps it presentation-only. The design brief and README
now describe the implemented runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shell's CSS already respected prefers-reduced-motion, but the engine
kept blinking prompts, bobbing heroes, pulsing sigils, twinkling
constellations, and scrolling starfields, which the brief left as the one
unfinished accessibility item.

The shell now forwards the host preference (at startup and on change) through
a new engine_set_reduced_motion command. Decorative motion reads a motion
clock that freezes at the resting composition, settling motion jumps to its
end state, and blinking prompts stay lit. Scene timing, input edges, Datafall
gameplay, and data are unchanged: a test drives both modes and asserts frozen
title and atelier frames plus an identical 720-tick scene timeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the question domain out of lib.rs into a Tauri-free questions module:
the saved-question model, the acceptance policy, prompt construction,
provider-response parsing, batch persistence, and learner progress. lib.rs
keeps the commands, provider plumbing, cartridge building, and wiring.

Generated questions now use payload v2: a concept lens plus a "why" for
every choice. Legacy saves with plain-string choices still deserialize and
play without rationales. Each generated question must name a lens that
learning::Concept::parse accepts and carry location-free rationales that
fit the lesson panel; failures drop individually so mixed batches survive.
Provider text is normalized to renderable uppercase ASCII, and parsing
finds the question array inside prose or fences and salvages finished
questions from a truncated reply. The anti-trivia filter now matches
phrases and word shapes instead of bare nouns, so questions such as WHY
WRITE THE SAVE FILE ATOMICALLY? pass while which-file, where-does, counts,
commits, branches, authors, recency, versions, hashes, paths, and real file
names stay rejected.

A new repo_context module builds a deterministic, budgeted project brief
from the README, design notes, a CODEQUEST.toml summary, and doc-comment
and signature skeletons of up to 30 components spread across the tree.
Excerpts are numbered, never named; path-like words and tracked file and
directory names are redacted; vendored, generated, test, binary, huge, and
symlinked files are skipped. Prompt v2 adds lens focus, PREDICT transfer
questions from level 4, misconception distractors and rationales, varied
answer positions, the weakest lens, and previously asked stems, and is
bounded to fit a Windows command line.

Progress keeps answered_questions as retired questions and adds
missed_questions and per-lens mastery. A wrong answer no longer retires a
question: misses return as reviews in later runs and launches until
redeemed. Cartridge loads now supply the lesson journal and mastery. Saves
gain an atomic read-modify-write update under the save lock so batch
appends and answer records cannot lose each other, and answers are
recorded on a dedicated writer thread instead of the engine loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Catch-up request: any powered quiz screen past boot keeps a question
  request in flight while the next question does not exist, so a first
  request that failed before battery verification is retried behind the
  opening, and the Oracle and level-up waits share one path. Hero
  creation (atelier and legacy) now shows the Oracle's real states:
  ready, writing, retry pending, or contacting.
- A new run drops the deck prefix answered earlier in the session and
  re-queues, at the front and flagged for review, every consumed question
  whose lesson is still outstanding. Batch ends and levels are rebased.
- Batches hold QUESTION_BATCH_SIZE questions: short deliveries top up the
  open batch (reviews count), saved batches are rebatched on insert, and
  a level-up only follows a full batch. The prefetch level accounts for
  the level-ups still ahead and never re-requests a queued level.
- CartridgeSpec gains question_batch_levels (empty = unknown), kept
  aligned through merges, retries, and drains.
- Scene-graph robustness: Oracle/quiz entry creates a fresh run when
  none is live, Oracle/quiz Back clears the run, quiz B works without a
  run, level-up requests missing questions, and its prompt and A input
  follow the manifest's can_signal timing instead of a fixed hold.
- Question requests carry a sequence number echoed by the reply; a
  superseded reply is ignored without touching the loading state, and a
  reply during play appends to an existing pending batch.
- Entering the quiz menu focuses BEGIN again.

Tests updated: failed_prefetch_waits_before_requesting_ai_again now
expects a level-1 top-up for its short 4-question batch, and
losing_the_last_heart_at_a_batch_boundary_does_not_level_up asserts the
retired deck the next run receives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	docs/game-design/oracle-quiz.md
#	package.json
#	src-tauri/src/lib.rs
#	src/main.js
# Conflicts:
#	src-tauri/src/engine.rs
#	src-tauri/src/lib.rs
Finish the seams between the parallel streams:

- Sound: the Codex is now a known audio scene with page-turn cues, confirm
  and cancel on entry and exit, the soft unavailable cue for its inactive
  A/Start, and no ambience under reading. A redeemed review gets its own
  climbing cadence (ranked below rune awakenings, above flow, matching the
  banner precedence), and the first B of the two-press leave sounds a
  distinct warning. Engine tests drive each cue through real input.
- Saves: cartridge loads carry each batch's generation level to the
  engine's prefetch logic and queue lower-level batches first, so a new
  Initiate run never opens on leftover Oracle-bound questions, while the
  lesson journal keeps generation order.
- The scaffold's temporary dead-code allows are gone: every part of the
  shared learning contract is now consumed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A live run of the payload-v2 prompt against this repository (Claude Sonnet,
levels 1 and 4) produced excellent lens-tagged questions with misconception
rationales and PREDICT transfer questions, but only 2 of 12 were accepted:
the misconception framing drew choices of 32-45 characters against the
display's hard 31-character limit, which would have left the Oracle
retrying most of the time.

The prompt now asks for choice phrases of 2 to 5 words and at most 28
characters (headroom under the display limit), calibrates with example
phrases and their exact lengths, caps questions at 100 characters including
any prefix, tells the model to put nuance in the rationale rather than the
choice, and asks it to count and rewrite before answering.

Re-running the same live evaluation: 11 of 12 accepted (the rejection was a
correctly caught location question), answers spread across all four slots,
three PREDICT questions at level 4, and level focus respected (5 of 6
purpose/responsibility at level 1, 6 of 6 invariant/tradeoff at level 4).
For comparison the pre-release generator accepted 8 of 12 surface-level
questions and put 11 of 12 answers in the first slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… off the UI thread

- Provider prompts (generation and the readiness probe) are written to the
  CLI's stdin instead of argv: `claude -p ...` with no positional prompt and
  `codex exec ... -`. There is no command-line limit any more, so the prompt
  cap becomes a byte budget (MAX_PROMPT_BYTES) that never trims a full brief.
- command_output_with_timeout takes an optional stdin payload (written from a
  detached thread, then closed), runs the child in its own process group
  (Unix) and kills the whole tree on timeout (killpg / taskkill /T /F). Pipe
  readers report on channels awaited only until the deadline, so a grandchild
  holding the pipes can no longer outlive it.
- Errors keep a short sanitized reason: the io::ErrorKind on spawn failure,
  the first stderr line or exit code on a failed call. Generation failures
  are logged instead of silently dropped.
- Windows discovery also finds the standalone Codex installer
  (%LOCALAPPDATA%\Programs\OpenAI\Codex\bin\codex.exe) and npm shims
  (<name>.cmd on PATH or in %APPDATA%\npm).
- engine_set_cartridge and cartridge_branch are async and do their git and
  save work in spawn_blocking, as pick_cartridge now does too. Loads apply in
  request order. Every git query goes through a 15 s timeout helper, the
  provenance queries are bounded by HEAD, the save is read once per load,
  and an add from disk reuses the picker's build instead of building twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Screenshots come from the real release build under Xvfb with questions
generated by Claude for this repository: a lesson card after a deliberate
miss (misconception, then answer and rationale) and the Codex page that
journals it as a pending review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Oracle quiz brief now carries the skill's pedagogy fields and a
pedagogy map, each verified against learning.rs, questions.rs, and the
engine: mastery runes at 1/3/5 evidence, spaced retry after up to
RETRY_GAP questions, level focus lenses, and PREDICT transfer at level 4+
as prompt-level requirements. The threshold contract gains lens mastery
and pending reviews, and the storyboard, mechanics, sound ledger, polish
matrix, traceability, and slices now describe the lesson card, the
save's retire-or-review semantics, reduced motion, the manifest-gated
level-up hold with its 180-tick auto-continue, stdin generation with the
anonymized brief, and full six-question batches. Slice 6 is in progress.

The game-designer skill describes sound as engine-derived from state
(audio entries stay production requirements with no sound schema),
points designers at the lesson card, shuffle, spaced retry, lens mastery,
Codex route, and reduced motion, and adds evals for both. The TOML
reference and Oracle asset README document prompt gating by after_ticks,
the concept-quiz lesson card, and the lesson-card and Codex preview tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated questions that fail only on display mechanics are now sent back
once for a precise fix instead of being discarded.

- questions.rs: question_violations reports every reason
  generated_question_is_acceptable rejects a question (question/choice/why
  length with measured characters and wrapped lines, missing why, unknown
  lens, non-ASCII characters, trivia, malformed structure), using the same
  primitives; a test pins that it is empty exactly when the policy accepts.
  Trivia and malformed questions are never repairable.
- parse_generated_batch keeps the rejected questions beside the accepted
  ones. GeneratedBatch::repair_request builds a pure repair prompt that lists
  each violated field with its measurement and limit, repeats the question
  verbatim in the generation schema plus an id, and asks for the same schema
  back. merge_repairs accepts at most one repair per sent id, only when it
  keeps its answer index and passes the full policy, never beyond the count
  and never repeating an identity.
- lib.rs ai_questions: when a reply falls short and a rejection is
  repairable, one repair call goes through ask_provider (stdin, process-tree
  timeout) with only what is left of the 120 s budget, skipped under 10 s.
  Before/after counts are logged.
- An ignored live test measures the repair against the real Claude CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nwhile

The question loader now returns Result<Vec<QuizQuestion>, String>. lib.rs
logs the full error and passes the engine a short category: TIMED OUT, CLI
UNAVAILABLE, RATE LIMITED, PROVIDER OVERLOADED, LOGIN NEEDED, BATTERIES
UNVERIFIED, NOT A GIT REPO, CALL FAILED, REJECTED BATCH, SAVE FAILED, or AI
DISABLED. The reply travels in EngineCommand::Questions under the existing
request seq, so a superseded or foreign failure is ignored. The engine keeps
the 300-tick retry, cleans the reason into one upper-case line of at most
24 characters, treats an empty batch as NO NEW QUESTIONS, and clears the
reason on success or when a cartridge is inserted.

While the player waits, both Datafall renderers grow their header band by
one row (sanctum 15->22 px, legacy 12->21 px). The band still ends above the
highest drop spawn. The new line shows:
- while a failure waits out its delay: "<REASON> - RETRY IN NS";
- otherwise, one journal lesson as retrieval practice, "REVIEW|RECALL
  [LENS:] ANSWER", with outstanding misses first and then cleared lessons,
  newest first, changing every four seconds. The lens is dropped before
  any of the answer would be cut;
- with no journal, "<REASON> - RETRYING" while the retry is in flight.

A new lesson is written in over a few ticks. Under reduced motion it
appears whole and does not move. Datafall play does not change.

Native tests cover seq-mismatched failures, showing and clearing the
reason, empty batches, sanitizing, recall order and cycling, the static
reduced-motion line, and layout: containment, disjointness from row one,
drops, and hero, contrast of at least 4.5:1, no pixels outside the line
box, and no change below the band. They also write preview frames
06c-06f.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game is a canvas, so a screen reader got nothing. The engine now has a
third output channel beside the framebuffer and the chip notes: a
plain-language transcript of what a sighted player can read and act on.
It covers the chronicle credits, each opening beat, the title, the menu
with its focus and journal, hero creation, the Datafall status and counts,
the trial question with its choices in display order and the focused
choice, the lesson card with both rationales, leave confirmation, level-up,
game over, the Codex mastery and lesson pages, and the quest screens.

The transcript is derived from the renderers' own state and helpers (the
display order, the lesson composition, the feedback banner, the journal
summary, the Oracle status order), so words and pixels cannot disagree.
Blinks, motion, and staged reveals are left out, and the lesson card reads
the same before and after its input hold, so nothing is announced twice.

EngineRuntime publishes the transcript with a sequence number only when it
changes, and engine_transcript(since) returns it only when newer. The shell
polls every 150 ms from the animation-frame loop behind its own in-flight
guard and writes a visually hidden polite live region only on a new
sequence number. The browser demo returns no transcript.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shpwrck and others added 13 commits September 25, 2026 12:25
The storyboard, mechanic and traceability rows already describe the failure
reason and the recall line; the polish matrix still listed the oracle scene's
static and motion elements without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The Oracle no longer recalls the answer of an outstanding miss whose
  question is still queued (a retry copy ahead, or the review the next
  run opens with), so the dwell before a review cannot give it away.
- A failure keeps the Oracle line until its retry has been out for a
  second, so a fast-failing retry never flashes a lesson; each recall
  starts at the top of the recall order and is written in fresh.
- Batch progress counts an open batch as its reviews plus a full batch
  of new questions, so a miss grows N at once and the top-up never
  jumps it.
- Deliveries top batches up by new questions while reviews ride along,
  matching rebatch and batch_is_full.
- Deliveries skip stems the journal holds as answered correctly and
  mark stems it holds as missed as reviews, so a parked delivery cannot
  replay a retired question as a fresh first try or redeem it twice.
- The level-up NEXT: footer names the lenses of the batch generated
  next, not the run's level.
- The legacy lesson footer sits on an INK strip so a MAGENTA rune III
  banner clears 4.5:1.
- Quest output and completion carry a quest generation, and readers stop
  forwarding once the quest is done, so a helper that outlives its
  shell (Windows) cannot write into a later battle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ool launch

A mutation sweep over save.rs, provenance.rs, lib.rs (repository check,
failure reasons, timed commands, cartridge building) and the non-Windows
paths of external_tools.rs left 23 of 51 compiling mutants alive; 20 change
behavior and each is now killed by a new test:

- save: a save from a newer schema is refused and left untouched.
- provenance: the first of several notices is credited; every prose word
  and template placeholder is rejected on its own.
- cartridges: placeholders for an unborn or detached HEAD, the oldest of
  several root commits, COPYRIGHT outranking LICENSE, package/Cargo/Make
  quests, CODEQUEST.md-only custom mode, palette spread, tracked paths.
- failure reasons: every rate-limit and login phrase, and signal deaths.
- repository check: git is asked for untranslated (LC_ALL=C) messages.
- external tools: each launcher honors only its own non-empty override.

Two pure helpers were extracted so the tests can reach them without
changing behavior: git_repo_check_command and cartridge_color.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jsx_comment_opening tested for `/*` after any `{` or backtick it found, so
an inline code span such as `/* block` opened a JSX comment and hid the
prose after it, up to the next `*/`, across lines. Only `{` (optionally
followed by spaces) now opens one; backticks just skip their code span.
The MDX case in restructured_text_and_asciidoc_comments_stay_hidden gains
a code span beginning with /* that must stay shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mutation sweep over engine.rs's quiz flow (56 mutants) left 14 alive.
Eleven were real regressions the suite could not see; each now has a
focused test at the end of engine.rs's mod tests:

- a miss on a batch-opening question keeps the full retry gap
- a missed review is not a redemption
- a short batch merged forward keeps the higher level
- continuing from a level-up keeps the same run
- a broken ward routed back to the Oracle starts a fresh run
- retiring a run keeps each remaining batch at its own level
- rebatching reads a boundary question from the batch it opens
- a reinserted cartridge drops replies to earlier requests
- a run that is not live does not set the next batch level
- a delivery held through the quiz lands on the level-up
- inserting a cartridge drops a held delivery and requests its own

The other three are equivalent (or unspecified) in reachable states.
No production code changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolution notes:
- The lesson card's retry note reads schedule_retry's RetrySlot: At(i)
  still says BACK IN N from i, and a deferred copy says LATER
  (RetryNote::Later). The screen transcript now reads the note as drawn.
- R1's cartridge-level question_attempts stay the attempt store; a
  same-launch relearning keeps its saved count (it still returns as a
  spaced check), and only a retiring success drops it.
- A regenerated missed stem arrives as a Review::InSession review; a
  delivery also skips identities waiting as deferred review copies.
- lens_stage is the shared gated rule (mastery, journal, concept); the
  lens-wake check reads it after both evidence and journal land, and the
  blinking footer rune leaves cracked runes still. The run ledger grades
  first tries the way evidence does.
- The J3 ledger test, R1/J2 tests, and the property tests now use Review,
  volume_stage, stage_with, and the relearned list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mutation sweep of 50 small changes to learning.rs and questions.rs left
13 alive. Each new test pins the behavior one of them broke:

- lens parsing tolerates surrounding whitespace
- a question on both progress lists stays a review (missed wins)
- years at both ends of 1990..=2039 are trivia
- a three-segment slashed word is a path
- legacy saves with an unknown lens are rejected on load
- a repair that moves the answer index is dropped
- a full weakest-lens tie goes to the earlier lens
- the focus minimum rounds two thirds up
- level-0 batches are neither saved nor loaded
- typographic dashes and quotes fold to ASCII
- each undrawable character is reported once
- a question filling exactly the question rows has no diagnostics

No production code changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolution notes:
- LensRecord keeps J1's always-saved relearned counter; a success after a
  Codex peek joins same-launch retries as relearning (not evidence, not
  graded in the recent window).
- SavedQuizProgress moves a peeked success to relearned_questions, so it
  returns once more as a spaced check, keeps its attempt count, and still
  records J4's missed_picks and clears peeked_questions.
- commit_answer grades with J1's demoted Review plus J4's peeked flag and
  pick; the loader passes picks and peeks to lessons for spaced and retired
  questions alike.
- The peek test now uses a Spaced review, where the peek is what demotes a
  redemption; property tests randomize peeked in their models.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/architecture.md walks through the runtime, the learning model, and
the scene machine; docs/reference/save-format.md documents every cartridge
save key and its compatibility rules. Both now describe the merged Codex
self-test (sealed pending pages, MarkPeeked and ProgressEvent through the
save writer, missed_picks and peeked_questions, and a peeked success as
relearning that returns as a spaced check). README links both from its
Architecture section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolution notes:
- F1A's retry_sentence replaces the integrator's shorter retry-note line;
  it gains the deferred RetryNote::Later ("returns later").
- The single-question missed card now defers its retry, so it says
  "returns later"; the ledger transcript test follows J1's gated stages
  (an open Invariants miss cracks rune III, so Roles wins the tie).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolution notes:
- Deliveries keep F1's journal rules on top of J1: a stem the journal holds
  as missed becomes a Review::InSession review (never a Spaced one), cleared
  lessons are skipped, deferred review copies count as queued, and the
  top-up counts new questions with Review::is_review.
- F1's batch_progress and tests use the Review enum and J4's four-argument
  record_lesson.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shpwrck and others added 14 commits September 25, 2026 12:56
Resolution notes:
- The retry rule stays J1's (a miss returns after exactly three other
  questions, carrying into the next batch); F4's Oracle-line and
  transcript additions are kept beside it.
- The docs contract now checks for "after (exactly) three other questions"
  and rejects the stale "up to three intervening questions".
- The preview-writer lists name the three new writers from J1 and F1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolution notes:
- The injectable ask/repaired_questions structure stays; T1's
  with_timeout_budget now names the budget on both the first call's and the
  repair call's errors.
- ai_questions passes configured_question_timeout(provider) as the budget
  instead of the removed fixed AI_QUESTION_TIMEOUT, and the save-guard test
  uses Claude's configured budget.
- The design brief and architecture guide describe the provider-aware
  deadline instead of 120 seconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The M2 sweep was written against the bool review flag, the three-argument
schedule_retry, and record_lesson without a pick. The batch-opening miss
test now expects RetrySlot::At with no deferred copy, review checks name
Review::InSession or is_review, and the retirement test records the miss's
pick.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Gate legacy lens records on their unseen history: evidence an earlier
  build recorded fills the accuracy window's older slots, so an upgraded
  veteran's first miss no longer collapses rune III to rune I.
- Remember the identities answered in this launch per save, so ejecting and
  reinserting a cartridge reloads same-launch misses and relearnings as
  InSession reviews, never as spaced checks that count as evidence.
- Seal a relearned lesson whose spaced check is due (CHECK PENDING) and let
  a reveal there be recorded as a peek, so the check is never open-book.
- Keep a retired question retired when a delivered repeat of its stem is
  relearned, so one question cannot earn redemption evidence twice.
- Save the wrong pick's choice text and resolve it by text on reload, so a
  reordered repeat of a stem shows the choice the player actually picked.
- Show CRACKED = LOW ACCURACY (and a matching transcript line) when no
  cracked lens has a pending review, instead of promising a review.
- Align the manifest and reference docs with sealed pages, the spaced-check
  seal, the two crack legends, and the mastery-page summary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolution notes:
- provenance.rs: both sides appended tests at the end of the module; all
  five are kept (the integration branch's length-cut and saturation tests,
  then M3's notice, prose, and placeholder tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After F3, a later batch that yields to earlier ticks cancels the slide note
before it starts (its stop precedes its start), so it was never sounding and
MUTE has nothing to silence there. The mute check now keeps only notes whose
stop follows their start and requires at least two of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolution notes:
- engine.rs imports both AtomicBool/Ordering (integration branch) and
  OnceLock (P1's plate cache).
- bench.rs builds its fixtures with Review::Fresh and J4's lesson fields;
  both ignored benchmarks still run through the merged engine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A question listed as both answered and missed now loads as a
Review::Spaced review, and the lens-tie fixture fills the new LensRecord
fields from their defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The P1 benchmark builds its journal with the new Lesson::spaced_check. The
architecture guide and the save-format reference now describe what the
review-3 fixes ship: missed_pick_choices beside missed_picks, peeks on
relearned lessons awaiting their spaced check (CHECK PENDING), same-launch
reloads as InSession reviews through persist_launch_progress, retired
questions staying retired on relearning, gate_accuracy for legacy records,
and the CRACKED = LOW ACCURACY legend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The macOS CI runner took longer than 500 ms (half of QUEST_OUTPUT_GRACE)
to kill, reap, and report an aborted quest, so the timing assertion failed
before the test reached its real correctness check. The quest's child
sleeps 3 s, so any bound well inside that still proves the abort did not
wait it out; allow 2 s, report the measured time on failure, and leave the
marker check as the proof that the child itself was stopped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shpwrck
shpwrck marked this pull request as ready for review September 25, 2026 19:30
@shpwrck
shpwrck merged commit 01a555d into main Sep 25, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant