Press Raven · Framing Proposal · Rev 2
A game is a document type. Its game types are the sub-grouping. Its content items are the entries. What doesn't fit inside one game is the mechanics catalog — and that's the only thing that lives outside.
Rev 2 supersedes Rev 1. The first pass proposed three categories — playbook, game-design, bank. That was wrong in the middle: the "how we adapted it" tier isn't entries at all, it's authored structure in guide.json, the way days and chapters already are. Rev 2 collapses to two things and derives the shape from the three shipped categories instead of inventing alongside them.
A game is one category, parallel to manual and itinerary. One guide per game.
The only thing that can't live inside a game guide is the mechanics catalog — because it's cross-game, and its whole value is that Drive Corps and Peregrine draw from the same shelf.
Grouped by game type, authored in guide.json the way days and chapters are. Entries are content items whose fields depend on which game type they feed. Drive Corps is a guide. Peregrine is a guide.
Reader: the game's loader — and you, doing QA
One entry per core mechanic: its verb, its loop, the kind of knowing it exercises, the data shape it demands. Brainstormed, not given — which is exactly why it's a catalog and not a fixed enum.
Reader: you, starting game #3
| Level | Field Manual | Itinerary | Game |
|---|---|---|---|
| A · Document | the manual | the trip | the game |
| B · Sub-group | Chapter — authored in guide.json chapters[]; broadly the same chapter to chapter |
Day, then kind roster — days authored in guide.json; kind vocabulary guide-extensible via kind_extensions |
Game Type — authored in guide.json games[]; carries its mechanic, subject, and priority ranking |
| C · Entry | Block — class is rule / hack / checklist / callout / passage |
Activity — class is place / plan_item / note; fields follow class |
Content item — fields follow game type, supplied per-guide by schema-extensions.json |
The complexity ladder holds and gets steeper at each rung: manual groups by authored chapters with essentially flat entries; itinerary adds derived kind rosters, three classes, and cross-references; game adds a sub-group vocabulary that is invented per guide and that changes the entry's schema. Game is strictly the most complex of the three, which is the right expectation to carry in.
Not in entries. In the game-type object, exactly the way a DayConfig carries label / title / date / headline and a ChapterConfig carries title / numeral:
"games": [
{ "id": "lineup",
"label": "The Lineup",
"order": 4,
"mechanic": "judging-a-narrated-chain", // → catalog entry id
"subject": "Reading & Writing — con detection",
"reskin": "A suspect narrates his reasoning; commit clean or con
as early as you dare, then pick him from the mugshots.",
"priorities": { "speed":"secondary", "accuracy":"primary", "stress":"secondary" },
"divergence": "Adds the lineup after the call — naming the error,
not just flagging it. Feed this back to the catalog.",
"bank_target": 40,
"build_status": "built" }
]
divergence is the field that earns its place: it records how the built thing departed from the catalog entry and why, so discoveries flow back up instead of stranding in one game.
There are two differences, and the bigger one is not the obvious one.
Game types are brainstormed rather than given, where eat / do / shop / photo are just… known. But the corpus already anticipated this: itinerary's kind_extensions lets a guide extend the kind vocabulary in guide.json, precisely because Asheville needed shelves NYC didn't. "The sub-group vocabulary varies by product" is a solved problem with shipped precedent.
In itinerary, the sub-group does not change the entry's fields. A restaurant and a museum are both class: place — same address, hours, price, badges. kind is a shelf label and nothing more.
In a game, the sub-group is the schema discriminator. A Recon Sweep item (target name, image, decoy group) and a Lineup item (passage, reasoning[], break_idx, con id) share a title and nothing else.
So the C-row parallel is more true of games than the itinerary comparison suggests. There, parameters depend on class, not on section. Here they depend on the sub-group itself — and there are eleven of them in Peregrine, not four.
Verified in source rather than assumed: a schema-extensions.json with extend omitted appends fields at the top level (SchemaModels.swift:129), and extension fields are ordinary FieldDefs, so they carry applies_when (SchemaModels.swift:57). That's the same mechanism Things With a Cost already uses to give only situation entries an incantation.
categories/game/schemas/entry.json
{"fields": [
{"key":"title","required":true,"type":"string",
"prompt":"How this reads in the navigator — not what the player sees."},
{"key":"family","label":"Game Type","required":true,"type":"string",
"prompt":"Which game type this feeds — an id from the games array in guide.json."},
{"key":"class","label":"Item Class","type":"enum",
"options":["item","flavor","asset"],
"prompt":"item = the thing the player answers · flavor = in-fiction copy
(transmissions, murmurs, taunts) · asset = the record about a picture or scene."},
{"key":"number","type":"int"},
{"key":"concept","type":"string",
"prompt":"The skill this scores against. Miss logs and spaced repetition key on
this, not on the id — three sign names share two pictograms apiece."},
{"key":"difficulty","type":"enum","options":["intro","standard","hard","boss"]},
{"key":"stem","label":"The Prompt","type":"prose",
"prompt":"What the player is shown or asked. Empty for purely visual items."},
{"key":"choices","type":"string_list"},
{"key":"answer","type":"string"},
{"key":"trap","label":"The Trap","type":"prose",
"prompt":"The wrong answer a smart player reaches for, and why it's tempting."}
]}
A note on family. The key is named family and labelled "Game Type" on purpose: the app's generic template groups by family when the schema declares one (DocumentTemplates.swift:300), so the document chapters itself by game type with no Swift written. When you later add a real game template case, renaming to game_type is a mechanical sweep across entry files — or you leave it, since the label is what anyone actually sees.
guides/peregrine/schema-extensions.json
[
{"fields":[
{"applies_when":{"family":"lineup"},"key":"passage","type":"prose"},
{"applies_when":{"family":"lineup"},"key":"reasoning","type":"object_list",
"prompt":"The suspect's narration, one line per object.",
"fields":[{"key":"t","label":"Line","type":"string"}]},
{"applies_when":{"family":"lineup"},"key":"break_idx","label":"Break Index","type":"int",
"prompt":"Index into reasoning where the con begins. Script-validated —
the app cannot check a pointer into a sibling array."},
{"applies_when":{"family":"lineup"},"key":"con_id","type":"string"}
]},
{"fields":[
{"applies_when":{"family":"target-practice"},"key":"claim","type":"prose"},
{"applies_when":{"family":"target-practice"},"key":"verdict",
"type":"enum","options":["clean","con"]}
]},
{"fields":[
{"applies_when":{"family":"recon-sweep"},"key":"image","type":"string",
"prompt":"Path under assets/. The entry is the record about the picture,
never the picture."},
{"applies_when":{"family":"recon-sweep"},"key":"decoy_group","type":"string",
"prompt":"Which near-twins this is confusable with. Drives the difficulty
ramp — currently pure human judgment with no tooling anywhere."},
{"applies_when":{"family":"recon-sweep"},"key":"shape","type":"enum",
"options":["diamond","octagon","rectangle","pennant","circle","triangle"]}
]}
]
One block per game type, each self-contained. Adding a twelfth game type is appending a block — not touching the category, not touching the other eleven.
Because root-level extensions work, the catalog can be a catalog-category guide with its beats added by extension. family, number, metadata, similar_to are already in that schema and already group correctly:
// guides/game-mechanics/schema-extensions.json (no "extend" key = append at root)
{"fields":[
{"key":"beats","type":"object","fields":[
{"key":"player_verb","label":"The Verb",
"prompt":"What the hands do. Tap, flick, strike, order, hold, draw.","type":"string"},
{"key":"loop","label":"The Loop",
"prompt":"One turn, from start to consequence.","type":"prose"},
{"key":"knowledge_fit","label":"What It Teaches Well",
"prompt":"The kind of knowing this exercises — visual recognition, name→image
recall, free retrieval, discrimination between near-twins, procedure,
judging someone else's reasoning. And what it teaches badly.","type":"prose"},
{"key":"consequence","label":"The Consequence",
"prompt":"How feedback lands inside the fiction. Never a red X.","type":"prose"},
{"key":"repetition","label":"Why You Play Again",
"prompt":"What makes the next round wanted, not assigned.","type":"prose"},
{"key":"failure_mode","label":"When It Becomes a Worksheet",
"prompt":"The specific way this mechanic collapses into drill.
Name it so you can watch for it.","type":"prose"}
]},
{"key":"priorities","type":"object","fields":[
{"key":"speed","type":"enum","options":["primary","secondary","ignored","penalized"]},
{"key":"accuracy","type":"enum","options":["primary","secondary","ignored","penalized"]},
{"key":"stress","type":"enum","options":["primary","secondary","ignored","penalized"]}
]},
{"key":"content_contract","type":"object","fields":[
{"key":"unit","label":"One Unit Is","type":"string"},
{"key":"requires","label":"Required Fields",
"prompt":"One per line. This becomes a game's schema-extensions block.","type":"string_list"},
{"key":"assets","label":"Asset Needs",
"prompt":"Art, audio, scenes — state it now. Sniper Spot stalled here.","type":"prose"},
{"key":"volume","label":"Viable Bank Size","type":"string"}
]},
{"key":"mockup","label":"Behavioral Reference",
"prompt":"Path to the HTML mockup that IS the spec. Prose cannot carry feel —
link it, never claim to replace it.","type":"string"},
{"key":"instances","label":"Built As",
"prompt":"game:game-type — where this exists today.","type":"string_list"}
]}
The honest tradeoff: family in the catalog schema is a plain string, so the five motivators — Finding, Dodging, Optimizing, Building, Surviving — are free text rather than a picker, and a typo silently creates a phantom section. Extensions append, they don't override, so you can't tighten it to an enum without a new playbook category. Start with the string; promote to a category only if the typos actually bite.
content_contract is the load-bearing field either way. It's what makes the catalog generative rather than a documentation chore: a mechanic declares the fields a game must supply to feed it, which is precisely the schema-extensions block you write when you adapt it.
One mechanic, three touchpoints, flowing one direction:
| Where | What it holds |
|---|---|
| Catalog entry | The mechanic itself, and content_contract.requires — the fields any game needs to supply. |
guide.json games[] | The adaptation: mechanic: "signal-detection", the reskin, the priority ranking for this subject, the divergence. |
schema-extensions.json | The contract's requires list, realised as real fields gated on applies_when: {"family": "<game type>"}. |
Nothing flows back automatically — divergence is a note to yourself, not a sync. That's deliberate; Press Raven exports one direction and shouldn't learn to do otherwise.
This is the part that isn't a matter of taste. Four things found in the repos:
docs/spec/verb-registry.md is referenced as companion to the ten Knowledge Rules and was never written. The catalog is the verb registry, promoted out of one game.
Peregrine/docs/spec/knowledge-rules.md
games[] divergence recorded as a parenthetical in a view file.
Peregrine/Views/KingpinShowdownView.swift
content_contract.assets line would have said so before the mechanic was greenlit.
Drive Corps/README.md · drive-corps-handoff-v1.md §5.3
Across both games there are roughly fifteen built game types and four specced-but-unbuilt. My guess is they collapse to eight or nine distinct mechanics. Finding that out is the catalog's first deliverable, and it's worth the exercise even if nothing else here gets built.
A category's brief.json can declare a slot with "writes": "editorial_notes", and editorial_notes is emitted into the CONTEXT half of every session packet. It's how Things With a Cost carries "the cost test" into every drafting session.
Make "Don't gamify the question. Gamify the reason you care about the question." the admission test on the game category's brief, and give the catalog guide its own: "the player must be solving a problem in the world, not answering a question about it."
Then every packet you export — to draft a mechanic, to generate forty Lineup rounds — arrives with that sentence stated as an admission bar, at the moment of authoring. Which is the only place it erodes.
The game brief should also carry the game's forbidden vocabulary. Drive Corps bans quiz, test, practice, and study in UI copy; right now that rule lives in a handoff doc, which means it protects nothing at 2am. In the brief it rides along in every packet.
Press Raven owns authored content. The app owns runtime content. Draw it once and it stops being ambiguous later.
vigilance-bank.json — 35 lineup rounds, 28 wager questionsstatic let in the viewconMoves · the murmurssigns.json (54) and dmv_questions.json (100)class: flavorQuestion rows — photographed by the player, classified at runtime by the vision callPlayerState miss logs and concept weightsVigilanceResult rows and the Trap AtlasUser data. Press Raven exports one direction by design; a round trip is not on offer and shouldn't be.
Assets follow the acrylics precedent: images live in guides/<game>/assets/ and the entry is the record about the asset — name, concept, decoy group, shape — never the asset itself. Drive Corps's signs.json is already exactly this shape, which is the cheapest possible proof that the model fits.
machine export target is declared, not built. framework.json lists it as "status": "declared"; ExportService emits paginated print HTML and MobileGuideExporter emits the mobile page — that is all that ships. Getting entries back into Data/signs.json is a script you write. Consistent with the README's own position that the press is already Claude Code pointed at the entries folder, but it is not a feature you have, and it remains the single largest gap in this proposal.games[] array won't be read yet — but it will survive. days and chapters are typed fields on GuideConfig with hardcoded template cases; a games array is not modelled. The good news, verified: updateGuideConfig round-trips guide.json through JSONValue specifically so keys the app doesn't model are preserved (CorpusStore.swift:591). You can author it today and the app won't eat it — it just won't render from it.games: [GameTypeConfig]? on GuideConfig, plus a case "game" in DocumentTemplates mirroring what manual already does. Same shape of work, already precedented — not speculative. Until then the generic family grouping gives plain string headings and no per-type metadata on the page.break_idx points into its own reasoning array. object_list holds the shape, but nothing checks the pointer. That stays a script's job, and the field prompt should say so out loud.catalog guideNo new category, no Swift, no schema work beyond one extensions file. File all ~19 mechanics from both repos as seeds — title plus seed note — then run the packet loop to draft the beats. You learn how many are actually distinct, Peregrine's missing verb registry gets written as a side effect, and if this step feels like a chore rather than a relief, you've discovered that cheaply.
categories/game/ + guides/drive-corps/Drive Corps first, not Peregrine: two game types instead of eleven, content that already exists as clean JSON mapping one-to-one onto entry fields, and the sharpest content pain of the two — its hundred-question bank has no authoring path at all, and decoy_group and shape are described in that repo as pure human judgment with no tooling. That's exactly what a schema with prompts and a confidence score fixes. Two game types is also enough to exercise applies_when properly, which is the whole bet.
games[] and the game templateWrite it once you know what the generic template can't show you — probably the priority ranking and the mechanic name in the section header. Doing it before Step 2 is guessing at a layout contract.
Eleven game types, ~40 HTML mockups that function as the actual spec, and an existing Python pipeline that already emits app-bank JSON. Press Raven would replace the authoring stage, not the ingest — and only if Steps 1–3 have earned it.
The framework already has the vocabulary: noir (charcoal paper, warm white ink, brass) for Peregrine; fieldkit (utilitarian kit monochrome, heavy rules, oversized numerals) for Drive Corps. The catalog itself wants editorial or fieldnotes — it's a reference you read, not a mood piece.
chapters, extensible vocabulary from kind_extensions, conditional fields from incantation, root extensions from the schema resolver.static let arrays inside SwiftUI views.content_contract makes the catalog generative — it authors the schema-extensions block its adaptations need.family as free text in the catalog means the five motivators aren't enforced, and motivator is the one axis you most want disciplined.mockup field — link it, never claim to replace it.break_idx and anything like it stays script-validated.playbook category if free-text motivators bother you enough to want the picker. That's the only thing a new category buys.Up ten because the shape is now read off manual, itinerary, and the schema resolver rather than proposed alongside them, and because the two mechanisms it leans on hardest — root-level extensions and applies_when on extension fields — were verified in source rather than assumed.
The residual doubt is entirely in one place: whether the mechanics catalog earns being its own guide, or is really just the first game's game-type list, generalised too early. Step 1 is designed to answer that in an afternoon rather than a quarter.