Peregrine · Plan for review
The Thread as Container and the Prime Suspect Inference Pass: Follow-Through
This plan finishes the work from the 20 September design night: The Thread as a container, the verb-registry amendment, and the four ecosystems, together with the rest of the Prime Suspect inference pass. It continues prime-suspect-inference-pass.md.
1Where things stand
Three of the recovery entry's next steps were already done. Two others turned out larger than the entry described. This table sets the baseline so no phase redoes finished work.
| Item from the entry | State | Evidence |
|---|---|---|
| Re-render the container spec review page | Done | 8bb4e79; the page carries §9 to §11 |
| Status for The Rookie, Undercover Gauntlet, The Shredder | Done | Shelved 2026-09-21, review cards 27 to 29 |
| Decouple the charge gate; flat quarter-clock chain bonus | Shipped | e0e98f0, ea3ecdb, 7c619b1 |
| Hide category until pulled; ask for the chain at the charge | Shipped | c805cd0 |
| Cases 003 and 006 turn on a crossing | Shipped | Blind solvers found both crossings |
Retire reading-game | Decided | The Dossier is the reading game. The slot is still in guide.json and has to be removed in-app |
| Verb-registry §7.6 amendment | Not done | Lives only in the-thread.md §2; registry line 62 still reserves locate for The Frisk |
| The Frisk in or out | Open | unlisted; Sarah on 2026-09-21: "Unclear" |
| Names in the answer choices of E01 to E03 | Larger | 23 clues across all 8 cases have a correct choice naming suspects (27 counting wrong choices) |
| No-scoring unit test for the container | Not yet possible | No container type exists; scoring writes are inline across the app |
Two stale records contradict the settled calls:
ecosystems.json › consequencesstill reads "Client Cases ship first inside the phone container". That is the deprecated §11 draft wording, which treats a content class as if it were a surface.- The
the-threadrow inguide.jsonstill carries the 5 September "subpar and conflicting" flag and lists locate, place and connect gestures.
2Dependencies
Case content changes go to the corpus (guides/peregrine/entries/ps-case*.json) and are re-exported with PressRaven/tools/export-machine.py --write. The exported case files are exporter-owned and are never hand-edited.
3Recommendations summary
Each recommendation is repeated with its reasoning inside the phase it changes. The phase sections below mark them in red callouts.
notice now and leave The Frisk's roster status open.guide.json edit into one in-app Press Raven session.P0Phase 0 · Reconcile the records
Success: every record matches the settled calls, and nothing in the corpus still says the container hosts an ecosystem.
- Mark the completed items in
prime-suspect-inference-pass.mdas done and point its step 5 to this plan - Rewrite the Client Cases line in
ecosystems.json › consequencesto state the orthogonality rule: the phone container's first tenant, a presentation, with Prime Suspect as the other presentation of Client material - Record in
the-thread.md §9that the Rookie, Gauntlet and Shredder shelving and the Dossier-as-reading-game call were made on 2026-09-21 - Re-render the container spec page only if
the-thread.mdchanged in this phase - Prepare the replacement text for the
the-threadrow (container, owns no verb, may not score) for Sarah to paste in-app R8
Two edits touch guide.json: removing reading-game and rewriting the the-thread row. Press Raven rewrites the file from memory, so a hand edit made while it is running gets restored silently. Do both edits in one in-app session, then confirm with git diff in the corpus that both survived.
P1Phase 1 · Decisions that block later phases (Sarah)
Claude does not make any of these calls. Where a recommendation exists, its reason is stated alongside it.
| Decision | Blocks | Recommendation |
|---|---|---|
| The Frisk: in or out | Nothing, if R1 is adopted | Keep it open until the V2 mockup pass. The registry fix lands either way (R1) |
| First thread's cast | Phase 5 | None. This is a story call |
| Voicemail transcript: authored or derived | Phase 5 schema | Leaning authored for the first case. Deriving it needs a punctuation-stripping pipeline that nothing else uses, and one case does not justify building one |
| Red String: Client Cases or Season Plot | Client contract costing | None. It depends on whether the bank is replayable, and that has not been measured |
| Case 101 (Halloran's Books): which ecosystem | Phase 4c | A Client case hosting one season trace (R7). The 13 September architecture has the arc ride in on "a fifth [clue] that's a memo that shouldn't exist". A Prime Suspect case with Halloran in one clue follows that pattern. The case follows the Client pair rule, and the one Halloran clue also follows the trace locks |
| Answer-choice leak: scope | Phase 4a | The 23 correct choices (R2). A wrong choice that names suspects reveals nothing the finding won't name once the clue is read |
| Has a teen feel pass on 003/006 happened? | Phase 4b | The inference plan records blind solvers but no teen pass. The answer decides whether 4b can start |
- Make each call in the table above
- Remove the
reading-gameslot from inside Press Raven, together with the R8 batch
P2Phase 2 · Amend the verb registry
Depends on: nothing, if R1 is adopted. Success: verb-registry.md states the station/container rule itself, and no verb is claimed twice.
The recovery entry treats the notice/locate split as conditional on The Frisk shipping. The split describes two different acts: salience with no criterion, and search against a stated claim. It holds whether or not either game is ever built. Recording notice as a reserved verb costs one table row and ends the three-way deadlock now. If The Frisk is cut later, the row is struck like any other reservation.
- Add the station/container amendment to
verb-registry.md §2, including the rules that a container may not score and that a scoring container counts as a station - Rewrite the Thread row (line 73) to say it claims no verb and accepts navigation only. Drop the gesture list, which reads as three verb claims
- Add a
noticerow reserved for The Frisk, strikelocate (The Frisk)on line 62, and clear The Lobby row's flag R1 - If The Frisk is later cut, strike the
noticerow and move The Frisk toshelvedorcutinroster-status.json - Update
the-thread.md §2.3with the outcome and re-render the review page - Write
tools/check-verb-registry.py: parse the verb tables, fail on duplicate claims within a section, and ignore struck rows R5 - Confirm the script passes on the amended registry and fails on a copy with a planted duplicate R5
§7.6 is the registry's binding rule, and today the only check is someone reading the tables. The tables are regular enough to parse. A duplicate-claim check would have caught the three-way locate claim when it was introduced.
P3Phase 3 · The container may not score, enforced
Depends on: the first shell code. This phase lands in the same commit as the first container type. A test written before a container exists passes without checking anything.
NightTake is inserted in NightDebrief.swift:240 and :291 and in CaseSession.swift:187. DrillResult is constructed inside at least four game views (ForensicsView, TheMakeView, ShakedownGameView, KingpinShowdownView). PersonalBests computes from stored drills and writes nothing, so the first draft's plan to spy on it was wrong.A behavioral test with spy sinks needs a scoring seam the app does not have. Building one only to test this rule is a larger refactor than the rule warrants. A scan of the shell's source for the symbols that write scores fails on exactly the "reasonable-looking later change" that the-thread.md §2.2 warns about. Pairing it with a pure navigator type in Core keeps the shell's logic testable without a view.
- Put the shell's navigation logic in a pure Core type whose only output is a closed route type, such as a
StationRouteenum. The SwiftUI shell renders routes and opens stations, and it holds no game state - Write a Core test that walks every navigation action and asserts each result is a
StationRoute. The route type cannot carry points, so the compiler enforces the rest - Write a source-level test that scans the shell's app directory and the navigator file, with comments stripped, and fails on
NightTake(,DrillResult(,context.insertorPrimeSuspect.ScoreR6 - Reference both tests from
verb-registry.mdnext to the amendment - Do not split the shell into its own SwiftPM target yet. The scoring types are public, so a separate target would not block access by itself
P4Phase 4 · Prime Suspect content
4a · Stop answer choices from naming survivors
The pattern is the same in every flagged clue. The question asks which suspects fit, so the correct choice is the survivor list, and the clue does the elimination for the player. For example, case 004 E01 asks "which suspects' vehicle-speed estimates fit the collar data?" and the correct answer is "Jon, Ryan, and Amir".
| Case | Clues with a correct choice naming suspects | Count |
|---|---|---|
| 001 Calder Cipher | E02, E03, E04 | 3 |
| 002 Ivory Gate | E02, E03, E04 | 3 |
| 003 Glass That Never Broke | E01 | 1 |
| 004 Vanishing of Scout | E01, E02, E03, E07 | 4 |
| 005 Last Toast | E02, E03 | 2 |
| 006 Dead Circuit | E01, E02, E03, E10 | 4 |
| 007 11:47 Message | E01, E02, E03 | 3 |
| 008 Four Seconds | E01, E02, E03 | 3 |
The proposed rewrite asks for the quantity (a speed, a time, a count) and moves the names into the finding. That can change which SAT skill the item tests. "Which rows satisfy a condition" is data reading, while "what is the average speed" is computation. Pilot it on two clues, one math and one reading-flavored, and check each against its knowledge_grain. A second pattern may fit the data-reading items better: the choices name rows or exhibits, and never people.
Fix only the 23 correct choices. Once the validator reports zero, promote the warning to an error so cases 101 and 102 can't reintroduce the leak.
- Add a
CaseValidatorwarning for a correct choice that names a suspect, using the existing word-boundary matcher - Pilot the rewrite on case 004 E01 and one reading-flavored clue, check both against
knowledge_grain, and record which pattern fits which item type R3 - Rewrite the remaining flagged clues in the corpus with the chosen pattern, without touching
clears. Case 001's print-margin clue has a pinned lint test - Re-export, then run
PrimeSuspectCaseLintTestsandPrimeSuspectContentTests - Promote the warning to an error once it reports zero R2
4b · Rewire the remaining cases to the pair rule
Depends on: V1. The inference plan puts this rewire after the teen feel pass, and the recovery entry dropped that gate. This plan keeps the gate, because five rewires are the expensive half and the feel pass is what justifies them.
testProvingItBeatsTheCoinFlipFromEveryOrdering checks one parametric shape: a 120-minute clock, 20-minute pulls, and a proof costing one or two extra pulls. The rewired cases differ in clock, clue count and pair position. The claim that careful play beats a coin flip is therefore verified for the shape and not for each case. A test that iterates every exported case, computes its minimum covering set with and without the pair, and applies the same inequality would turn each rewire into an automatic check.
- Write the per-case pricing test before the first rewire, and confirm it passes for 003 and 006 R4
- 005 Last Toast: promote E09+E10 so Gavin is pair-only; edit E03; set
clues_to_chargeso the gate stays at 3 - 008 Four Seconds: same shape. Oscar becomes pair-only through E09+E10, with E03 edited
- 004 Vanishing of Scout: new E01+E04 pair for Jon; edit E02 and E04. Fold in E01's 4a fix
- 007 11:47 Message: decide whether Vivian's triple can drop to a pair before editing
- 002 Ivory Gate: last and most expensive; both halves are independently conclusive. Fold in its 4a fix
- For each case, edit the prose that asserts the moved elimination (arithmetic, logs, finding, closing), then re-export, lint, and run the pricing test
- For each case, run a blind solve and confirm the crossing is found unaided
4c · Cases 9 and 10
- Finish
ps-case101-hallorans-books(stilldraft) to the pair rule, under the ecosystem Phase 1 assigns R7 - Author case 102 to the pair rule, with choices that pass the 4a check. Set
clues_to_chargeonly if the default of 3 is wrong for its graph - Resolve the con-alias collisions (Idris, Bell, Voss, Graves, Finch, and the reused Luca) before export
- Export, lint, run the pricing test, blind-solve, and add both cases to the sequential-unlock list
P5Phase 5 · One Client case for a phone
Depends on: the cast, Voicemail and Red String calls. Success: one complete phone case exists as data, with its cost recorded, before any shell prototype is built.
A phone case has a different shape from a board case. It is told in the first person from one person's messages and logs, and it asks what that person was doing. It cannot be a port of ps-case002 (the-thread.md §11.2).
"Most expensive by unit" is a measured claim only if the unit is fixed before the case is written. Setting the target first also gives the shell its first answer to the 13 September rubric question on unit size and word ceiling.
- Set the size target (item count, word ceiling per item, number of distinct stations) in
the-thread.md §6R9 - Draft the content model in
the-thread.md §6: message, voice memo with transcript, photo, and log, with the station each routes to - Author the case in the corpus under
guides/peregrine/design/until an exporter target exists - Record the cost (hours, words, items) against the size target and the thirty-case Client contract in
ecosystems.json - Hold the shell prototype until V2 is done
P6Process
Nothing from 20 September reached Peregrine's Chronicle, because the design session ran from the PressRaven checkout. When a Peregrine design session starts from PressRaven, run /cr-entry from the Peregrine checkout at the end.
- Add one line to Peregrine's
CLAUDE.mdsaying Peregrine design sessions file their entry from the Peregrine checkout R10
VVerification (Sarah)
- V1. Teen feel pass on cases 003 and 006: does crossing two findings feel like deduction or like a longer subtraction, and does the ending still read as a tally? This gates 4b
- V2. Teen feel pass on the existing Thread and Frisk mockups before any content is commissioned. This gates the Phase 5 prototype and informs the Frisk call
- Review the registry amendment and the
noticerow - Open the re-rendered container spec page and confirm §9 records the 2026-09-21 roster calls
- After the 4a pilot, play both pilot clues on device and confirm the choices no longer reveal who survives
- After 4b, play one rewired case start to finish and confirm the Case Closed line names the pair
- After the R8 batch, run
git diffin the corpus and confirm bothguide.jsonedits survived
CConfidence and how to raise it
Why 74
The score is a weighted view of the plan's parts. Each row's bar shows the score at writing. A green bar shows where the checks changed it.
| Part | Weight | Score | Reason |
|---|---|---|---|
| Baseline | 20% | 92 | Every claim was checked against git, roster-status.json, guide.json and the registry. The margin covers records outside those sources |
| Sequencing | 20% | 85 | Follows the inference plan's own gate. Unknown: whether a teen pass already happened |
| Phase 2 · Registry | 10% | 85 | Mechanical once R1 is accepted |
| Phase 3 · Guard | 15% | 60 → 75 | The sink list was assumed, and the check found it partly wrong, which led to R6. Remaining gap: no shell code exists to test the design against |
| Phase 4a · Leak | 15% | 55 | The rewrite pattern is my proposal. It hasn't been tried on a single clue, and it may change what an item tests |
| Phase 4b/4c · Rewires | 10% | 75 | The method is proven on 003 and 006. Case 007's triple and case 002's double-conclusive halves are unresolved |
| Phase 5 · Phone case | 10% | 55 | No content model yet; size and cost unknown |
What would raise it
| Action | Who | Moves | Effect |
|---|---|---|---|
Pilot the 4a rewrite on two clues and check knowledge_grain | Claude | 4a: 55 → 75 | +3 |
| Say whether a teen feel pass on 003/006 has happened | Sarah | Sequencing: 85 → 92 | +1.5 |
| Draft the phone-case content model and size target (R9) | Claude, after the cast call | Phase 5: 55 → 70 | +1.5 |
| Write the R4 per-case pricing test | Claude | 4b/4c: 75 → 85 | +1 |
| Answer the Phase 1 calls | Sarah | Removes branches in Phases 2 and 5 | +1 |
| Settle 007's triple and 002's pair shape on paper | Claude | 4b/4c | +0.5 |