π User stories
These are the gameβs acceptance stories: what each surface promises, written as Gherkin scenarios (Given / When / Then) and kept in the repository at docs/design/user-stories/. This chapter reads those files directly β what you see below is the committed spec, 21 features with 212 scenarios. Scenarios badged planned (0 of them) are designed but not built yet; everything else describes behavior the app really has.
The stories bind to the same honesty contract as the app: they describe recorded reality β bases on every transition, fail-closed checks, no scripted theater. A story that stops matching the app is a bug in one of them.
Worlds index β choose your world
worlds.featureAs an operator with several projectsI want each project to appear as a world card with its real, derived historySo that I can pick a fight knowing exactly what has actually shipped and failed there
ScenarioThe grid shows every world with stats derived from real runs
- Givenat least one world exists in the game database
- WhenI open the worlds page
- ThenI see one parchment card per world with its name and emblem
- Andeach card shows raid count, bosses defeated over total, shipped tickets over total, and a wipe count
- Andeach card shows a progression line "Level N Β· N XP Β· earned/total achievements"
- Andevery one of those numbers is computed from the world's recorded timeline, not stored copy
ScenarioAn empty roster says so plainly
- Givenno worlds exist yet
- WhenI open the worlds page
- ThenI see the line "Every world starts empty. Name yours."
- Andthe "Create a world" card is still offered
ScenarioCreating a world
- WhenI click the "Create a world" card
- Thenthe card flips into an inline form with Name required and Description, GitHub repo, and Local path optional
- WhenI submit a name
- Thena world is created via POST /api/game/worlds
- AndI am taken to the new world's page
- Anda failure shows the API's error message in the form instead of navigating
ScenarioRenaming a world and binding its repos
- Givena world card
- WhenI click "Rename"
- Thenan inline form opens pre-filled with the current name, GitHub repo, and local path
- Andsaving PATCHes the world, where an emptied repo field clears that binding
- Andcancel closes the form without changes
ScenarioWorlds with history cannot be deleted
- Givena world that contains at least one raid
- Thenits "Delete" button is disabled
- Andits explanation reads "Worlds with raids carry history β they cannot be deleted"
ScenarioDeleting an empty world uses an in-card confirm, never a browser dialog
- Givena world with zero raids
- WhenI click "Delete"
- Thenthe card shows a confirm step naming the world with "Delete forever" and "Keep it" buttons
- Andno window.confirm dialog is used
- WhenI confirm
- Thenthe world is deleted and the grid refreshes
ScenarioA world card tooltip carries the same derived stats
- WhenI hover a world card with a mouse
- Thena cursor-following tooltip shows Raids, Bosses down, Shipped, Wipes, Progress, and Founded
- Andon a touch device the first tap pins the tooltip in place and the second tap navigates
ScenarioRepo bindings are visible on the card
- Givena world bound to a GitHub repo or a local path
- Thenthe card displays those bindings under the world name
ScenarioThe local-path binding states whether it is REALLY a git repo
- Givena world bound to a local path
- Thena status chip next to the path states plainly one of: "β git repo", "β path missing", "β not a git repo", or "β not the repo root"
- Andthe status is computed on the server against the real filesystem at render time β never assumed from the stored string
- Andthe same status renders on the world page header and the SDLC project page
ScenarioA relative local path is refused at the door
- WhenI try to bind a local path that does not start with "/"
- Thenthe API answers 400 naming the offending value, and the form shows that error in place
- Anda nonexistent absolute path is ACCEPTED (bind first, clone later) β the chip then reads "β path missing"
ScenarioEditing avatar prompts from a world card
- Givena world card
- WhenI click "π¨ Avatars"
- Thenthe Avatar Sheet dialog lists the world crest, the hero, and every raid and boss with its current emblem or painted art
- Andselecting an entity shows its stored avatar prompt in a text field
- And"Save prompt" persists the prompt via PATCH /api/game/avatar/[entityKind]/[entityId] β with or without an image service
- Andthe dialog closes on backdrop click or Escape, never trapping me
ScenarioThe sigil is the honest default when no image service is configured
- GivenAVATAR_IMAGE_BASE_URL, AVATAR_IMAGE_API_KEY, or AVATAR_IMAGE_MODEL is unset
- Thenevery entity's status chip reads "sigil (no image service)"
- And"Save & conjure" is disabled with an explanation naming those variables
- Andprompts still save
ScenarioConjuring real art swaps the sigil for the painting when it lands
- Givenan image service is configured and an entity has a prompt
- WhenI click "Save & conjure"
- ThenPOST /api/game/avatar answers 202 and the painting runs out of process, never in the request path
- Andthe entity's chip reads "conjuringβ¦" while the sheet polls the avatar API
- Andwhen generation lands the seeded sigil is swapped for the PNG, streamed from .game/avatars/ via the API β never from public/
- Anda failed generation shows a "failed" chip with the verbatim error, and the sigil stays
ScenarioA painted world crest replaces the card emblem
- Givena world whose crest avatar has been generated
- WhenI open the worlds page
- Thenthe card head renders the painted crest in the same 48px slot the sigil used
World screens β one purpose per screen behind a persistent hub nav
screens.featureAs a player moving around a worldI want each surface on its own labeled screen with a persistent navSo that I always know what I am looking at and nothing else fights it for the page
ScenarioThe world splits into five purpose-built screens
- Givena world with recorded work
- Then/world/[slug] is the WORLD HUB β the character strip and the quest log hierarchy only
- And/world/[slug]/armory is Armory & Achievements only
- And/world/[slug]/replay is the battle replay and its combat log only
- And/world/[slug]/timeline is the world-line canvas only
- And/world/[slug]/live is the Live Workshop β party rail, agent terminal, and App View
ScenarioEvery screen states what it is
- WhenI open any world screen
- Thena screen title names it in plain words ("World Hub β the Quest Log", "Armory & Achievements", "Battle Replay", "World Timeline", "Live Workshop")
- Anda one-paragraph subtitle says what the screen shows and where it comes from
ScenarioThe hub nav is persistent and marks where I am
- Thenevery world screen renders the same world header and screen tabs
- Andthe active screen's tab is visually marked and carries aria-current="page"
- Andeach tab's tooltip states the screen's purpose in one plain sentence
ScenarioWatching a replay is a navigation, never an autoplay
- Givena ticket row in the quest log with a recorded fight
- WhenI click "Watch replay βΈ" (or "β Watch live" while it runs)
- ThenI land on /world/[slug]/replay?fight=<cycleId> with that fight selected
- Andnothing on the hub ever starts playing a battle by itself
ScenarioLegacy hash deep links land on the new screens
- Givena link minted before the screens split
- Then/world/[slug]#replay=<cycleId> forwards to /world/[slug]/replay?fight=<cycleId>
- And/world/[slug]#armory forwards to /world/[slug]/armory
- And/world/[slug]#character stays on the hub, whose character strip carries that anchor
ScenarioData refreshes never move the viewport (operator rule)
- Givenany world screen is polling its APIs
- Whena poll lands new data
- Thenthe page scroll position does not change
- Andno panel expands, collapses, or scrolls itself into view because of the poll
- Andthe only automatic scrolling anywhere is a log following its tail while I am already at the tail
Live app preview with app lifecycle and honest campaign recovery
app-preview.featureAs the operator of a world with a bound repoI want the delivered app running, embedded, and controllable from the world hubSo that I can use what the SDLC built at any time without touching the SDLC itself
ScenarioBackground
- Givena world whose repo binding is a real git checkout with a package.json
ScenarioThe panel says what is happening in plain words
- WhenI open the world hub
- Thenthe Live App panel shows an honest server status chip ("app running on :PORT" or "app stopped β why")
- Anda campaign chip that distinguishes active work, paused intent, and a stopped campaign
- Andthe panel states that app lifecycle and SDLC lifecycle are separate
ScenarioStarting the app embeds a clickable live preview
- Giventhe app is stopped
- WhenI press "Start app"
- Thena dev server for the bound repo starts on the world's own port
- Andthe panel reports started only after the port actually answers
- Andthe running app renders inside an iframe I can click around in
ScenarioOpening the app full screen leaves our UI entirely
- Giventhe app is running
- WhenI press "Open full screen β"
- Thenthe app opens in a new browser tab at its own URL, unrelated to the game UI
ScenarioStopping the app never touches the SDLC
- Giventhe app is running and a campaign is executing
- WhenI press "Stop app"
- Thenthe app's process group is stopped and the chip reads stopped
- Andthe campaign keeps running exactly as before
ScenarioRestarting a running app uses the engine's stale-build recovery
- Giventhe app is running
- WhenI press "Restart app"
- Thenthe engine stops the app, clears its framework build output, and starts it again
- Andthe panel reports success only after the page and its required same-origin assets verify
ScenarioRestart does not silently become start
- Giventhe app is stopped
- WhenI request "Restart app"
- Thenthe request is refused and tells me to use "Start app"
ScenarioPausing the campaign never touches the app
- Givena campaign is executing and the app is running
- WhenI press "Pause campaign"
- Thenthe scheduler launches no new tasks
- Andthe task already mid-build finishes and its outcome stays on the record
- Andthe app keeps serving
ScenarioResume continues from the ledger
- Giventhe campaign is paused
- WhenI press "Resume campaign"
- Thena live driver continues launching from the ledger
- Anda dead driver is relaunched with the same retained ask id before the pause marker clears
- Andnothing is lost
ScenarioA stopped campaign is never called active
- Giventhe campaign has no pause marker
- Andneither its driver nor any of its slices is alive
- Whenthe Live App panel polls status
- Thenthe campaign chip reads "campaign stopped"
- And"Pause campaign" is absent
- And"Relaunch campaign" is available for the retained ask
ScenarioRelaunch preserves campaign identity
- Givena stopped campaign has a deterministically recoverable original ask
- WhenI press "Relaunch campaign"
- Thenthe engine starts the driver with the existing ask id and world slug
- Andno new campaign id or hierarchy is created
- Anda second concurrent relaunch is refused
ScenarioA failed paused recovery preserves pause intent
- Givena paused campaign's driver is stopped
- Andits original ask cannot be recovered or launched
- WhenI press "Resume campaign"
- Thenthe recovery fails closed with a specific reason
- Andthe pause marker remains present
ScenarioA dead server never shows as running
- Giventhe app's recorded process has died
- Whenthe panel polls status
- Thenthe chip reads stopped with the reason, because liveness is asked of the OS, never a status field
The Watchtower β the engine watches the delivered app after shipping
watchtower.featureShipping is never the end of the SDLC (operator ruling 2026-07-25): theengine watches the running app's log for errors, dedupes what it sees into"sightings", and shows the evidence on the world hub so the conveyor canfile and fix them.
ScenarioBackground
- Givena world with a bound repository
- Andits app has been started at least once by the app server manager
ScenarioThe panel says what it is in plain words
- WhenI open the world hub
- Thenthe Watchtower panel appears between the Live App panel and the quest log
- Andits subtitle explains that the engine watches the running app's log
- Andit explains that a repeated defect raises a count instead of filing a twin
ScenarioThe last watch is evidence, never a status dot
- Givenat least one watch pass has run
- Thenthe panel shows WHEN the last pass ran
- Andthe pass's own account verbatim β bytes scanned and what was found
- Andan all-clear is worded as only as fresh as that last watch
ScenarioRunning a watch pass by hand
- WhenI press "Watch now"
- Thena Log Sentinel pass runs against the app server's log
- Andthe panel refreshes with the pass's account and any new sightings
ScenarioThe first pass baselines instead of excavating history
- Giventhe app's log existed before the Watchtower ever ran
- Whenthe first watch pass runs
- Thenit reports how many bytes of history it baselined
- Andit files no sightings from that history
- Andonly lines logged after the baseline can become sightings
ScenarioA defect in the running app becomes a sighting with verbatim evidence
- Giventhe app logs an error after the baseline pass
- Whenthe next watch pass runs
- Thena sighting appears with a specific summary quoting the error line
- Andits severity is shown (blocker / bug / paper-cut) with a color accent
- Andclicking the row expands the verbatim log excerpt in place
- Andclicking it again collapses the excerpt
ScenarioThe same defect seen again raises the count
- Givena sighting already exists for a defect
- Whenthe app logs the same defect again and a pass runs
- Thenthe sighting's count rises and its "last seen" freshens
- Andno second row appears for it
ScenarioLong sighting evidence reflows on a phone
- Givena sighting summary and metadata are longer than one mobile line
- WhenI open the hydrated world hub at 375px wide
- Thenthe Watchtower row wraps the complete summary and metadata
- Andno visible Watchtower node paints outside the viewport
ScenarioFiling a sighting as a quest β the find β file arrow
- Givenan open sighting with its evidence expanded
- WhenI press "File as a quest (ticket)"
- Thenthe defect is filed through the SAME intake path a player uses
- Andthe filed ask carries the summary, the sentinel's history, and the verbatim evidence
- Andthe sighting shows "ticket filed" and offers no second filing
- Andif the factory is busy the refusal is shown and the sighting stays open
ScenarioA log meltdown cannot bury the quest log
- Giventhe app floods its log with hundreds of error lines
- Whena watch pass runs
- Thenat most a bounded number of sightings are recorded in that pass
- Andthe pass's account names how many candidates were dropped
The Design Studio β what the design pipeline drew, honestly counted
design-studio.featureThe world hub shows the campaign PRD's declared surfaces and flows as data,with deterministic visuals rendered FROM that data, and per-surfacecoverage that states plainly what has not yet been designed.
ScenarioBackground
- Givena world whose campaign persisted a v2 PRD (surfaces + flows + sketch)
ScenarioThe header carries the unconditional provenance qualifier
- WhenI open the world hub
- Thenthe Design Studio states the surface and flow counts
- Andit states the surface list is model research, not observation
ScenarioThe app shell visual is computed from the PRD's own data
- Thenthe shell SVG shows one labeled region per view surface
- Andits caption says it is a deterministic render from PRD data
ScenarioPer-surface cards state their coverage plainly
- Theneach declared surface has a card with its name, kind and description
- Anda card with only the PRD visual says wireframe/critique/hi-res have not yet run
- Anda card never implies a stage that has not produced an artifact
ScenarioVersions are history, not garbage
- Givena surface's artifact was re-persisted
- Thenthe card shows the LATEST version
- Andprior versions remain in the store (the Council can still pin them)
ScenarioA world with no campaign PRD shows no empty shell
- Givena world whose campaigns predate PRD rows
- Thenthe Design Studio renders nothing rather than an empty frame
ScenarioThe coverage audit renders in the header, computed from rows (F7)
- Thenthe header shows the audit qualifier with its coverage fraction
- Andthe qualifier ALWAYS states the surface list is model research, not observation
- Andany denominator problem (a surface dropped without a retire record) renders as a visible warning
ScenarioEach card carries its audit cell β the surface's true pipeline position
- Thena critiqued surface's card names the wireframe verdict and critique version
- Anda rendered surface names its hi-res version and identity state, including "UN-REVIEWED" when no eye formed a verdict
- Anda retired surface's card shows RETIRED with the recorded reason
Quest log β the world hub opens on the work itself
quest-log.featureAs a player returning to a worldI want the hub to open on a quest log ordered milestone β epic β story β task β slicesSo that I read what the work IS before any battle theater plays
ScenarioThe world hub opens on the quest log
- Givena world with recorded work
- WhenI open the world page
- Thenthe first thing presented is the quest log, not an auto-playing battle
- Andthe battle stage, terminal, timeline, and armory are one click away on the screen nav
ScenarioThe log is a milestone β epic β story β task hierarchy
- Givena world with milestones, epics, stories, and tasks
- Thenthe quest log lists milestones (raids) in order
- Andeach milestone expands to its epics (bosses)
- Andeach epic lists its user stories, each with a derived status
- Andeach story lists its tasks (tickets) beneath it
- Andeach ticket lists the slices its design produced, each with its own outcome
ScenarioA story's Gherkin expands in place
- Givena story with recorded Given/When/Then acceptance text
- Thenits row offers a "Gherkin βΈ" toggle
- WhenI click the toggle
- Thenthe stored Gherkin text renders verbatim beneath the row
- Anda story without Gherkin says "no Gherkin yet" instead of hiding the gap
ScenarioBlockers render as chips on any tier
- Givenan entity that is blocked by another entity
- Thenits row shows a "β blocked by <title>" chip naming the blocker in plain words
- Andthe chip appears on whichever tier is blocked (milestone, epic, story, or task)
ScenarioThe world hub reflows at a 375px viewport
- Givena world with counts and a dependency whose title is longer than one mobile line
- WhenI open its hydrated quest log at 375px wide
- Thenthe page has no horizontal scroll
- Andthe Rulebook link and every count remain fully visible
- Anddependency chips wrap their complete labels inside the quest-log row
ScenarioTasks are the slices β the tier that actually executes
- Givenan epic broken down into stories
- Thenevery task row is marked "slice β executes"
- Andno story row is ever marked as a slice β a story is a rollup, only its tasks run
ScenarioA story with no tasks says it cannot execute
- Givena story that has not been broken into tasks
- Thenits row shows "β no tasks yet β not executable"
- Andit is never marked "slice β executes"
- Andits status never reads shipped, whatever the stored row claims
- Andits epic cannot derive defeated while it remains undelivered
ScenarioLegacy worlds render exactly as before
- Givena world recorded before stories existed
- Thenevery epic lists its tickets directly with the "Ticket" tier chip
- Andno story rows, slice markers, or blocker chips appear
- Andno "legacy task" note appears β nothing looks anomalous without a story tier
- Andevery status, action, and muted-fallback behavior is unchanged
ScenarioA story-less task inside a broken-down epic says so honestly
- Givenan epic that has user stories
- Anda task under it that predates the story tier, or whose story was deleted
- Thenthat task renders directly under the epic
- Andit carries the note "legacy task β filed before the story tier existed"
- Andno story is fabricated for it to make the tree look uniform
ScenarioPlain names lead, flavor follows
- Thenevery quest-log row leads with the plain work title and its real status
- Andthe raid/boss/phase flavor renders second, never as the only label
ScenarioEvery status is derived from recorded runs
- Thena row's status (queued, running, shipped, stalled, wiped) comes from the recorded transcript history
- Andno status is invented, simulated, or hand-set
ScenarioReplays are opt-in
- Givena ticket row with a recorded fight
- Thenno battle replay starts playing on page load
- WhenI explicitly choose "Watch replay βΈ" on that ticket
- ThenI land on the Battle Replay screen scoped to that fight
ScenarioThe log offers each entity's actions in place
- Thena quest-log row offers its actions visibly (open the run report, watch the replay)
- Anda running ticket shows a live dot and offers "β Watch live"
- Andno action is locked behind a hover-only affordance
ScenarioDone work folds away and mutes; actionable work stays vivid
- Givena milestone with cleared and in-motion epics
- WhenI open the quest log
- Thencleared milestones, defeated epics, all-shipped stories default to collapsed and render muted
- Andin-motion tiers default to expanded
- Andevery row leads with a status glyph colored by its machine status (β done, β running, β up next, β needs attention, β blocked)
- Andthe glyph decorates the row's plain-words status, never replaces it
ScenarioEvery tier is collapsible and remembers my choice while I watch
- WhenI toggle a milestone, epic, or story closed or open
- Thenmy toggle wins over the smart default
- Andthe 5-second poll never fights my click
ScenarioTier chips carry icons beside their words
- Thenmilestone, epic, story, and task chips show π π π π― beside the tier word
ScenarioA retired tree stops counting as work to ship but stays on the page
- Givena breakdown tree born of a superseded ask, never locked and never shipped
- Whenthe engine retires it with a recorded reason
- Thenits rows still render, muted, each carrying "β retired β <the recorded reason>"
- Andits rows lead with the β glyph and never read as actionable
- Andevery "shipped/total" and "cleared/total" count on the log counts live rows only
- Andthe retired milestone and epic default to collapsed like finished work
ScenarioRetiring debris never fabricates a victory
- Givenan epic whose every ticket is retired
- Thenthat epic reads "awaiting", never "defeated" β zero live tickets can never defeat a boss
- Anda live epic's defeat in the same world is computed exactly as before
ScenarioRetirement is reversible data
- Givena retired tree
- Whenthe retirement is cleared
- Thenthe tree returns to every count exactly as it stood before
Quest intake β posting work to the board
quest-intake.featureAs a playerI want to describe work in plain words and have it filed at the right tierSo that a posted ticket becomes a real run without me learning the taxonomy first
ScenarioOpening and closing the quest board
- WhenI click the "οΌ New quest" button
- Thena modal opens titled "The quest board"
- Andclicking the backdrop closes it, and visible Close and Cancel buttons are always offered (operator rule)
- Andclosing while a submission is in flight aborts the request so a late response cannot resurrect the dialog
ScenarioThe Oracle is explained in plain words before it runs (operator rule)
- Thenthe form states that posting files the quest instantly under a provisional title
- Andthat the Oracle is a real model call that renames/re-slots it in the background
- Andtier chips offer Campaign/Whole app, Milestone/Raid, Epic/Boss, Ticket/Phase, and "Let the Oracle decide"
- Andeach chip's tooltip explains the tier in one plain sentence
ScenarioA Campaign runs the whole SDLC chain from one ask (operator goal 2026-07-18)
- GivenI pick the Campaign chip and describe an app-scale ask (e.g. "a true clone of Todoist β web only")
- Thenthe form explains the chain in plain words BEFORE it runs: Oracle sizing, a researched PRD of the real product, the PM's milestone β epic β story β task breakdown into this quest log, the Council's three rounds, the LOCK, and one real build run per task
- Andposting starts a DETACHED campaign runner (it survives dev-server restarts) and answers 202 with the campaign's ask id
- Anda toast links to /runs/<askId>, where the whole campaign β the PRD document included β replays live
- Andwhen the world has a bound local repository, every slice builds in it and the commits stay there as the deliverable
- Anda failed turn (bucket, PRD, breakdown, or materialize) HALTS the campaign with a recorded ok:false check naming the step β never a silent degradation
- Andthe Oracle sizing the ask at epic or story tier halts the campaign and tells me to file it as a quest instead
ScenarioI can pin the placement under existing work (operator rule)
- Giventhe world already has raids and bosses
- ThenI can slot the new work under an existing raid, and a ticket under an existing boss
- Andpicking a boss automatically sets its raid, and the boss list filters to the picked raid
- Andpins are honored even on the "auto" tier β the Oracle only fills what I left open
- Anda pinned placement is never moved by the Oracle's background ruling
ScenarioPosting never blocks on the Oracle (async intake)
- WhenI post the quest
- Thenthe board answers 202 in under a second with the quest already filed
- Andthe modal closes with a toast: "The Oracle is pondering β the quest will be renamed when it rules."
- Andthe quest log picks the rename up on its normal poll when the ruling lands
ScenarioA ticket placement starts a real run immediately
- Givenmy ask was filed as a ticket and a cycle started
- Thenthe cycle is pre-minted and bound to the ticket BEFORE the runner spawns (design R14)
- Andthe toast offers "β Watch the pull" linking to /runs/<cycleId>
ScenarioThe Oracle's ruling is recorded, not narrated
- Giventhe Oracle answered with a valid classification
- Thenthe same entity is renamed and re-slotted under the ruled raid/boss
- Andan ok:true kind:"check" row at step intake.classify quotes the ruling verbatim in the run transcript
ScenarioA refusal or invalid ruling fails closed
- Giventhe model refused, answered prose, or failed the classification schema
- Thenan ok:false kind:"check" row is recorded in the run transcript (a wipe β never silent)
- Andthe fallback filing stays: the entity keeps its provisional title and stays marked auto-filed
ScenarioFallback filings stay honest in the quest log
- Givenan entity the Oracle never ruled on
- Thenit renders muted with an "auto-filed" marker in the quest log
- And"Re-consult the Oracle" renames/re-slots THAT entity β it never files a duplicate
Factory pipeline β the contract-checked review turns of a run
factory-pipeline.featureAs a player reading a run transcriptI want every product, design, and review turn to be a real recorded model call with checked inputs and outputsSo that no verdict, story, or mockup in the story is theater
ScenarioThe Product Manager writes user stories after the bet is framed
- Givena run whose bet framing has completed
- Thena productManager turn (decide.stories) makes one real model call
- Andits output must parse as 2 to 4 stories each carrying a Given/When/Then gherkin scenario
- Anda refusal or malformed answer is recorded as a failed check, which counts as a wipe
- Andthe recorded stories are quoted verbatim in the transcript narration
ScenarioThe user stories feed the slice decomposition
- Giventhe productManager turn produced contract-valid stories
- Thenthe decomposition prompt carries those stories as notes
- Andwhen no valid stories exist the decomposition proceeds from the framed bet alone, stated plainly
ScenarioA UX mockup is drawn only for user-visible work
- Giventhe ask or the designed slices mention a user-visible surface such as a UI, page, screen, component, or CLI output
- Thena uxDesigner turn (design.ux) produces a markdown wireframe mockup recorded as a real model call
- Andthe run page renders that mockup as markdown
ScenarioLibrary-only work skips the mockup honestly
- Givenneither the ask nor the slices imply a user-visible surface
- Thenno UX turn runs
- Andthe transcript records a narration saying the UX mockup was skipped because there is no user-facing surface
ScenarioThe Design Reviewer rules on the mockup with one bounded revise loop
- Givena contract-valid UX mockup
- Thena designReviewer turn (design.review) receives the mockup only after a minimum-content input check
- Andit must answer with an approve or revise verdict plus a note, validated by schema
- Andon a revise verdict the UX Designer re-runs exactly once with the reviewer's note before a final ruling
- Andan unresolved revise after that one round is stated plainly as proceeding despite the verdict
ScenarioCode review and architecture review join the attestation
- Giventhe integrate commit produced a real diff
- Thena codeReviewer turn (assure.code) and an archReviewer turn (assure.architecture) each review that diff
- Andboth use the same fail-closed input contract that rejects an empty or stub diff
- Andthe ATTESTATION transition's basis quotes the security, code review, and architecture verdicts verbatim
- Anda risk verdict is promoted only with an explicit "promoted DESPITE" statement in the basis
ScenarioA missing verdict never earns the attestation
- Givenany of the security, code review, or architecture turns failed its turn contract
- Thenthe ATTESTATION transition records basis kind none marked UNPROVEN
- Andthe failed check stays on the record as a wipe
Party frame β the agent roster in marching order
party.featureAs a player watching a runI want the left rail to list the real agent turns in execution order and light up who is actingSo that the "party" reads as the actual delivery pipeline it is
ScenarioMembers are listed in execution order with numbered rows
- WhenI open a world page
- Thenthe party frame lists all 21 agent turns from the contract registry, numbered 1 to 21
- Andthe order shown is the order the pipeline actually executes
ScenarioEach member names its input source (operator rule)
- Theneach row shows "takes: <input> β fed by the <previous member's job title>"
- Andthe first member's row says it is fed by me, the player
- Andeach row shows "makes: <output>"
ScenarioA member lights up while it is really acting
- Givena branch whose newest transcript node maps to a member's turn key
- Andthat branch is running
- Thenthat member's row renders in the battling state
- Andits row names the boss and phase it is fighting
ScenarioFloating combat text only for real recorded outcomes
- Giventhe 3-second poll returns a node whose ok flipped to true
- Thenrising combat text appears over that member reading "HIT β <phase> GREEN"
- Andthe hit is styled as a CRIT only when the branch has zero wiped pulls
- Anda node whose ok flipped to false flashes the member's row border red instead
ScenarioIdle members rest honestly
- Givena member with no running branch on its turn
- Thenits row shows its last recorded action ("last acted β <phase>" or the recorded fact line)
- Anda member that never acted shows no invented activity
ScenarioThe member tooltip repeats the contract and the live fight
- WhenI hover a member row
- Thena cursor-following tooltip shows the job title, flavor name, class and role, takes and delivers lines
- Andwhen the API served one, a verbatim quote of the member's last recorded result
- Andeither the fight it is in right now or "Resting at camp"
ScenarioThe frame stays current without me refreshing
- Thenthe party frame polls the world API every 3 seconds
- Anda failed poll keeps the last snapshot instead of blanking the rail
Character sheet β one honest contract card per party member
character-sheet.featureAs a reader meeting an agent anywhere in the gameI want the same matter-of-fact sheet for it everywhereSo that I always know what it really does before I see its costume
ScenarioThe job title is the primary label everywhere
- Givenany surface that names a party member
- Thenthe plain job title (for example "Implementer" or "Test Writer") renders first
- Andthe RPG character name and class (for example "The Forgemaster Β· Berserker") render second as flavor
ScenarioThe sheet states the member's real input and output contract
- WhenI read a member's sheet in the party frame or the guide
- Thenit names what the member takes in and what it must produce
- Andit names which member feeds it, with the first member fed by me, the player
ScenarioThe sheet says who actually acts on the turn
- WhenI read a turn card in the guide's party chapter
- Thenit states whether the turn runs as a real model call, a real coding agent in a git workspace, or the pipeline itself with no model call
ScenarioThe sheet is generated from the contract registry, never re-typed
- Thenthe party frame, the battle roster, and the guide's party chapter all read the same AGENT_TURNS registry
- Anda registry change updates every surface, so the sheet cannot drift from what the runner validates
ScenarioThe sheet shows the member's latest real action
- Givena member that has acted in a recorded run
- Thenits row shows the last phase it acted in, with the recorded fact line when the API serves one
- Andits hover tooltip may quote a verbatim excerpt of the recorded result, never a rephrasing
ScenarioEquipped boss drops ride on the sheet
- Givena boss drop equipped to a member
- Thenthe member's row shows a rarity-colored badge dot for the item
- Andthe badge's explanation names the item and says it is cosmetic, minted from a real fight
ScenarioThe Raid Leader's sheet is the player's own
- Thenthe party frame lists me first as "Raid Leader β You"
- Andmy sheet shows the world's level and XP with the note that they were earned by this world's real runs
- Andthe hero card shows my clean-ship streak as "π₯ streak: N (best M)" β consecutive shipped tickets with no wipes
Battle stage β auto-battler replay of a real run
battle-replay.featureAs a playerI want each fight rendered as a scrubbable battle whose every beat traces to a recorded eventSo that the spectacle is a skin over the run, never a script
ScenarioPicking a fight
- Givena world with recorded fights
- Thena chip row lists the fights newest first with status glyphs (β running, β shipped, β stalled)
- Anda ?fight=<cycleId> deep link (the quest log's "Watch replay") preselects that fight
- Andwith no explicit pick the stage defaults to the running fight, else the latest
- Andwhen two or more fights are running it defaults to the merged "β All battles" arena
- Anda poll may add chips but never expands, selects, or scrolls anything by itself
ScenarioThe arena layout mirrors the roster contract
- Thenparty members stand on the left in role ranks β tanks front, dps mid, healers/support/scouts back
- Andeach member is labeled with its plain job title
- Andthe boss stands on the right with a name plate and an HP bar
ScenarioReplay controls
- Thenthe bottom bar offers play/pause, a speed cycle of 1Γ 4Γ 16Γ 64Γ, a scrubber, and elapsed/total clocks
- Andscrubbing pauses playback and folds the battle state instantly to that time
- Andonly beats near the new playhead spawn animations, so a far scrub never spawns a storm
ScenarioWatching live
- Giventhe selected fight is still running
- Thena "β LIVE" pin is offered, and while pinned the playhead sticks to the newest recorded beat
- Andscrubbing away unpins so I can review earlier moments while the fight continues
ScenarioCombat log rows state the SPECIFIC recorded action
- Theneach log row reads "[time] <actor> <specific action> Β· β<flavor>β <damage>"
- Andthe specific action is built from the row's real payload β which files the agent changed ("wrote the failing test slice-1.test.js", "implemented slice-1.js"), the actual command with exit code and executed-test count ("node --test β exit 0, 3 tests green", "git commit landed β exit 0"), what the role did plus ~90 verbatim chars of its response ("framed the ask into a testable bet β ββ¦β"), or the real cause of a FAIL/WOBBLE/PATCH_READY from its recorded basis
- Andgeneric phrasing appears only when the recorded row carried no specifics
ScenarioA log row opens the full recorded evidence in place
- WhenI click anywhere on a log row (the whole row is the toggle, aria-expanded)
- Thenan anchored, scrollable evidence popover opens near my click (a bottom sheet on small screens)
- Andit shows the actor, the machine stepKey, and the FULL underlying transcript row fetched from the run report β instructions, diff or changed files, the agent's last message, test output, the verbatim model response, or a transition's basis
- And"(none recorded)" appears only for a field that is truly absent
- Anda beat with no verbatim excerpt says so honestly and still links to the full report β a click is never a no-op
- And"Open in the full report β" links to /runs/<cycleId> as a secondary path, never a requirement
- Andthe popover dismisses on any tap or click outside it and on Escape; the β close button is a courtesy
- Anda small β± "jump replay here" button on the row's right scrubs the replay to that beat's time
ScenarioThe log never yanks my scroll position
- GivenI have scrolled up in the combat log
- Thennew rows do not snap the log back to the tail
- Andauto-follow resumes only when I scroll back to the bottom
ScenarioWipes and victories are recorded events, not effects on a timer
- Giventhe transcript records a wipe beat
- Thenthe whole party takes damage and the stage flashes a blood vignette
- Giventhe transcript records a victory beat
- Thenthat boss's HP reaches zero and its plate reads VICTORIOUS
- Andno failure or recovery is ever injected for drama
ScenarioThe merged arena puts concurrent fights on one wall clock
- Giventwo fights overlapped in time
- WhenI select "β All battles"
- Thenboth bosses render side by side, the roster is the shared party, and beats interleave by real timestamps
- Andeach log row names which fight it belongs to
ScenarioA merged log row names its own fight's real work, then the boss flavor
- GivenI am watching the merged "β All battles" arena
- Thenevery log row carries a tag naming the fight it came from β its ticket's plain title first ("Forge the Truncate Rune"), the boss flavor after it ("Β· Profile Formatter"), never the flavor alone
- Andthe tag is built from that row's OWN fight, so no row can be labelled with another fight's name
- Andthe full "<fight> β boss: <boss>" text is available on the tag when the row is too narrow to show all of it
- Anda single-fight replay shows no such tag β there is only one fight to name
ScenarioTwo fights under ONE epic stay distinguishable
- Giventwo fights of the same epic are running at once
- Andboth record a beat with the same actor and the same text at the same second
- Thenthe two rows still read as two DIFFERENT fights, because each names its own ticket
- Andneither row is a duplicate of the other β a row is unique per (fight, source transcript row)
- Andmerging the same fight twice would still render its rows once
ScenarioA beat whose fight is unknown never borrows another fight's name
- Givena beat cannot be attributed to any fight in the merged script
- Thenits row carries no fight tag at all rather than a name it did not earn
- Andwhere a boss name would be shown for that beat it reads "Unattributed fight" β never the first fight's boss
ScenarioHovering the pixels reveals the fact behind them
- WhenI hover a projectile, member, or boss
- Thena tooltip states the plain-words fact of that beat, its replay time and wall-clock time, and any damage number
- Andfor beat effects it quotes the recorded excerpt when one exists
ScenarioReduced motion is honored
- Givenmy system prefers reduced motion
- Thenthe stage spawns no projectile or floating-text animations
- Andthe battle state, log, and controls still work
Agent terminal β the live transcript feed
terminal.featureAs an operator who wants the unskinned truthI want a terminal view of the selected fight's transcript, row for rowSo that I can read exactly what each agent was told, answered, ran, and proved
ScenarioSelecting a fight
- Thena select lists the world's fights with status glyphs, defaulting to the running fight, else the latest
- Andwhile the selected fight runs, a live dot and the caption "streaming β updates every 2s" show
- Andthe transcript polls every 2 seconds while live and is fetched once otherwise
ScenarioOne badged row per transcript entry
- Thenmodel calls render as MODEL rows with the actor's job title and a one-line response summary plus token counts
- Andcoding-agent runs render as CODEX rows with the changed files (or last message) and duration
- Andshell commands render as RUN rows with the command, exit code, and duration
- Andstate transitions render as STATE rows reading "<event>: <from> β <to>"
- Andnarration and notes render as plain unbadged lines
ScenarioSTATE rows carry their basis marker inline
- Givena transition with recorded evidence
- Thenits STATE row appends a one-line excerpt of the basis detail
- Givena transition advanced by runner policy
- Thenits row appends "Β· RUNNER POLICY"
- Givena transition with an explicit none basis
- Thenits row appends "Β· UNPROVEN"
ScenarioCheck rows fail closed
- Givena turn-contract check entry
- Thenonly an explicit ok:true renders as "CHECK β"
- Andanything else β including a missing ok field β renders as "CHECK β" in the failure tone
ScenarioRows expand in place to the full evidence
- WhenI expand a MODEL row
- ThenI see the recorded system prompt, user prompt, and full response
- WhenI expand a CODEX row
- ThenI see the instructions, the diff with added/removed lines colored, the agent's last message, and output tails
- WhenI expand a RUN row
- ThenI see the full command with its working directory and the recorded stdout/stderr, or "(no output recorded)"
ScenarioExpand all and collapse all
- Thenheader buttons expand or collapse every expandable row at once
ScenarioThe feed follows the tail only while I am at the tail
- GivenI scrolled up more than 40 pixels from the bottom
- Thennew entries do not move my scroll position
- Andauto-follow resumes when I return to the bottom
ScenarioEmpty states say what is missing
- Givena world with no recorded fights
- Thenthe terminal says "No fights recorded in this world yet."
- Anda fight with no transcript says so instead of showing a spinner forever
App View β what was delivered
app-view.featureAs the person who posted the questI want to open the actual files the agents built and the runs that proved themSo that "shipped" always means inspectable code, not a status flag
ScenarioSelecting a fight and reading its status honestly
- Thena select lists the world's fights, defaulting to the running fight, else the latest
- Andthe artifact header states the fight's real status in plain terms (still in the forge, delivered & proven, the fight stalled, waiting in the queue)
- Andthe original ask is expandable under "The ask"
ScenarioThe deliverable renders as pixels when a capture exists
- Givena run whose transcript directory holds <cycleId>-deliverable.webm or .png (the Ship phase's headless-browser capture)
- Thenthe App View shows it above the delivered files, labeled "The deliverable, as pixels" with its real type and byte size
- Anda fight with no capture shows the files and proofs alone β no placeholder ever pretends pixels exist
ScenarioDelivered files are browsable as tabs
- Givenan artifact with files
- Theneach file is a tab named by its path, with the size shown on hover
- Andcode files render with line numbers and syntax highlighting
- Andimage files render inline as images
- Anda fight that delivered nothing says "No files were delivered" instead of showing an empty viewer
ScenarioProof runs sit next to the code they prove
- Givenrecorded test runs for the fight
- Theneach renders as a chip with β or β, the command, and its exit code
- Andexpanding a chip shows the run's recorded output verbatim
ScenarioThe agent's summary is quoted, not paraphrased
- Giventhe coding agent left a final message
- Thenit renders as a quoted block labeled "Agent's summary"
ScenarioThe panel declares its source of truth
- Thena footer badge states whether the files come from the live workspace or were reconstructed from recorded diffs
- Anda "Full transcript β" link opens the run report for the same cycle
ScenarioThe view refreshes when the fight finishes
- GivenI am viewing a running fight
- Whenthe fight ships or stalls
- Thenthe artifact is re-fetched so the delivered state replaces the in-progress one
World-line timeline β the world's recorded history as a canvas
timeline.featureAs a playerI want the world's whole history drawn as a gold trunk with branches, nodes, pulls, and scarsSo that one glance shows what happened, when, and what it cost
ScenarioThe canvas is recognizably a timeline
- Thena gold trunk runs left to right as the time axis
- Anddate ticks render under the trunk at raid boundaries at every zoom level
- Andeach ticket is a branch off the trunk whose nodes sit in time order
ScenarioWheel and trackpad scroll the timeline without click-drag (operator rule)
- WhenI use the mouse wheel or trackpad over the canvas
- Thenthe view pans horizontally along the time axis without needing to click and drag
- Andshift+wheel pans vertically
- Andtaking the wheel turns follow-live off
ScenarioDragging pans without stealing clicks
- WhenI press and move less than 5 pixels
- Thenthe gesture still counts as a click and reaches nodes and buttons
- WhenI move past the threshold
- Thenthe canvas pans and the click is suppressed
ScenarioFour zoom levels cover the whole story
- Thena zoom control offers Map 0.25Γ, World 0.5Γ, Raid 1Γ, and Pull 2Γ
ScenarioRune-coin nodes lead with the plain job title
- Theneach node's primary label is the agent's job title
- Andthe RPG character name and the machine stepKey render as demoted secondary labels
- Anda node that recorded ok shows a green pip, a failed one a red pip, and unreached steps a padlock
ScenarioHovering a node shows this run's reality before the generic contract
- WhenI hover a node
- Thenthe tooltip leads with the job title and any recorded fact lines
- Andit quotes verbatim recorded excerpts labeled "given" and "did" when the API serves them
- Andstatus says "in progress" only for the live tip of a running branch β otherwise the settled outcome
- Andthe generic takes/makes contract renders dimmed at the bottom
ScenarioClicking a node answers "what happened here" with the record itself
- WhenI click a node
- Thenan inspector popover shows the quest, phase, outcome, and timestamp
- And"What happened here" lists every transcript entry recorded for that step
- Andeach model call offers its full prompt collapsible under "Prompt β what it was told"
- Andits response, an agent's diff, and a command's output render verbatim, never summarized
- Andany transition at that step shows the basis that earned it, quoted verbatim
- Andtelemetry (duration, tokens, model) renders as one dim secondary row, never the headline
- Andan "Open full report βΈ" link goes to the run page for that cycle
ScenarioThe node inspector dismisses like any honest popover (operator rule)
- Giventhe node inspector is open
- Thenclicking or tapping anywhere outside it closes it
- Andpressing Escape closes it
- Andthe Close button uses the shared contrast-checked style β never light-on-light
- Andthe panel scrolls inside itself when the evidence is long
- Andunder 600px wide it renders as a bottom sheet
ScenarioPulls, wipes, and merges are drawn from the record
- Theneach attempt renders as a parchment "Pull N" tab with its status
- Andeach wipe renders as a skull whose tooltip names the recorded cause and the step it happened at
- Anda shipped branch ends in a merge gem on the gold trunk
ScenarioBosses and raids frame the branches
- Theneach boss gets a tinted band with a segmented HP bar β one segment per phase ticket, drained when that ticket shipped
- Andeach raid gets a frame with bosses-defeated, pull, and wipe badges
- Andthe raid strip across the top lists raids and bosses in order, and clicking a boss pans to its band
ScenarioLive runs move the canvas honestly
- Givena branch is running
- Thenthe canvas polls every 2 seconds (10 seconds when idle)
- Andfollow-live keeps the newest current node centered until I pan, scroll, or turn it off
- Anda running branch plate counts down its ETA between polls
ScenarioCelebrations fire only on real recorded changes
- Givena poll shows a branch flipped to shipped
- Thena gold "β SHIPPED" banner shows and the trunk shimmers
- Givena poll shows a new wiped pull
- Thena blood vignette flashes and the wipe banner quotes the recorded cause
- Anda victory landing while the tab is hidden badges the tab title with π
ScenarioA new world starts empty and says so
- Givena world with no branches
- Thenthe canvas shows the trunk, a hearthstone marked "World created", and the hint to post the first quest
Armory & achievements β progression earned by real runs
armory.featureAs a playerI want XP, achievements, and loot that trace to recorded runs on their own screenSo that progression is a scoreboard of reality, not a slot machine buried in another page
ScenarioThe Armory is its own screen with the derived progression stated
- Then/world/[slug]/armory is titled "Armory & Achievements" and shows nothing else
- Andthe panel headline reads "Level N raid leader Β· N XP Β· earned/total achievements"
- Andthe lead paragraph states everything shown derives from this world's real recorded runs
- Andthe panel polls the progression API every 10 seconds
ScenarioThe XP ledger names why every point exists
- WhenI expand "Where the points came from"
- Theneach ledger row shows the event's plain label saying why the points exist
- Andeach run-earned event links to the run that earned it
ScenarioThe clean-ship streak derives from recorded fights
- Thenthe XP row shows "π₯ streak: N (best M)" with the plain tail "shipped tickets in a row with no wipes"
- Andthe streak resets when a shipped ticket's fight wiped or a ticket stalled
- Andthe character sheet shows the same streak
ScenarioAchievements show earned and locked honestly
- Thenthe grid shows every achievement in the catalogue
- Andearned ones render bright with π and a link to the run that earned them
- Andunearned ones render dimmed with π and the note "not earned yet"
ScenarioBoss drops are minted from real fights
- Givena boss whose last ticket shipped
- Thenthe boss dropped exactly one item, minted from that real fight
- Andthe item card shows its rarity in the rarity color, its slot, and which boss dropped it
- Anda plain note explains the item, and a link opens the fight that dropped it
ScenarioItem stats are declared cosmetic
- Thenitem stats like +Vigor and +Haste are labeled as cosmetic game flavor computed from real fight metrics
- Andthey are never presented as real telemetry
ScenarioSet-tier drops join their raid's named set
- Givenan item that rolled the teal "set" rarity
- Thenits card shows "Set piece β <set name>" where the set name derives deterministically from its raid (for example "Vestments of <raid>")
- Anda Named sets card lists each set's pieces collected
- Andcollecting 2 pieces of one set grants the line "+Morale (set bonus β cosmetic)"
- Andcollecting 3 pieces also grants "+Camaraderie (set bonus β cosmetic)"
- Andevery set bonus line says it is cosmetic β a set bonus never changes the rules
ScenarioEquipping a drop
- Givenan unequipped item
- ThenI can pick a wearer β me as Raid Leader, or any party member by job title β and equip it
- Andthe wearer's row in the party frame gains a rarity-colored badge dot
- Andan equipped item shows its wearer and offers Unequip
- Andan equipped card is visibly marked β a gold glow and an "β equipped" corner chip
- Anda refused equip shows the API's error next to the item
ScenarioNo drops yet is stated plainly
- Givenno boss in this world has fallen
- Thenthe panel explains a boss drops one item the moment its last ticket ships
Run report β replay exactly what the runner recorded
runs.featureAs an auditor of an AI-driven cycleI want every transition, check, command, and diff shown with the evidence that earned itSo that no claim in the game survives without a recorded basis
ScenarioThe runs index lists every recorded cycle honestly
- Thenruns list newest first with title, short cycle id, and "β completed", "cancelled", or the real final state
- Andeach row shows its agent-build count, a "tests green" badge only when tests really ran green, and the model-call count
ScenarioRunning rows carry a runner-health chip with the same verdicts as /api/game/health
- Givena cycle that is not done, not cancelled, and silent for less than the game's live horizon
- Thenits row shows "healthy" while transcript output is fresher than 90 seconds
- And"silent Ns" β the measured silence in seconds β past 90 seconds
- And"runner dead β its process is gone" when the recorded pid no longer exists, even if the file looks fresh
- Androws past the live-silence horizon show no health chip β the run is already derived abandoned
ScenarioThe header states how far the game database trails the transcripts
- Thena "game sync" chip says "up to date" or "N rows behind" (tracked cycles only)
- Andstandalone transcripts the game never ingests are not counted as lag
ScenarioA stuck run can be cancelled, honestly
- Givena running cycle in a non-production environment
- Thenits row and its run page offer "β Cancel", which arms and asks before killing anything
- Andconfirming kills the recorded runner pid (the detached process group when possible)
- Anda CANCELLED note is appended to the transcript stating exactly what was killed, or that nothing was
- Andthe game layer derives the pull abandoned immediately β a fresh cancel note never masquerades as in progress
- Andthe run page header shows "β cancelled by the operator" and stops polling
Scenario"Run it back" relaunches with the recorded ask
- Givena run whose transcript records the original ask verbatim
- Then"β» Run it back" launches a fresh guided cycle with exactly that ask and jumps to the new run
- Anda transcript with no recoverable ask is refused rather than relaunched from a guess
- Andat the WIP cap the retry answers 429 and the controls show the refusal verbatim ("wait for one to finish")
- Anda cancelled run frees its WIP slot immediately β a fresh cancel note never holds capacity
ScenarioStarting a guided run is a dev-only door to a real cycle
- Givena non-production environment
- Thena form offers "Run it for real" for a plain-words ask
- Andthe hint says a real coding agent will build it locally over several minutes
- Andsubmitting jumps straight to the live run page
ScenarioThe run page discloses what the run builds against (operator rule)
- Givena guided run for a world with NO bound local repository
- Thenthe lede states the run builds a throwaway demo project in a temporary folder on this machine
- Andthat nothing is pushed to any repository or deployed anywhere
- Givena guided run for a world BOUND to a local git repository
- Thenthe lede states the run builds in that repository, naming its absolute path
- Andthat its commits land on that repo's current branch and stay there β they are the deliverable
- Anda "workspace.bound" check row records the validated binding (clean checkout, branch) or the refusal verbatim
ScenarioA bound repository that fails its contract refuses the run, fail closed
- Givena world whose bound path is missing, not a git repo, not the repo root, or has uncommitted changes
- Whena run is launched for it
- Thenthe run records an ok:false "workspace.bound" check naming the exact refusal (a wipe)
- Andthe runner exits without ever falling back to a temp dir silently
ScenarioEvery state row answers "what earned this?" with a basis badge
- Givena transition with recorded evidence
- Thenits row shows "proof Β· <kind>" (ai, command, agent, or check) with the evidence detail verbatim
- Givena policy step with no evidence to have
- Thenits row shows "runner policy" with the declared reason
- Givena stage promoted despite a failed gate
- Thenits row shows "unproven" β an explicit admission, never a disguise
- Givena transcript recorded before bases existed
- Thenits row shows "no recorded basis" rather than borrowing proof
ScenarioCheck rows are the turn contracts firing, and they fail closed
- Thenevery agent input/output validation renders as a "check β" or "check β" row with its message
- Andonly an explicit ok:true renders as a pass β a malformed check row renders as a failure
- Anda failed check is recorded and counts as a wipe in the game layer
ScenarioEvery failed check and wipe-causing row carries a Forensics expander
- Givena row the game layer counts as a wipe (failed check, FAIL/WOBBLE transition, red tests where green was required, or a failed agent)
- Thena "π©Ί Forensics" expander assembles the whole failure story in one place, led by a plain-language sentence
- Andit shows what failed (the check's own message, or the derived wipe cause)
- Andthe output behind it β the tail of the nearest prior agent/model/command output (or the failing row's own)
- Andthe repair attempt β the next agent row after the failure β and the retry result with an honest pass/fail
- Andwhen no repair was recorded it says so plainly instead of hiding the section
ScenarioCommand rows prove tests by count, not exit code
- Theneach command row shows the command, exit code, duration, and β for test runs β how many tests executed
- Anda run with exit 0 but 0 tests ran is flagged, because the test runner exits clean on zero discovered tests
- Andfailure output is expandable verbatim under the row
ScenarioModel and agent rows expand to the raw record
- Thena model call shows its role, model, outcome, token counts, and latency, with the full prompt and response expandable
- Andresponses render rich β JSON as a collapsible tree, prose as markdown
- Andan agent row shows the instructions it was given, the diff it produced with changed files, and its own summary
ScenarioThe stage ladder tracks the run left to right
- Thena ladder shows Decide, Design, Slice, Build, Assure, Verify, Ship, Learn
- Andcleared stages glow, the current stage pulses "now", and unreached stages stay locked
- Andclicking a reached stage jumps to its evidence below
ScenarioA live run replays in real time
- Giventhe cycle is still running
- Thenthe page polls every 2 seconds and the newest step is marked "workingβ¦"
- Andthe header shows "β running β <phase>" until the recorded transcript completes
ScenarioThe Ship phase presents the final deliverable
- Givena design that produced N slices
- Thenthe run builds all N slices β the build region loops once per slice, never just the first
- Andthe Ship phase itemizes per-slice outcomes and names the delivered artifact
- Andwhen nothing renders it states plainly there are no pixels to record β the deliverable is the code plus its proofs in App View
ScenarioA renderable deliverable ships as real pixels
- Givena delivered workspace containing a renderable artifact β a top-level .html file, or a slice module exporting render()/toHTML() (detected by a real node probe, never by guessing)
- Thenthe runner captures it with a headless Chromium: a .webm scroll recording when the page is taller than the viewport, else a paint-guaranteed full-page .png screenshot
- Anda motionless page never records as a blank video β Chromium's screencast emits frames only on damage, so static pages ship the screenshot and a suspiciously tiny recording is rejected for the screenshot
- Andstores the capture next to the transcript as <cycleId>-deliverable.webm|png and cites its file, type, and byte size verbatim in a transcript row
- Andthe run page's Ship section renders the capture inline via the path-confined /api/runs/<cycleId>/deliverable route
- Andwhen the artifact is renderable but no renderer is available, a "DELIVERABLE NOT CAPTURED" note records the concrete reason β nothing visual is ever faked
ScenarioA recorded evidence video plays when one exists
- Givena committed evidence video for this cycle
- Thenthe run page shows it captioned as recorded live and unedited
ScenarioA keyboard user can read a diff that overflows its block
- Givena step whose recorded diff is taller than the block's 360px max-height
- Thenthe diff block is focusable, so it can be scrolled to its last line without a mouse
- Andthe same holds for a scrollable code fence in any rendered transcript markdown
ScenarioThe run report's verdicts are readable, not just coloured
- Thenthe "β RED" and "β GREEN" test-run badges meet the 4.5:1 AA contrast floor against their own wash
- Anda diff's metadata lines meet it against the diff block's dark background
- Andthis is enforced by an axe scan of the running page, which fails the build closed on a regression
Lifecycle β the real state machines, drawn with their inline documentation
lifecycle.featureThe /lifecycle page renders the ACTUAL machines that drive the factory βnever a hand-drawn diagram that can drift. Every state and transitiondescription is collected from the machine definition itself (G1), and thedrift gate holds the allowlist empty.
ScenarioBackground
- GivenI open /lifecycle
ScenarioThe ticket lifecycle renders from oneFinalFlow
- ThenI see the SDLC lifecycle statechart derived from the real oneFinalFlow machine
- Andhovering a state shows the documentation written inline in that machine
ScenarioThe campaign (ask) machine renders with its real documentation (G1's owed surface)
- ThenI see the "Campaign (ask) machine" section
- Andits statechart is derived from the real sdlcAskMachine definition
- Andthe section explains in plain words what the campaign spine does
ScenarioThe slice (sub-ticket) machine renders with its real documentation
- ThenI see the "Slice (sub-ticket) machine" section
- Andits statechart is derived from the real sdlcSliceMachine definition
- Andthe section says this is the machine each slice walks inside a campaign
Game guide β the in-app rulebook
guide.featureAs a new player or a skeptical readerI want every game concept explained in plain language with the honesty contract spelled outSo that I can understand any surface without decoding the flavor first
ScenarioThe guide home is a chapter nav
- WhenI open /guide
- ThenI see one card per chapter with an icon, a chapter number, a title, and a plain one-line tagline
- Andthe world page links here as the "π Rulebook"
ScenarioChapters are navigable in order
- WhenI open a chapter
- Thena breadcrumb names the chapter and links back to the guide home
- Anda pager offers the previous and next chapters
- Andan unknown chapter slug renders the not-found page
ScenarioThe honesty contract is written down where players can read it
- WhenI open "What is this game?"
- Thenit states that every transition carries a basis, that wipes are never hidden, and that nothing is simulated or injected
- Andit states what a run builds against: a throwaway demo project in a temp folder for an unbound world, or the world's bound local repository β where the commits ARE the deliverable β when one is set
ScenarioThe party chapter cannot drift from the pipeline
- WhenI open "The party, in marching order"
- Thenthe turn cards are generated from the same AGENT_TURNS registry the runner validates against
- Andeach card leads with the plain job title, shows takes/produces, and names who actually acts (model call, coding agent, or the pipeline)
ScenarioUnshipped mechanics say "coming soon"
- Givena chapter describing a designed-but-unshipped mechanic
- Thenthe mechanic is visibly marked "coming soon"
- Andthe guide's stated rule is: if a mechanic is not marked shipped elsewhere in the app, it is not shipped
ScenarioThe user-stories chapter renders these very files
- WhenI open the "User stories" chapter
- Thenthe Gherkin feature files under docs/design/user-stories are read from the repository and rendered
- AndFeature, Scenario, and Given/When/Then keywords are visually toned
- Andevery scenario tagged @planned carries a visible "planned" badge meaning designed but not built yet
Tooltips β detail on demand that never hides the truth
tooltips.featureAs a reader of any game surfaceI want tooltips that add depth without ever being the only place meaning livesSo that every surface reads at a glance and hovering only enriches it
ScenarioNo meaning is locked behind a hover (operator rule)
- Thenevery label, log line, and card leads with a matter-of-fact description visible without hovering
- Andtooltips repeat and enrich that meaning β they are never its sole carrier
ScenarioTooltips never obscure or wash out the content beneath (operator rule)
- Thenthe shared tooltip is offset from the cursor so it does not sit over the hovered element
- Andit ignores pointer events so it can never trap the mouse
- Andit is clamped to the viewport, flipping to the other side of the cursor at the edges
ScenarioOne shared tooltip follows the cursor
- WhenI move the mouse across hoverable elements
- Thena single cursor-following tooltip updates its content per element
- Andit disappears when the pointer leaves
ScenarioTooltips quote the record instead of rephrasing it
- Givena tooltip that shows run evidence (a member's last result, a beat's detail, a node's given/did)
- Thenthe excerpt is rendered verbatim in quotes, never paraphrased
ScenarioTouch devices can pin tooltips on the worlds grid and the quest board
- Givena coarse pointer on the worlds grid or the quest-intake dialog
- WhenI tap a tooltip-bearing element
- Thenthe tooltip pins near the tap instead of following a cursor
- Andon world cards the first tap pins and the second tap navigates
ScenarioTouch devices can pin tooltips on the party frame and character sheet
- Givena coarse pointer on the party frame or the character sheet's equipped chips
- WhenI tap the raid leader row, a party member row, or an equipped-item chip
- Thenthe tooltip pins near the tap instead of following a cursor
- Andtapping the same element again dismisses it
ScenarioTooltips are exposed to assistive tech
- Thenthe shared tooltip renders with role="tooltip"
Is the SDLC truly working? β the operator's primary journey
campaign-health.featureAs the operator of an autonomous factoryI want one surface that tells me whether the machine is moving, stuck, ordrifting β with the proof, the blockers, the action I owe, and the ETASo that I never have to read a transcript to learn my factory died an hour agoA dashboard of green counters is exactly how a stalled campaign hides. Everyclaim on this panel is COMPUTED from real recorded rows (transcript, ledger,design stores, a live HTTP fetch of the app) and carries the evidence thatearned it. An unproven stage reads NOT DONE with the reason β never blank,never assumed.
ScenarioBackground
- Givena world whose campaign has run at least once
- WhenI open the world hub
ScenarioThe verdict is the first thing on the page
- Thenthe status panel renders above the live app and the quest log
- Andit shows one of MOVING, WORKING, STUCK, DRIFTING, BLOCKED, DONE, IDLE
- Andbeside the verdict is its meaning in plain words
- Andbeneath it a one-sentence headline says what happened, in plain words
- Anda "Because:" line quotes the evidence that earned the verdict
ScenarioMOVING β work is landing
- Giventhe newest transcript row is less than 12 minutes old
- Thenthe verdict is MOVING
- Andthe headline states how many minutes ago work landed
ScenarioWORKING β quiet is not the same as stalled
- Givenno row has landed for 20 minutes
- Buta driver or slice process for this campaign is alive
- Thenthe verdict is WORKING, not STUCK
- Andthe because line states that the driver process is alive
ScenarioSTUCK β nothing is alive and nothing has moved
- Givenno process for this campaign is alive
- Andnothing has landed inside the freshness window
- Thenthe verdict is STUCK
- Andthe "What YOU need to do" box names relaunching the campaign driver
ScenarioDRIFTING β motion is not progress
- Giventhe same step has failed 3 or more times in the recent window
- Thenthe verdict is DRIFTING
- Andthe headline names the failing step
- Andthe action explains that the same failure is repeating
- Buta single failure that was repaired is NOT drift
ScenarioWhat progress has truly been made
- Thena progress cell shows shipped-of-total tasks with a percentage
- Andit shows how many are running, failed, and stalled
- Andit shows how long ago the last activity was recorded
ScenarioHow fast are we going and how long will it take
- Thena speed cell shows tasks per hour measured over a stated window
- Andan ETA derived from that rate when the rate is above zero
- Butwhen nothing shipped in the window NO ETA is given
- Andthe note says an estimate from zero throughput would be a guess
ScenarioProof that each stage really happened
- Thena proof list covers the UX research brief, the reviewed PRD, the
- milestone/epic/story/task tree, the design critiques, the hi-res mockups,
- and an app a human can open
- Andeach line is marked done or NOT done
- Andeach line states the evidence, or the reason it is absent
- Anda persisted PRD with no passing critique reads NOT done as "unreviewed"
ScenarioWhat is blocking it
- Thena blockers cell lists stalled tasks, failed tasks, open Watchtower
- sightings, and a bound app that does not render
- Andwhen nothing is recorded as blocking, it says so plainly
ScenarioA broken app is a blocker with an action, not a status dot
- Giventhe campaign has shipped at least one task
- Andfetching the app's page returns a framework error page
- Thenthe blockers list says the app does NOT render, quoting the failure
- Andthe action tells me to restart it from the world page
ScenarioA campaign still in design is never called broken
- Giventhe campaign has shipped no tasks yet
- Andits app server is stopped
- Thenthe app proof line reads NOT done, "expected before the first delivery"
- Andthe app is NOT listed as a blocker
- Andno action is demanded of me
ScenarioThe plan and the grand vision, on demand
- Thena line states the milestone β epic β story β task counts
- Andit names the repository this campaign builds into
- Andpressing "Show the plan" reveals the original ask verbatim, in place
ScenarioNothing waiting on me is stated, not implied
- Givenno blocker demands an operator action
- Thenthe panel says "Nothing is waiting on you right now."
ScenarioThe panel refreshes itself
- Thenit re-reads the health API about every 20 seconds
- Andit never scrolls the page or steals focus while refreshing
SDLC monitor β the delivery hierarchy without the game
sdlc-monitor.featureAs an operator who wants status, not theaterI want a plain project β milestone β epic β story β task view with blockersSo that I can read what is delivered and what is blocked at a glance
ScenarioThe index answers what/what's-happening/what-you-can-do
- Giventhe local factory database has projects
- WhenI open /sdlc
- Thenthe page is headed "SDLC monitor" with a one-line description of the hierarchy
- Andeach project renders as a card with its plain name, slug, and a plain status
- Andeach card shows milestone, epic, story, and delivered-task counts
- Andeach card offers "Open project" to drill into its tree
ScenarioA project drills into its full tree
- Givena project card on the index
- WhenI open that project
- Thena header shows the project name, slug, status, counts, and any bound repo
- Andthe milestone β epic β story β task tree renders beneath it
- Anda "β All projects" link returns to the index
ScenarioPlain names lead, no RPG flavor
- Thenevery row leads with the plain tier noun (Project, Milestone, Epic, Story, Task)
- Andthe plain work title (the display* field, game prefixes stripped)
- Andno raid, boss, or other RPG flavor name appears anywhere on the surface
ScenarioStatus is plain language, derived from the real data
- Thena stored "cleared"/"defeated"/"shipped" renders as "Complete"/"Delivered"
- Anda stored "engaged"/"running" renders as "In progress"/"Running"
- Andthe raw stored status stays available as the chip's title for auditing
- Andno status is invented β every one comes from hierarchyForWorld
ScenarioBlockers render as plain chips on any tier
- Givenan entity that is blocked by another entity
- Thenits row shows a "β blocked by <name>" chip naming the blocker in plain words
- Andthe chip appears on whichever tier is blocked
ScenarioTasks are marked as the executing slice
- Givena task under an epic or story
- Thenits row is marked "slice β executes"
- Andit links to its run report when it has a recorded run
- Anda task filed before the story tier is marked "legacy task"
ScenarioA story with no tasks says it cannot execute
- Givena story that has not been broken into tasks
- Thenits row shows "β no tasks yet β not executable"
- Andit is never marked as an executing slice
ScenarioTiers collapse to keep the tree scannable
- Givena project tree with many milestones and epics
- Theneach milestone, epic, and story is a collapsible section
- Anda story's Gherkin acceptance text expands in place, or says "no Gherkin yet"
ScenarioNo local database is stated honestly
- Givena deployment with no local factory database
- WhenI open /sdlc
- Thenthe page says the database runs locally and shows no fabricated rows