- Status: accepted — owner batch acceptance 2026-09-25; implementing
- Author: Marco (drafted by Claude)
- Created: 2026-09-25
- Design refs:
design/03 §8(Demo Disc Arcade, Tier 0–1, host: the CRT — the evolving nostalgia container),design/03header (T0–1 minigames are tutorial-tier and free),design/03 §Clock taxonomy(session skill: short skill runs, cooldown-gated),design/03 §Unlock stagger(Tier 0: Demo Disc Arcade),design/03 §12a(dailies doctrine),design/03 §12b(scaling seam and Fairness Law),design/03 §5(2026-08-06 Soul ruling),design/08 §1(voice rules),design/08 §2(era 1995 and 2000 presentation),design/11 §1b,§2,§7(T0 satire beat, one hint per system, accessibility baseline),design/00(pillars and anti-goals),design/01 §Tier 0(theCRT),design/BACKLOG.md §Nostalgia arcade sweep - Depends on: Minigame Platform Foundation (accepted, implementing; C1–C40); Minigame &
Recovery API + Surface (accepted, implementing; MA-C1–C15, including the MA3 surface
components not yet built); The Pitch (archived, the template this RFC follows); UI Foundation and
Game UI Screens (archived); Soul Foundation (archived); Copy Pipeline (archived); First Content
Epoch (the
minigames/minigame_apiartifact chain). It also consumes the draft Accessibility of Player Workflows RFC as its accessibility floor if that RFC is accepted first. - Parent / amends: content on the Minigame Platform. It adds two tenants, one pinned content artifact, and four named platform/composition amendments (AR-P1–AR-P4). None of them changes a ruled platform grammar.
- Supersedes / superseded by: —
- Planning:
planning/minigame-demo-disc-arcade/(once accepted)
The Demo Disc Arcade is the Tier-0 minigame home. In 1995 it looks like the demo disc from a magazine cover, playing on the garage CRT. Design gives it three jobs: host tiny era-authentic toys, change with the era (cover disc → Flash portal → app store), and carry the tier-0 tutorial role. This RFC specifies:
- The container: its pinned stage manifest, unlock, Soul classification, economy stance, exclusivity with the rest of the game, and UI surface.
- The first two toys that design names: the Minesweeper-lineage deduction grid (engine
mine_grid) and a Snake (enginesnake). Both are specified the same way as The Pitch: deterministic pure engines over(seed, commands), Go/TypeScript byte parity, certified integer results, and content as exact-key data.
Design's third named toy, the shareware platformer demo that ends with "ORDER NOW: $0.00", is a declared successor. The later era stages and the rest of the nostalgia sweep are also successors.
The recommended economy stance follows design/03 §8's "pure nostalgia texture". The arcade pays
zero into the economy through a zero-rate faucet row and charges no offline quality. It
therefore moves no T0–T1 pacing number, and its rules and correctness can ship before any balance
tuning.
The platform has one real tenant, which sits at Tier 5. Tier 0 has no minigame, but
design/03 §Unlock stagger puts the arcade there. design/11 §1b names the arcade's ORDER NOW
screen as the ≤10-minute satire beat, and the onboarding research makes the arcade the place where
a player first learns that minigames exist. The capability ledger records the gap directly:
M-011.02 Play a real arcade tenant from the Demo Disc is marked client_or_fixture_only, and no
arcade tenant is playable.
The platform RFC also has an open question: do arcade toys need full sessions or a lightweight
score-send path? This RFC answers it: full sessions. A path where the client sends its own score
is a client-sent result, which violates the server-authority law (CLAUDE.md law 2). Full sessions
also keep the arcade ready for the later daily-slot ghost scores.
Out of scope: the platformer demo; T2–3 Flash-portal and T5 app-store stages and their toys (Solitaire/Boss Key, the match-3, block stacker, tower defense, plinko, the 2048-lineage daily slot); ghost scores and daily seeds; achievement hooks (DG-2); Soul restoration (DG-5); any payout magnitude; audio; canvas rendering.
AR1.1 Identity and host. The arcade is a Game UI surface named arcade, not a minigame. It
lists toys, and each toy is an ordinary platform minigame definition. The "host: the CRT" in
design/03 §8 is presentational: the Desk entry point wears the CRT. See DG-6 for the host/tier
mismatch.
AR1.2 The pinned arcade artifact. A new artifact balance/arcade.json (schema_version: 1)
holds the container manifest and the content for both toys. Its exact top-level keys are
{schema_version, container, mine_grid, snake}. It joins CatalogBundle as CatalogBundle.Arcade
under the Pitch precedent (TP-C2/TP-C14), with these rules:
- The canonical hash uses the established constants-hash construction.
- Session content identity lives in exact genesis keys
{arcade_content_hash, arcade_schema_version}. There is no database column. - Artifact chain:
arcaderequiresminigame_api, which already requires the full minigame content chain. The artifact is added tovalidArtifactNameswith the same hard-fail discipline.
container has one exact key, stages: a list of rows {stage_id, min_tier, title_copy_key, toys} sorted by strictly ascending min_tier.
toysis a byte-sorted list ofminigame_ids.- At bundle composition, every toy ID must resolve to a
minigamesdefinition whoseengine_refismine_gridorsnake. - v1 has exactly one stage:
{"stage_id":"cover_disc","min_tier":0,"title_copy_key":"arcade.stage.cover_disc.title","toys":["arcade.mine_grid","arcade.snake"]}. - The client shows the stage with the greatest
min_tier ≤ authoritative tier. - The stage is presentation. Server-side permission to start a toy comes only from that toy's
unlock_condition. Tier-gated later stages need a server-enforced unlock arm first (DG-4).
AR1.3 Era evolution under the UI laws. Platform C9 removed era_skins, and UI Foundation
allows one token theme per era, never per-feature skins. Era evolution is therefore expressed only
through:
- (a) the active stage row, which selects the toy list and title key;
- (b) the Copy Pipeline's existing
era_variantson arcade copy keys, so the same key reads as a 1995 cover disc or a 2000s download page; - (c) the global era theme tokens,
era_1995andera_2000.
Tier 1 keeps the cover_disc stage. The Flash portal (T2–3) and the app store (T5) are successor
stage rows (DG-4).
AR1.4 Unlock. Both toys use unlock_condition: {"kind":"always"} (OD-3), which matches
"Tier 0–1 minigames … are tutorial-tier and free". The epoch-6 Fiscal row
{"unlock_id":"unlock.arcade","cost":5} contradicts that line (finding AR-F2). This RFC does not
bind it, and OD-3 recommends retiring it in the arcade mint epoch.
AR1.5 Soul. Both toys declare soul_gate: "human_hobby" (OD-4). design/03 §8's "Hobby
framing applies" is implemented as the lock half only: near-zero Soul rejects start with the
shipped not_eligible/human_content_locked. Soul restoration is not implemented. The
2026-08-06 §5 ruling says paying hobbies never restore Soul and that §5c cozy activities are the
sole recovery source, and §8's parenthetical contradicts it. That contradiction is the
owner-authored RP-017, which this RFC must not resolve (DG-5).
AR1.6 Economy stance: texture only (OD-2). Every arcade definition carries:
- a zero-rate faucet row
{credited_resource_id:"company.cash", sends_per_day:0, per_send_cap:0, conversion_ppm:0, ...}; - an inert offline-quality row: one curve row whose grade equals the neutral floor, and zero decay;
- an inert neutral rating row, with certified
rating_delta: null(C40).
A resolution therefore credits exactly zero, forfeits exactly zero (so no cap reason appears), and leaves rating and offline quality unchanged. It still commits the ordinary resolution records (both logs, events, session receipt) through the shipped composer. This depends on AR-F1 being fixed, because the shipped composer currently fails closed on a zero credit.
AR1.7 Clock. design/03's clock taxonomy puts the arcade on "session skill, cooldown-gated".
The skill runs are the sessions. The platform's only cooldown is the daily faucet quota, and
nothing guards a zero faucet, so v1 has no play cooldown. A cooldown on play itself would be a
new platform mechanic (DG-3).
AR1.8 Exclusivity and Exit. Arcade sessions are ordinary platform sessions and inherit two rules:
- (a) one active minigame session per Founder. Starting any minigame while one is active rejects
not_eligible/exclusive_activity. - (b) MA-C12: while a session is
active|claimed, the Exit path rejectsnot_eligible/minigame_session_active. That path includescross_gate, which drives the scripted first-company ending (server/production/replay.go:629).
A T0 toy can therefore block the ~15-minute scripted collapse. Three mitigations are normative:
- Every arcade engine has a
quitcommand that reaches a terminal state from any non-terminal phase, so the player can always clear a session without playing it out. - The Desk renders the
minigame_session_activerejection with copy that names the arcade and links back to the running toy (AR6.5). - The arcade entry shows when a session is active.
AR1.9 Persistence across Exits (design/03 rule 5). A session cannot cross an Exit (AR1.8b).
Rating and offline-quality maps persist but stay neutral. An optional local-only best-score
display may be kept (OD-11). It follows the gate-splits precedent (docs/game-ui.md): a client
display record that never feeds an intent, receipt, or board.
AR1.10 AI fallback. Both toys use fallback: {"kind":"solo"}; solo play is the design. Design's
"daily-slot ghost scores" belong to the T5 daily slot, a successor.
Two rows are added to the pinned minigames artifact (schema v3). Both copy the Pitch shape and
change only the literals below. All integers are provisional balance data unless the text marks a
value as structural.
{
"minigame_id": "arcade.mine_grid",
"engine_ref": "mine_grid",
"engine_version": "1.0.0",
"modes": ["solo"],
"result_score_fact_ids": ["mine_grid.cells_revealed", "mine_grid.cleared"],
"scaling": {"schema_version": 1, "scaling_inputs": [
{"destination": "minigame.arcade.mine_grid", "destination_class": "breadth", "source_kind": "literal",
"source_ref": "1", "op": "identity", "operand": 0, "clamp_min": 1, "clamp_max": 1}]},
"payout": {"credited_resource_id": "company.cash", "sends_per_day": 0, "per_send_cap": 0,
"conversion_ppm": 0, "payout_score_fact_id": "mine_grid.cells_revealed",
"cap_reason_key": "cap.minigame_faucet"},
"fallback": {"kind": "solo"},
"offline_quality": {"score_fact": "mine_grid.cells_revealed",
"grade_curve": [{"score_threshold": 0, "grade_ppm": 200000}],
"decay_grid_ms": 3600000, "decay_ppm_per_grid": 0, "neutral_floor_ppm": 200000,
"automation_destination": "minigame.arcade.mine_grid"},
"rating_policy": {"starting_elo": 1000, "elo_floor": 0, "elo_ceiling": 3000,
"provisional_games": 10, "season_member": "s1"},
"unlock_condition": {"kind": "always"},
"soul_gate": "human_hobby"
}The arcade.snake row is identical except for:
engine_ref: "snake";result_score_fact_ids: ["snake.food_eaten", "snake.ticks_survived"];- payout and quality
score_fact:snake.food_eaten; - scaling destination and automation destination:
minigame.arcade.snake.
The rows sort byte-wise (arcade.mine_grid < arcade.snake < pitch). The only scaling input is
the literal breadth 1, so the Fairness Law holds trivially.
AR3.1 Content (arcade.json#/mine_grid). Exact keys: {presets}. The rows are
{preset_id, width, height, mines, copy_key}, byte-sorted by preset_id. Loader bounds are
5 ≤ width ≤ 30, 5 ≤ height ≤ 30, and 1 ≤ mines ≤ width*height − 9. The v1 rows
(provisional, OD-12) are:
large {30, 16, 99}medium {16, 16, 40}small {9, 9, 10}
Their copy keys are arcade.mine_grid.preset.<preset_id>.
AR3.2 Coordinates. Cell index i = y*width + x, zero-based and row-major. Neighbors are the
up-to-8 cells at Chebyshev distance 1. Every list of cells is sorted ascending.
AR3.3 Randomness (no stored cursor). The engine uses these streams:
run_seed = Substream(seed, "mine_grid.run.v1").Next()- mine stream:
Substream(run_seed, "mine_grid.mines.v1")
Mines are placed lazily at the first reveal, as follows:
- Build the ascending eligible list: every cell except
first_celland its neighbors. - Shuffle it with the shipped descending Fisher–Yates: for
kfromn−1down to 1, drawj = Bound(k+1)using the rejection-sampleddeterminism.SplitMix64.Bound, then swap. This is byte-identical to Pitch'sdeal. - The mines are the first
minesentries after the shuffle, sorted.
Because placement is a pure function of (seed, preset, first_cell), the mine set is never
stored. The engine re-derives it on every Apply.
AR3.4 Snapshot (exact thirteen keys).
{phase: "setup"|"playing"|"terminal", preset_id: string|null, width, height, mines,
first_cell, revealed: [{cell, adjacent}], flags: [cell], exploded_cell, mine_cells: [cell],
revision, arcade_content_hash, arcade_schema_version}
- Genesis values:
phase:"setup",preset_id:null,width/height/mines: 0,first_cell: -1,exploded_cell: -1, and empty lists. - Hidden-information invariant:
mine_cellsis[]in every non-terminal snapshot. The snapshot is the API response (MA-C7) and the stored platform state, so it must never carry mine positions before the game ends. At terminal it holds every mine, sorted.
AR3.5 Commands (closed union, discriminator kind).
| Command | Legal phase | Effect |
|---|---|---|
choose_board {preset_id} |
setup |
Copies the preset dimensions and enters playing. |
reveal {cell} |
playing |
Rejects with cell_flagged or cell_revealed. If first_cell = -1, sets it and places mines (AR3.3). A mine sets exploded_cell = cell, and the game ends detonated. Otherwise the engine flood-reveals (below). |
toggle_flag {cell} |
playing |
Rejects with cell_revealed. Otherwise toggles membership in flags. There is no flag cap. The display shows mines − len(flags), which may go negative, as in the lineage. |
chord {cell} |
playing |
Requires cell to be revealed with adjacent > 0 and flagged-neighbor count equal to adjacent; otherwise rejects chord_unsatisfied. Reveals every unflagged hidden neighbor. If any of them is a mine, the game ends detonated with exploded_cell = the smallest such index. Otherwise each neighbor flood-reveals. |
quit {} |
setup, playing |
Terminal quit. |
Flood reveal. The revealed set is the closure from the start cell: reveal each cell with its adjacent-mine count. If the count is 0, add every hidden, unflagged neighbor. Flagged cells are never auto-revealed. The result is order-independent, and the stored list is sorted.
Win. When len(revealed) = width*height − mines, the game ends cleared.
Every applied command advances the revision by exactly one. A rejected command mutates
nothing. cell must satisfy 0 ≤ cell < width*height, or the command rejects
cell_out_of_range.
AR3.6 Terminal result. The terminal transition writes phase:"terminal", fills mine_cells,
and emits:
outcome: one ofcleared | detonated | quit;- score facts, byte-sorted:
mine_grid.cells_revealed(safe cells revealed) andmine_grid.cleared(1only oncleared, otherwise0); rating_delta: null.
AR3.7 Rejection taxonomy (sorted). cell_flagged | cell_out_of_range | cell_revealed | chord_unsatisfied | illegal_phase | unknown_preset.
AR4.1 Content (arcade.json#/snake). Exact keys, all provisional (OD-13):
| Key | v1 value | Loader bound |
|---|---|---|
width |
20 | 5–30 |
height |
20 | 5–30 |
start_length |
3 | 2 to width/2 |
growth_per_food |
1 | 1–8 |
max_ticks_per_advance |
64 | 1–256 |
presentation_tick_ms |
125 | 50–1000 |
presentation_tick_ms is presentation-only. The Go engine validates its range and never reads
it. Walls are solid, with no wraparound.
AR4.2 Randomness.
run_seed = Substream(seed, "snake.run.v1").Next()food_seed = Substream(run_seed, "snake.food_seed.v1").Next()
food_seed is published in the snapshot as a canonical decimal string (OD-5). Food number
k goes on the ascending list of empty cells at index
Substream(food_seed XOR uint64(k), "snake.food.v1").Bound(len(empty)).
AR4.3 Snapshot (exact fourteen keys).
{phase: "playing"|"terminal", tick, direction: "up"|"down"|"left"|"right", body: [cell]
(head first), pending_growth, food_cell, food_index, food_seed, score, width, height,
revision, arcade_content_hash, arcade_schema_version}
Genesis:
- head at
(width/2, height/2)(integer division), with body cells extending west tostart_length; direction: "right",tick: 0,pending_growth: 0,score: 0;- food 0 placed, then
food_index: 1.
AR4.4 The tick step (tick t = tick + 1, in this exact order):
- Turn. If a turn is scheduled at
t, it becomesdirection. A turn must be a 90° change; the same direction or the opposite direction rejects the whole command withinvalid_turn. - Move. Compute
next = head + delta. Ifnextis out of bounds, the game endscrashed. - Tail.
growing = pending_growth > 0, andkept = growing ? body : body[:-1]. The tail cell that is leaving is free this tick. Ifnext ∈ kept, the game endscrashed. - Commit.
body = [next] ++ kept. Ifgrowing, decrementpending_growth. - Food. If
next = food_cell: add 1 toscoreandgrowth_per_foodtopending_growth, then place foodfood_index(AR4.2) and incrementfood_index. If no empty cell remains, the game endsclearedandfood_cellbecomes -1. tick = t.
On crashed, tick is the death tick and the body is left unmoved.
AR4.5 Commands.
advance {through_tick, turns: [{tick, direction}]}- Requires
tick < through_tick ≤ tick + max_ticks_per_advance; otherwiseadvance_window. - Every turn tick must lie in
(tick, through_tick]; otherwiseadvance_window. - Turn ticks must be strictly ascending; otherwise
turns_not_ascending. - The engine simulates ticks in order. If a terminal state occurs at tick
d, the command must havethrough_tick = dand no turn afterd; otherwise it rejectsadvance_past_terminalwithout mutation. An honest client simulating locally always submits exactly up to the terminal tick. The server never silently discards input.
- Requires
quit {}gives terminalquit.
AR4.6 Terminal result.
outcome: one ofcleared | crashed | quit;- facts, byte-sorted:
snake.food_eaten(score) andsnake.ticks_survived(tick); rating_delta: null.
AR4.7 Rejection taxonomy (sorted). advance_past_terminal | advance_window | illegal_phase | invalid_turn | turns_not_ascending.
AR4.8 Wall-time trust. Tenants are pure and receive no clock. The server certifies an input schedule, not the real time it took to play, so a scripted client could play "slow-motion" Snake. With zero payout, no boards, and no ghosts this changes nothing (OD-6). Any future board, ghost, or payout on Snake needs a server-stamped pacing contract as a platform successor.
- AR-P1 —
CatalogBundle.Arcade. Artifact name, path, and loader chain as in AR1.2, with a loader in both Go and TypeScript that passes one shared fixture. Cross-artifact checks at composition cover the stage toys, the engine refs, and copy-key existence. - AR-P2 — content resolver generalization.
ReplayCatalogSet.ResolveTenantContentcurrently accepts onlypitch(server/production/replay.go:114). It becomes a closed table:(pitch, 1.0.0) → pitch,(mine_grid, 1.0.0) → arcade,(snake, 1.0.0) → arcade. Unknown pairs still resolve nothing. - AR-P3 — start precondition generalization.
StartMinigameAPISessionrequiresbundle.Pitch != nilfor every minigame (server/production/minigame_start.go). That becomes "the definition's tenant content resolves through AR-P2". Pitch keeps its present behavior, and an arcade start no longer depends on the Pitch artifact. - AR-P4 —
minigame_apirows and API arms. Theminigame_apiartifact'stenantsgains{mine_grid,1.0.0,arcade.mine_grid}and{snake,1.0.0,arcade.snake}. These are new artifact bytes, minted with AR8. Following MA-C7/MA-C10, the generated API adds two discriminated snapshot arms and two command arms. The closed rejection-detail enum adds the AR3.7/AR4.7 details under the same{status, category, detail}mapping that Pitch's tenant rejections use. All of this is additive-only under the v1 compatibility baseline. - AR-P5 — client tenant-surface registry (MA-C9) registers two children keyed by
(engine_ref, engine_version). It is blocked on the MA3minigame_sessionsurface being implemented.
AR6.1 Surfaces. A Game UI surface row {surface_id:"arcade", mount_id:"game.surface.arcade", unlock:{kind:"always"}}. Its closed states are:
menu: the active stage's toys, each with a title, a description, and a Play control.session: mounts MA3minigame_sessionwith the tenant child.blocked: another minigame is active; shows copy plus a control that returns to that session.
The Desk gets one entry control, which wears the CRT. The first time the arcade is opened, it shows
exactly one contextual hint (design/11 §2; arcade.hint.first_open), and nothing else
teaches. On mount, the surface calls GET …/sessions/current and reopens an active arcade
session. The arcade uses DOM only, with no canvas (design/06; Game UI "no canvas in Phase A").
It has no audio in v1.
AR6.2 mine_grid child.
- Structure. A
role="grid"of buttons with roving tabindex. Each cell's accessible name comes from copy keys: position, then hidden / flagged / revealed with its adjacent count. - Keyboard. Arrow keys move. Enter or Space acts in the current mode.
Ftoggles a flag, andCchords. - Pointer and touch. A visible Reveal/Flag mode toggle replaces right-click and long-press, so no action depends on a pointer-only gesture (Accessibility A2.1). Targets are at least 24 CSS px, and larger boards scroll inside a named two-axis container (A3.1's two-dimensional exception).
- Non-color state. Every state has a digit or glyph plus text; the numbers are never hue-coded only.
- Announcements. One polite live-region announcement per command response ("N cells revealed", outcome). Cascades are never announced cell by cell.
- No timer. The certified result has no time, so the grid shows none. A display timer would imply a scored clock that does not exist.
- The client renders server snapshots only and never simulates (MA3).
AR6.3 snake child.
- Board. A DOM grid. Steering uses arrow keys, WASD, and a visible four-button D-pad (at least 24 px; 44 px recommended).
- Pause.
P/Escand a visible Pause button pause the game. It also auto-pauses onvisibilitychange→hidden, window blur, and focus leaving the board. Pausing is fair by construction (AR4.8): the server never reads wall time. - Local simulation. The client runs the shared TypeScript engine locally at
presentation_tick_ms. Every 16 ticks it flushesadvance(provisional, OD-13), and it flushes immediately on terminal. It keeps at most one command in flight (MA-C8). If its local lead reachesmax_ticks_per_advancewhile unacknowledged, it freezes the simulation. - Divergence. A rejected or diverged flush triggers
currentand a resync to the server snapshot, with thearcade.resync.noticestatus. The server snapshot is the only truth. - Pace. Where OD-7 is adopted: a local, labeled pace setting (100% / 75% / 50%) scales only
presentation_tick_ms. - Screen readers. Snake is inherently a real-time visual game. It is declared an optional
toy with a timed-input exception (Deviations D7), and the untimed
mine_gridbeside it is the accessible arcade path. The live region announces only food eaten (throttled) and the outcome.
AR6.4 Reduced motion, CRT effects, photosensitivity.
- Reduced motion (
prefers-reduced-motion, and the era_1995 zero-motion budget) removes every decorative transition: no tweening between Snake cells, no screen shake, no reveal cascade animation, and no explosion or death animation. - Snake's discrete one-cell steps are the game's content, not decoration, and they remain. Their pace control is OD-7.
- The same media-query path drives both CSS and component logic (A4.1). A CSS-only reduction fails.
- CRT effects (OD-8): v1 ships no CRT visual effect beyond a bezel built from the existing
era border and chrome tokens. Animated effects (flicker, rolling bar, glow pulse, curvature
warp) are prohibited in
era_1995by its zero-motion budget, and under reduced motion in every era. Any static scanline or curvature treatment requires a UI Foundation theme-schema amendment that adds governed tokens, never component literals. - Photosensitivity: no content flashes more than 3 times in any 1-second period, in any mode (WCAG 2.3.1), and there are no full-field red flashes.
AR6.5 Exit interplay copy. The Desk renders not_eligible/minigame_session_active from a Wind
Down or gate-crossing receipt with arcade.exit_blocked and a control that opens the running
session. This is a named Game UI consumer amendment.
AR6.6 Copy keys (mechanical; all prose owner-authored and pending, OD-15).
arcade.container.titlearcade.stage.cover_disc.titlearcade.hint.first_openarcade.toy.{mine_grid,snake}.{title,description}arcade.mine_grid.preset.{large,medium,small}arcade.mine_grid.mode.{reveal,flag}arcade.mine_grid.cell.{hidden,flagged,revealed}(paramsrow,column,adjacent)arcade.mine_grid.flags_remaining(paramcount)arcade.snake.dpad.{up,down,left,right}arcade.snake.pace.{full,three_quarter,half}(only if OD-7 is adopted)arcade.action.{play,pause,resume,quit,quit_confirm}arcade.outcome.{cleared,detonated,crashed,quit}arcade.blocked.session_activearcade.exit_blockedarcade.resync.noticearcade.error.rejected
T0 voice follows design/11 §1b: the earnest webmaster, warm and never mocking. The era-boundary
banned-word linter applies. Player-facing toy names must not use trade names or trade dress
(design/03 §8 legal rule). "Minesweeper" is a Microsoft title, and the owner names both toys.
A versioned arcade content-gate corpus is checked in, using a fixture artifact whose extra presets
and Snake dimensions (5×5) make the edge cases reachable. Each scenario is an exact
(seed, commands, expected terminal or intermediate snapshot).
mine_grid scenarios:
- every preset;
- first-reveal exclusion;
- a zero-cascade flood that stops at flags;
chordsuccess,chorddetonate, andchord_unsatisfied;- flag toggle on and off;
- a clear-board win;
quitfromsetupand fromplaying;- every rejection code.
snake scenarios:
- a crash into each of the four walls;
- self-collision;
- a legal tail chase;
- eat and grow;
clearedon the 5×5 board;- every rejection code;
- an
advancewhose terminal falls mid-window.
Go and TypeScript byte-compare every snapshot and result. The fixed transition budget equals the sum of corpus command counts. Content changes regenerate the corpus in a reviewable balance-change commit.
Fixture-first (TP-C18 option a). Engines, artifact, amendments, API arms, and surface ship
against fixture bundles. The production mint of arcade.json, the two definitions, and the new
minigame_api bytes is a later owner-gated epoch (a BALANCE-CHANGE: commit under the epoch
protocol), which also carries owner-adopted copy and the OD-3 Fiscal retirement.
Work slices, each reviewed separately:
- AR-P1–P3 plus the AR-F1 fix (platform-owned; must be red-first);
mine_grid;snake;- AR-P4 API arms;
- AR6 surfaces, after MA3.
- D1 — No Soul restoration despite
§8's "Hobby framing applies (Soul restoration)". This follows the later §5 ruling. RP-017 is the owner's to reconcile indesign/03(DG-5). - D2 — No play cooldown despite "cooldown-gated" (AR1.7). The zero faucet leaves nothing to govern (DG-3).
- D3 — Achievement hooks deferred (DG-2). The achievements catalog has no condition arm over minigame results.
- D4 — Only the
cover_discstage ships. The Flash portal and the app store are successors (DG-4). - D5 — CRT is a token-built bezel, not a per-feature skin. UI Foundation and platform C9 forbid per-feature skins (AR6.4, OD-8).
- D6 — Research, not design, says the arcade teaches that minigames "pay into the main economy"
(
research/onboarding-retention.md §2c). The recommended zero payout follows design's "pure nostalgia texture" instead (OD-2, DG-1). - D7 — Snake is a timed-input game. It conflicts with the draft Accessibility A2.2 ("no timed
key sequence") and is declared an optional-toy exception, mitigated by pause and the local pace
setting. The untimed
mine_gridis the accessible arcade path (OD-7). - D8 — Unlock is
always, not the epoch-6 Fiscalunlock.arcadepurchase (AR-F2). The Fiscal row deviates from design; this RFC follows design.
- DG-1 "The tier-0 tutorial hides here" (
§8) vsdesign/11 §2"No tutorial mode". Options:- (a) recommended: the arcade's tutorial role is the single first-open hint plus the act of playing, with no tutorial mechanic;
- (b) a small payout, so the arcade teaches "minigames pay" (needs harness evidence against the
[45,90]-minute first-Exit envelope); - (c) a dedicated tutorial toy (a
pitch.demo-style variant was already rejected in TP-C6).
- DG-2 Achievement hooks. Options:
- (a) recommended: an Achievements successor adds a
minigame_fact_at_least {minigame_id, fact_id, minimum}condition over theminigame_resolved.v1event; - (b) arcade-local cosmetic badges (a new system; not recommended).
- (a) recommended: an Achievements successor adds a
- DG-3 A play cooldown for session-skill toys. This would be a platform successor if a paying arcade toy ever needs it.
- DG-4 Later stages need, first, Game UI eras beyond tier 1 (currently fail-closed) and,
second, a server-enforced tier unlock arm.
fact_equalsis parsed byserver/minigame/catalog.gobut never evaluated at start: onlyfiscal_unlockand Soul are checked inminigame_start.go. - DG-5 RP-017 (Soul restoration across hobbies, Go, and the Arcade). This is owner-authored design text; the owner reconciles it.
- DG-6 Host tier.
design/01 §Tier 0lists aCRTgenerator at T0, but the shippedgenerator.crt_wallistier: 1, costs 2.48832e6, and is unaffordable in the first 15 minutes. Options:- (a) recommended: the host is presentational and the unlock is
always; - (b) unlock on owning
crt_wall(moves the arcade to late T1 and needs a new server unlock arm); - (c) retune the generator ladder (a balance mint).
- (a) recommended: the host is presentational and the unlock is
- AR-F1 (Minigame Platform, severity high): a zero-credit resolution fails closed and strands the
session.
- The chain, from reading the code:
economy.Ledger.ApplyAccrualomits unchanged balances fromReceipt.Changes(server/economy/ledger.go,if before.Eq(after) { continue }).server/production/minigame_resolution.gothen hitslen(ledgerReceipt.Changes) != 1and returns(MinigameResolutionDecision{}, ledgerErr)with a nil error.server/save/minigame_resolution.gorejects the empty receipt ("invalid minigame resolution receipt"), and the transaction rolls back.
- Every zero-credit resolution is affected. That includes Pitch after
sends_per_day(5) is exhausted on an attended day, and any payout whilecompany.cashis at its hardcap. The claimed session cannot resolve, so MA-C12 then rejects Exit. - This has not yet been executed. The fix must land red-first: a failing composed test for a 6th Pitch send, then the fix. This RFC's AC8 depends on it.
- The chain, from reading the code:
- AR-F2 (Fiscal / First Content Epoch):
{"unlock_id":"unlock.arcade","cost":5}is minted inbalance/fiscal/first-content.json. It originates from the fiscal-foundation fixture (53d1b4a) and contradictsdesign/03's free T0–1 arcade. It is currently unbound and unpurchasable (there is no Fiscal player surface). - AR-F3 (Game UI / Curriculum): MA-C12's Exit rejection also blocks the
cross_gatethat triggers the scripted first ending. Pitch (T5) never met this; a T0 toy does. AR1.8's quit command and Desk copy mitigate it. Whether the scripted ending should instead auto-quit an active session is a platform/curriculum question this RFC does not decide.
- Artifact loader. Go and TypeScript accept the fixture and the v1
arcade.jsonbyte-identically. Fails on: unsorted presets,mines > width*height−9, a stage toy with no definition or a wrongengine_ref, an extra key, or a non-ascendingmin_tier. Each of these must reject in both runtimes. - Definitions. Both rows load under schema v3. A policy test asserts that every minted arcade
row has
conversion_ppm = 0and an inert quality curve. Fails on: a fixture with nonzero conversion fails the test, and apowerscaling row fails load. mine_gridrules and parity. The AR7 corpus is byte-identical across Go and TypeScript. Fails on: each of these seeded mutants turns it red:- a 4-neighborhood flood;
- placement that ignores the first-reveal exclusion;
- a chord that reveals flagged cells;
- a TypeScript-only
Boundwithout rejection sampling.
- Hidden information. Every non-terminal snapshot in the corpus, and every non-terminal API
response in the composed test, has
mine_cells: []. No stored session state contains mine positions. Fails on: a mutant that stores or exposes mines early. snakerules and parity. The AR7 corpus is byte-identical across Go and TypeScript, and the client's local predictor is the same TypeScript module. Fails on: each of these mutants:- tail-chase counted as a collision;
- food drawn from all cells instead of empty cells;
- a reversal accepted;
- turns after the terminal tick silently discarded.
- Rejection without mutation. Every AR3.7/AR4.7 code is reached. Revision, command rows, and
the snapshot are unchanged. Fails on: a mutant applying a partial
advancebefore rejecting. - Platform amendments. The resolver returns arcade bytes for exactly the two new pairs, and
Pitch is unchanged. An arcade start succeeds in a bundle without the Pitch artifact but with
arcade. Fails on: removingarcadefrom the bundle makes the arcade start reject, and reverting AR-P3 makes the Pitch-less bundle reject. - Full composed path. For both toys, over a real socket: create, commands, then terminal
auto-resolution returning a receipt with credited
"0", no cap reason, and unchanged rating/quality. A retry returns identical bytes. Fails on: the pre-fix AR-F1 path, shown red first. - Exit interplay. With an active arcade session, Wind Down and
cross_gaterejectminigame_session_active. Afterquit, both succeed. Fails on: a build withoutquitleaves no player-reachable terminal fromsetup, and the test demonstrates that. - Soul gate. Near-zero Soul rejects the start
human_content_locked. Fails on: classifying the toyunrelatedpasses the start, so the test turns red. - API. The generated schema passes the additive-only compatibility baseline, and the new
details map exactly. Fails on: removing or renaming an existing arm breaks
make api-check. - Surface and accessibility. axe WCAG 2.2 AA shows zero serious/critical findings in
Chromium, Firefox, and WebKit for
menu,mine_grid(setup/playing/terminal), andsnake(playing/paused/terminal). A keyboard-only browser test completes amine_gridgame and a Snake quit. Reduced-motion tests assert zero decorative durations, no inter-cell tween, and live media-query switching. Fails on: a CSS-only reduced-motion mutant, a tween mutant, a pointer-only flag action, and a hue-only cell state. - Photosensitivity and CRT. No arcade component defines an animation in
era_1995, and a static check rejects component-literal CRT styles. Fails on: a seeded flicker keyframe. - Copy. Every AR6.6 key resolves with owner-adopted text, and the denylist contains the trade names. Fails on: a missing key, and a seeded "Minesweeper" string rejected by the linter.
- Fixture-first boundary. The live epoch manifest does not pin
arcadeuntil the AR8 mint. Fails on: a test that pins it without the mint record.
- OD-1 — v1 roster. Recommended:
mine_grid+snake, both named in§8. Alternatives:mine_gridonly (Snake deferred with OD-5–7); or also the platformer (not recommended: a realtime platformer is a much larger engine). - OD-2 — Economy. Recommended: zero payout and inert quality ("pure nostalgia texture").
Alternative: a small
company.cashfaucet, which requires harness evidence against the T0–T1 envelopes. - OD-3 — Unlock. Recommended:
always, and retire Fiscalunlock.arcadein the arcade mint epoch. Alternative: keep the 5-IC purchase (contradictsdesign/03). - OD-4 — Soul. Recommended:
human_hobby(lock only, no restoration) pending RP-017. Alternative:unrelated. - OD-5 — Snake transport. Recommended: batched
advancewith the publishedfood_seed. Alternatives: server-revealed food only, with the client flushing on every eat (visible rubber-banding at the round trip); or defer Snake. - OD-6 — Snake wall time. Recommended: no pacing validation while payout, boards, and ghosts are all absent. Any of those later requires a platform pacing contract.
- OD-7 — Snake accessibility. Recommended: ship as an optional toy with pause (always), a local pace setting (100/75/50%), and the D7 exception. Alternatives: no pace setting; or defer Snake until the Accessibility RFC rules.
- OD-8 — CRT visuals. Recommended: only the token bezel in v1. Alternative: a UI Foundation theme amendment for static scanline tokens. Animated CRT effects stay prohibited either way.
- OD-9 — Tutorial role (DG-1). Recommended: option (a).
- OD-10 — ORDER NOW placement. Recommended: it stays on the Desk where Game UI shipped it. The platformer successor owns the in-arcade "demo ends → ORDER NOW" frame.
- OD-11 — Best score. Recommended: a local-only display record (the gate-splits precedent), labeled as local. Alternative: none.
- OD-12 —
mine_gridliterals. Recommended: presets 9×9/10, 16×16/40, 30×16/99; first-reveal 3×3 exclusion; chord included. All provisional. - OD-13 —
snakeliterals. Recommended: 20×20 with walls, start 3, growth 1, 125 ms, 64 ticks per advance, flush every 16 ticks. All provisional. - OD-14 — Delivery. Recommended: fixture-first, with the production mint in a later owner-gated epoch.
- OD-15 — Copy. The owner authors every AR6.6 string and both toy names. No trade names.
- OD-16 — Platform open question. Recommended: close the platform RFC's "lightweight score-send path" question as rejected (it is a client-sent result), citing this RFC.
- AR-F3: should the scripted first ending auto-quit an active minigame session instead of rejecting? That is a platform/curriculum ruling. This RFC ships the quit-plus-copy mitigation regardless.
- Future stages, ghosts, and daily slots are named successors (DG-3, DG-4, OD-6). Nothing here pre-builds them.
- 2026-09-25: created (draft) — container,
mine_grid, andsnakeon the platform, following the Pitch template; findings AR-F1–AR-F3 filed.
Marco accepted this RFC in the 2026-09-25 batch (the answer to a direct batch question in-session, recorded by Claude). Every owner decision this RFC lists is ruled at its stated recommended default. Where the text offers alternatives, the recommended option is the normative one and the alternatives are rejected. Provisional numbers stay provisional and are ratified by harness measurement and SHA, as the RFC already requires. Owner-authored copy stays owner-authored: implementation ships copy keys, with any placeholder or candidate text clearly marked.