Barrel Toss ▪ Reversed Donkey Kong — Game Design Spec

Type: Product Requirements Document Status: Draft for review — pre-build; all tier numbers pending the §9 simulation Series: Do villain things (sibling of saboteur, ghost-squad, stacked, minelayer) Deliverable target: single offline HTML file

1. Background and Goals

Concept. Barrel Toss is a reversed Donkey Kong (Nintendo, arcade 1981, 25m girder board): the player is Donkey Kong at the top of the structure, choosing when and how to heave barrels, while an AI opponent — the Runner (Jumpman/Mario) — climbs the girders and ladders in real time, exactly as a skilled arcade player would. The player wins by landing a barrel or fireball on him before he reaches Pauline. Where the original asks one player to survive the climb, the reversal asks the other to make the climb unsurvivable.

Design lineage. The candidate was approved under the near-perfect F2 standard and carries the open spike "window lethality vs a route-planning bot" (selection spec §9). This design is grounded in the arcade ROM itself, not memory: the barrel engine was verified against a pixel-comparison-verified Z80 decompilation (qarl/arcade-js, mirrored at computerarcheology.com) and Don Hodges' 2008 ladder-routine disassembly. Every mechanic below exists in the original; the reversal changes only who decides.

Goals (observable outcomes).

2. Product Contract

ElementDefinition
Primary userA casual player in a browser; solo only; full keyboard and touch support
TriggerOpening the HTML file; boots to TITLE with no external resources
Core jobHeave barrels (roll / hurl / blue) that kill the Runner during his climb
Completion (kill)The Runner is hit by a barrel or fireball, or his bonus clock reaches zero. The kill is scored and the tier attempt ends at SUMMARY
FailureNone by design (mirror of the original's absent win state for DK). If the Runner reaches Pauline he escapes: the board resets with a fresh Runner, the escape is counted, and play continues in the same attempt. Quitting is always available.
Trust boundariesFully local: no network calls, no external assets, no accounts; best scores and settings only in localStorage

3. Scope

P0 — required for the complete game P0

P1 — valuable, not blocking P1

Explicitly excluded OUT

Rejected during design: a HURL that lands "wherever Mario was at the moment of the release press" — player-chosen targeting. Rejected on three filters. F4: the arcade ROM never aims a release; trajectories are fixed waypoint strings, and no aiming exists in the original's vocabulary. F6: a rate-limited guaranteed hit is still an I-WIN button — the Runner must commit to ~8 ladders per climb, each lasting seconds with him X-locked, so "press HURL during a climb" admits no whiff, no bait, no counterplay; cooldown only paces the cutscene. F2: it kills even a frame-perfect Runner on schedule, which is the pure-execution forced-loss structure the near-perfect standard exists to exclude; there is no human-shaped channel being exploited. The player does control where a barrel goes — through timing against fixed paths (Mario's own running does the aiming), and through the ROM's own post-bounce reseed, which on high native difficulty literally redirects bounces toward the Runner (SR-4). Fixed trajectories, player-chosen moment.

4. Game Model

4.1 Objects

ObjectCanonical definition
The Site (25m)The arcade's first board, portrait orientation (224×256): six sloped truss girders zigzagging from the oil drum at bottom-left up to the top, Pauline's short platform (88→150, sloping down-left) at the very top, and DK standing on his own ramp (girder 1, x≈52) beside his barrel piles — as in the arcade screen. Ladders sit on the native X mod 8 = 3 grid, including the twin pair at the top (75 partial / 91 full) and the short right ladder (139) up to Pauline. Geometry replicates the arcade reference screenshot, not an invented layout.
The Tosser (player = DK)Stationary on his ramp beside his barrel piles. One verb family — heave — with seven native release options across three families (SR-2). Between heaves he plays his native idle chest-pound animation. He never moves or aims.
The Runner (AI = Jumpman/Mario)The opponent. Runs girders, waits at ladder bases, climbs, and jumps with fixed arcs — a finite-state router over the girder/ladder graph, never a generic platforming AI (FR-05). Starts at the ROM's native spawn point; reaching Pauline is an escape.
Roll family (ROLL / DEEP ROLL / CHASE ROLL)Orange barrel set onto the top girder via the ROM's three-waypoint release string — identical physics for all three modes. They differ only on the ladder-descent axis: ROLL runs the native SR-4 gate chain at every ladder head (difficulty-scaled likelihood steered by the Runner's own X position and movement — the barrel is natively "interested" in him); DEEP never descends (full zigzag to the drum); CHASE descends at every head it passes. All three are regimes the ROM's own latch/state machine can produce (SR-2, R-6).
HURL barrelThe ROM's direct drop, shipped as the arcade's high hurl ("sometimes Kong throws them directly down the building" — attract-mode text): a 14-frame grab at the launch point (60,74), then a ballistic arc at HURL_VX = 3.0 px/f under ROM gravity — it clears DK's own ramp and lands on a girder below (fall contact never re-lands on the ledge it left), where the native bounce reseed takes over. Mode NONE: its rolling never descends a ladder. Rarest release in the ROM (1-in-16 gate) → longest cooldown. Single form.
Blue family (BLUE / DEEP BLUE / CHASE BLUE)Alternate-kind barrel on the roll path — same three descent modes as the roll family, same physics. Every blue retires at the oil drum, ignites it, and arms exactly one fireball emission (~1 measured per blue barrel; native 64-frame arm; SR-4). The player's area-pressure weapons.
FireballSpawned by an ignited drum (native cap: 5 fire records). Roams the girders and climbs ladders under its native motion tables; its post-contact velocity uses the same reseed family as bouncing barrels, so on high native difficulty fireballs also steer toward the Runner. Kills on contact.
Bonus clockThe Runner's native board timer (L1 = 5000 display points). On 25m the ROM does not tick it per second — it decrements one unit per barrel released. ~50 thrown barrels drain a fresh clock; at zero the Runner dies the native time-up death. His clock is therefore the player's throw economy made visible, and it is refilled on every escape (SR-1).
Heave cycleEach press commits DK to the native grab–raise–heave animation for a flat HEAVE_FRAMES = 26 (~0.43 s) lockout shared by all release types — presses during it are ignored, never queued (SR-2). The ROLL string advances one waypoint every WP_STEP = 14 frames (~0.23 s): three points, hands → slight arc → ramp contact (~0.47 s total); HURL holds its launch point 14 frames, then goes ballistic.
CooldownPer-release-option lockout in real seconds, ticking only during PLAY. Every deck button shows its remaining cooldown live (sweep + seconds). Tier-scaled: Beginner gets the richest supply, Expert the leanest (SR-3).
EscapeThe Runner reaches Pauline's row: a visible cutscene plays for ESCAPE_FRAMES = 90 — he walks to her, three hearts rise, and a blinking localized "HE ESCAPED!" banner shows with the running escape count — then a fresh Runner spawns at the native floor spawn with a refilled bonus, in-flight barrels retire, and the escape counter ticks. The attempt is not over — only a kill ends it. Escapes are visible, counted failures of pressure, not losses.
Hammer P1Two fixed props. While held (~5 s native) the Runner smashes barrels he contacts but cannot climb, jump, or change plan — a stall that drains his own clock. Spike-gated; see R-5.

4.2 Match state machine (canonical)

TITLE ──▶ TIER SELECT ──(pick tier)──▶ READY (~1.5 s) ──▶ PLAY ▲ ▲ │ │ │ ┌── Runner killed ──▶ KILLED ──▶ SUMMARY │ │ │ (barrel / fire / time-up) │ │ │ │ ├─▶ retry tier (R) │ │ │ └─▶ TIER SELECT (Esc) │ │ │ │ └────────┴── Runner reaches Pauline ──▶ ESCAPE ANIM ──▶ (fresh Runner, refilled bonus) ──▶ PLAY │ PLAY ◀──(Esc / blur)──▶ PAUSED: all physics, heaves, cooldowns, and the bonus clock frozen PLAY ──(Q / quit)──▶ TIER SELECT (attempt abandoned; no score recorded)

There is no LOSS state by design (§2). The only terminal state of an attempt is KILLED; escape is a sub-loop of PLAY, and passive play produces endless escapes at zero score.

5. Functional Requirements

FR-01Arcade-faithful presentation

FR-02Difficulty tiers and progression

FR-03Match loop (heave in real time)

AspectRule
Ready phase~1.5 s: empty girders, Runner at native spawn, drum unlit, bonus clock full, all cooldowns zero. Input ignored.
HeavePlayer presses a release key/taps a deck button that is off cooldown. Cooldown starts immediately; DK plays the native animation for that release; the barrel enters its fixed waypoint/ballistic path (SR-4). No aiming, no queueing, no take-backs — the moment of the press IS the decision.
In-flight economyMaximum 10 active barrels (native object table). If the table is full the press is refused with a brief "pile full" deck shake (a native limit, vanishingly rare under cooldowns). Every released barrel ticks the Runner's bonus clock one unit.
Kill paths(1) barrel contact while the Runner is on a ladder — he cannot jump mid-climb; (2) contact during a jump arc whose landing is covered by a second barrel — the double bind; (3) fireball contact; (4) bonus clock reaching zero — the native time-up death. Kills play the native death animation.
EscapeRunner reaches Pauline's row → escape animation (~1.5 s) → fresh Runner, refilled bonus, all active barrels retire naturally/are cleared, fireballs despawn, escape counter +1, play resumes without a READY pause. Thrown-barrel total keeps accumulating (escalation is the score pressure).
No lose stateThe attempt continues until the kill or the player quits (Q → TIER SELECT, nothing recorded). Idle play yields endless escapes: no throw, no bonus drain, no risk to the Runner — inaction is self-penalizing through score, exactly like Saboteur/Stacked.
ReplaySUMMARY offers retry (R) and TIER SELECT (Esc).
ExceptionsTab hidden / window blurred during PLAY → auto-PAUSED (SR-5). Kill and escape completing on the same frame: kill wins (attribution to the last throw is preserved).

FR-04The release deck and cooldowns

Purpose. One verb family, one panel: choose the release option; the option and the moment are the entire decision. The deck is a 3×3 numpad grid — columns are barrel families, rows are descent intensity — matching the Saboteur dealer-bar convention so on-screen position equals physical numpad position.

Interactive demo — FR-04 release deck (mock timings)

Tap a release or press its numpad key (1/4/7 · 2 · 3/6/9). Watch the live cooldown sweep per button. Demo cooldowns are shortened for illustration; game cooldowns are SR-2/SR-3 anchors finalized in §9.

Bottom row = the authentic trio. Up each column: DEEP (never dives) and CHASE (takes every ladder) — stronger, slower, and fully readable to him.
Demo timings: ROLL 2.0 · DEEP 4.0/9.0 · CHASE 7.0/14.0 · HURL 8.0 · BLUE 5.0 s. Columns are families, rows are descent intensity — physics identical for all seven.

FR-05The Runner bot

Purpose. A skilled arcade runner who is nonetheless killable through the information and commitment windows built into the original. This is the design's concentrated risk (R-1, R-4): the generic "clumsy platformer bot" is a killed category in this project (selection §7), and 25m is deliberately board-like — flat girders, straight ladders, fixed-arc jumps, no pits — so the Runner is implemented as a finite-state router over a graph, not as a physics agent.

FR-06Scoring and attribution

FR-07Audio

FR-08Responsive layout and touch controls

FR-09Result sharing (community layer)

6. Shared Rules

SR-1 · Time and economy model (canonical)

The match is real-time at the arcade fixed step. The atomic economic event is the release: one press, one heave animation, one barrel, one bonus-clock decrement. Cooldowns are denominated in real seconds (not barrels), tick only while state === 'play', and freeze on pause. The bonus clock starts at the native L1 value (5000 display = 50 internal units → ~50 releases per climb) and refills on every escape. The native DIFFICULTY byte advances on elapsed board time (~1 per 33 s, capped at 5) independent of tier; it rescales barrel speed and the reseed arms (SR-4), so an attempt that runs long escalates itself.

SR-2 · Release options and cooldown rule (canonical)

The original resolved DK's decision layer with dice and latches only because no human controlled his side of the cabinet. The reversal hands that layer to the player: the ROM's own barrel behaviors become seven selectable releases, each a regime the ROM itself can produce, with a cooldown as its cost. Physics and trajectories are identical for every option — an option decides when a barrel changes planes, never how it moves.

KeyReleaseDescent modeNative groundingStarting anchors (Beginner / Intermediate / Expert)
1ROLLNative lottery — the SR-4 gate chain decides at every ladder headThe shipped arcade behavior (15/16 release gate)2.7 / 3.0 / 3.3 s
4DEEP ROLLNever descends — rolls the full zigzag to the drumThe pre-latch regime / first-barrel behavior (R-6)5.2 / 6.8 / 8.2 s
7CHASE ROLLDescends at every ladder head it passesThe pre-latch regime read literally (latch arm of startBarrelDescentAtLadder)9 / 13.5 / 18 s
2HURL— single form; ballistic arc, never descends (mode NONE)high hurl across the board, then native bounce reseed9 / 15 / 21 s
3BLUENative lottery + drum ignitionAlternate-kind bit (~12.8% measured) on the roll path12 / 18 / 24 s
6DEEP BLUENever descends + drum ignitionKind bit × pre-latch regime18 / 27 / 36 s
9CHASE BLUEDescends at every ladder head + drum ignitionKind bit × pre-latch regime27 / 39 / 51 s

Cooldown starts on press (the release is final when committed — same spirit as Saboteur). A press during an active heave, a winding master cadence, or on a cooling cell is ignored. The 10-barrel native table is a hard cap independent of cooldowns. Ordering guardrail: within a family, cooldown must order base < DEEP < CHASE at every tier.

Master release cadence (round 7): on top of the per-type cooldowns, one global timer gates every release — the cabinet's release-opportunity pulse (SR-4 Release scheduling) made player-facing. After any throw, no type fires for gcd seconds (tier anchors 0.9 / 1.1 / 1.3 s, Beginner / Intermediate / Expert). The cadence strictly extends the heave lockout (gcd ≥ HEAVE/60) and stays below the ROLL cooldown, so single-type spam is never gated by it — it throttles only cross-type weaving and the opening volley (the round-7 fix: all seven types could previously be emptied inside ~3 s of an attempt start, a volley even the Expert Runner cannot survive). The first throw of an attempt is ungated; the timer ticks only in PLAY, freezes on pause, is untouched by the escape reset, and a press refused by it consumes nothing. The anchors are sim-locked: Expert burst loses kills from 1.4 s up (§9.4 round-7), so the ladder sits under that cliff.

Balance — certainty cuts both ways: DEEP and CHASE barrels are deterministic, so the Runner's planner treats them as hard facts and routes around them exactly (FR-05); only the native-lottery releases contribute expected risk he cannot resolve. Each option therefore has a distinct role: DEEP = patient zone pressure and guaranteed drum delivery (blues), CHASE = shaft pursuit that punishes misroutes, lottery = unresolved threat everywhere at the lowest cost. The §9 F6 checks must confirm no option is dominated and no single-option spam approaches the human-policy kill rate. The anchors deliberately compress authentic ROM frequencies for playability (a literal 1/16 gate at ~2 s cadence ≈ 32 s between hurls), the way Saboteur compresses its academic results into playable beats; the §9 sim decides the final values. Only cooldown lengths may be tuned — no new rule, meter, or ammo type may be introduced to fix balance.

SR-3 · Difficulty tier configuration (canonical)

TierReaction latency (react)Jump-timing jitterBody speedPlayer cooldowns (all seven, SR-2)Intended matchup
Beginner18 f60.8× arcade-native run/climbRich (SR-2 Beginner anchors)Full vision, slow hands and feet. A fair first kill while learning the deck — joy of winning first, then graduate.
Intermediate11 f30.9×Mixed (SR-2 Intermediate anchors)Quicker feet, quicker reads; kills need prediction, not reaction throws.
Expert6 f11.0× (arcade-native)Lean (SR-2 Expert anchors)Near-perfect vision and reflexes — the F2 near-perfect standard. Only predicted spawns, double binds, and fire pincers kill him.

The Runner's decision model is identical at every tier — full-vision planning over the shared horizon, no foresight cap, no slips, no second-best picks (2026-10-17 play-test ruling: maximum theoretical strength first). Tiers rescale only the body (reaction latency, jump jitter, speed) and the player's economy; the native DIFFICULTY ramp (SR-1) and all SR-4 physics are identical across tiers. Named constants grouped by category (NATIVE_* ROM constants, TUNE_TIERS tier parameters), each with a descriptive comment; no magic numbers in logic. If post-ship play-testing finds a tier's Runner too strong, the documented response is body-scaling (lowered speed or reaction, Ghost-Squad style) — never decision sabotage.

SR-4 · Barrel and fireball physics (canonical, ROM-grounded)

These are the arcade constants and decision routines, reproduced rather than reinvented. Addresses refer to the verified Z80 decompilation; implementation copies the behavior and keeps the labels in comments for auditability.

MechanicNative rule
Release schedulingNative release cadence is gated by an every-32-frames phase check against the DIFFICULTY countdown; fire rate rises with difficulty. In the reversal the player schedules releases (subject to SR-2); the same animation pipeline renders them.
Release-type decisionThe ROM draw is a timer-seeded RNG low nibble: nonzero (15/16) → ROLL, zero (1/16) → direct drop; the first scheduled drop of a board is also forced deterministically. The orange/blue kind is an independent bit (~12.8% alternates measured). The reversal replaces both draws with the player's explicit type choice — the only place we change a decision from system to player.
ROLL pathThree fixed waypoints (60,74) → (72,72) → (84,84), WP_STEP = 14 frames per step; the barrel lands on the top girder (girder 1) and zigzags the sloped girders under native rolling velocity. Shipped edge behavior: rolling off a girder end drops straight down (horizontal velocity zeroed at the lip) and lands on the first girder surface below the one it left — never re-lands on its own ledge, never drifts past the next girder's catch range.
HURL pathA 14-frame grab pinned at the launch point (60,74), then a ballistic throw: HURL_VX = 3.0 px/f horizontal under ROM gravity at 16t+8 subpixels/frame² (terminal cap 3.5) — the arcade's high hurl. The arc clears DK's own ramp and lands on a girder below (fall contact starts checking one girder below the one it left), where the native bounce reseed takes over; mode NONE means its rolling never descends a ladder.
Bounce reseedOn each girder contact the barrel's horizontal velocity is re-seeded by routine family 0x22CB, selected by state: (a) before the first barrel retires to the drum — fixed magnitude by board level; (b) native difficulty 1–2 — random magnitude; (c) difficulty 3–4 — random magnitude, direction toward the Runner's X; (d) difficulty 5 — magnitude proportional to the X offset to the Runner, sign toward him. Bottom girder: fixed −1 px/f +176 up-launch, rolls into the drum (ROM detail — shipped barrels simply retire in the drum band, see Retirement below). This reseed is the ONLY native "aiming" in the game and belongs to the bounce, not the release.
Ladder descentA rolling barrel tests the ladder-endpoint table every 8th pixel of travel (X mod 8 = 3) and re-decides descent fresh at every ladder head — the barrel record carries no behavior-type field; nothing is fixed at spawn. Gate chain (startBarrelDescentAtLadder): (1) a one-way global latch (0x6348) flipped by the first barrel retirement into the drum (any kind) separates the pre-latch from the graded regime (resolved — R-6: pre-latch ships as never-descends, the observed first-barrel behavior; the decomp's literal unconditional reading ships separately as the CHASE mode); (2) never when the Runner is at/above the ladder head (Mario Y − 4 vs head row + 5): descent only chases downward; (3) difficulty-scaled 2-bit draw — pass while (RNG & 3) < (DIFFICULTY >> 1) + 1, i.e. 25% → 75% across the native ramp; (4) Runner steering: exactly on the ladder column → descend at once; else moving toward the column and within sight of it (STEER_SIGHT = 24 px — barrels only dive toward a Runner they can practically reach; the live joystick in the original — this is the documented "barrel steering" technique; the Runner FSM's run direction in the reversal) → descend; else a 1-in-4 tail gate (RNG & 0x18 = 0). Replicated verbatim — the ROLL is natively homing through ladder heads, steered by the Runner's own position and direction.
Retirement / drumBarrels retire in the bottom-left drum band (X 30–43, Y ≥ 228). A BLUE barrel reaching the drum writes fire-arm state 3: after a 16-count ÷ 4 prescale (~64 frames) exactly one fireball spawns at the drum mouth (x 44, floor girder), then the arm drops to state 1 (drum lit, no more emissions) until the next blue barrel. Up to 5 fireball records exist.
Fireball motionFireballs roam girders and climb ladders under native motion; post-contact velocity reuses the 0x22CB family, so they wander early and steer toward the Runner at high difficulty. Despawn follows native edge/range retirement.
Bonus clock25m decrements the bonus once per released barrel (ROM 0x2CDA), not per second; L1 start = 50 internal units (5000 display). At zero the Runner dies the time-up death. Refilled on escape.
Ladder tableFifteen ladders, all on the native X mod 8 = 3 grid, pixel-verified against the 25m screenshot (girder top → bottom; broken climb-proof for the Runner but still barrel descents): 131 full (0→1, Pauline's platform ↔ DK's ramp); 67, 83 tall sky decoys ending mid-air (top y 24; perchable, the arcade's famous safe spot); 187 full, 91 broken (1→2); 35, 75 full, 171 broken (2→3); 115, 187 full, 67 broken (3→4); 35, 99 full (4→5); 187 full, 83 broken (5→6). 11 full + 4 broken.
Jump geometryThe Runner's fixed-arc jump clears three framework sections (frame-data correction over the cabinet sticker's "two"). Shipped reflex model (round 5 — the low native arc): JUMP_VY0 = −1.85, JUMP_GRAV = 0.12, JUMP_AIR = 30 f, apex −13.35 px — the arc skims a barrel top (−8) with ~5 px to spare, exactly like the original, so his head stays below the floor above except near a girder's low-ceiling end (floor gap < ~27 px), where the headClear sim vets the arc against barrels riding the upper deck and vetoes the jump like a covered landing. Road-level frames sit outside [JUMP_SAFE_T1, JUMP_SAFE_T2] = [9, 21] (kill contact needs him below −11 px of arc height). A visible oncoming barrel inside JUMP_TRIGGER = 92 px starts his reaction (tier latency ± jitter, SR-3); he plants and the exact arc-vs-barrel sim (arcClear — real integration, edge clamp, girder slant, and the tick's barrel-first interleave) owns the takeoff trigger: the analytic go (speed-aware closing window) only bounds the evaluation zone, and a failed read inside the zone is classified too-early vs too-late by an exact lookahead that steps the barrel ahead (wait-sep — hold the plant while the window is still approaching). A barrel first sighted too late for the window falls back — neutral hop (same exact read with zero drift) / stand-base / retreat — with the marginal forward arc only as a wall-pinned last resort. Fireballs are never jumped: their thicker contact band (body-center ±8) outlasts the low arc's safe frames at every speed, so he gives ground (fire-flee) — fires only re-steer every FIRE_STEER_FRAMES, making sustained lateral distance and ladders the honest answer. A jump whose arc and landing are both covered is the double-bind kill; he otherwise always jumps cleanly (FR-05 guardrails).
Object cap10 barrel slots (stride 0x20), active/claimed/empty flags per slot. Full-table presses are refused (FR-03).

Evidence base: qarl/arcade-js games/dkong/ (idiomatic decompilation, pixel-diff verified against MAME 0.288; the project itself lists the airborne continuation at ROM 0x20EC as its open issue #1 and the eight unlogged dropping barrels as #17 — see R-6), Computer Archaeology annotated Z80 dump (ROM tables 0x39B9–39CE), and Don Hodges' Controlling the barrels in Donkey Kong (2008) for the ladder-head routine. Open reconciliation work is part of the build, not a license to invent.

SR-5 · Pause and focus

Esc toggles PAUSED; the fixed-step clock, heaves, all cooldowns, barrel physics, fireballs, the bonus clock, and the Runner freeze. Window blur or tab hidden forces PAUSED. PAUSED dims the board and shows touch-capable controls; no state is lost.

SR-6 · Persistence

Stored in localStorage: per-tier best kill score (barrels thrown; tiebreak time), mute preference, last played tier. Storage unavailable → session-only, no error surfaced.

7. Exceptions and Boundaries

CaseBehavior
Player never throws (idle)Runner climbs untouched, escapes, reclimbs forever. Bonus never drains (it is per-release on 25m). No score, no kill, no unlock — endless escape loop until the player engages or quits (FR-03). Shipped cadence (browser verification): an untouched Beginner Runner completes a climb in ≈4–5 s — 5 escapes in ~20 s idle.
Press during a heave animation or on a cooling cellIgnored, never queued; the cell shows its sweep state. No "buffered smart throw".
10 barrels already activePress refused with a brief deck shake; cooldown not consumed (no barrel was released).
Kill and escape complete on the same frameKill wins; attribution goes to the fatal release.
Bonus reaches zero during an escape animationEscape wins the frame (he touched Pauline); bonus refills. Edge case pinned by 03-flow tests.
HURL lands with no Runner anywhere near its laneA whiff: it bounces the native chain and retires. Whiffs are the cooldown's cost — this is required, never silently corrected toward him.
Fireball cap (5) reached when a BLUE retiresDrum ignites/relights and the emission is consumed natively with no spawn (ROM table full), exactly as arcade behavior.
Small viewport / portrait phoneMobile layout (FR-08); below a usable minimum, proportional board scaling and a compact strip — reject only below ~300 px width.
Autoplay-blocked audioEngine initializes on first gesture; game fully playable silent.
localStorage blockedSession-only bests; no error surfaced (SR-6).

8. Open Questions and Risks

9. Testing and Difficulty Validation (F2 Spike Plan)

The tier numbers are not guessed before implementation: the game is built instrumented, then the same three-layer discipline shipped with the other titles is run. All logic tests load the real index.html in-memory with stubbed browser APIs; the file on disk is untouched.

9.1 Test harness infra

9.2 Headless unit tests (planned scenarios) P0

FileFocusScenarios
01-barrel-physics.test.jsSR-4 fidelityROLL follows 4-waypoint string at 24 f/step · HURL follows 1 waypoint then 16t+8 gravity on the fixed column · all four bounce-reseed arms select correctly by pre-drum/difficulty state · ladder-head test every 8 px with the never-above-Runner gate, X/steer bias, and 25% fallback · all three descent modes gate correctly (lottery per SR-4; DEEP never starts a descent; CHASE starts at every head) · retirement band only · BLUE arms exactly one fire after ~64 frames, then state 1 · 5-fire cap · bonus decrements exactly one per release and refills on escape · 10-slot cap
02-runner.test.jsFR-05 behaviorFSM transitions legal; CLIMB cannot JUMP · always jumps a single visible barrel on open ground · never walks into a visible barrel · commits only to reachable ladders (never broken) · planner foresight is the shared full-vision horizon at every tier (no tier cap) · determinism: identical seeds reproduce the Runner exactly · death only via the native kill classes · all-shafts-busy siege: never motionless without a priced wait or holdX, never eats a barrel · commit gate: never enters a shaft while a barrel beats him to the safe height · surface pause: holds below the deck while a barrel crosses the ladder head · converging barrels: most-urgent reflex prevents freeze on stacked streams · zero bonus → time-up death · reaches Pauline → escape · round 4: single-barrel geometry sweep (3 tiers × 5 girders × positions × 3 speeds × both approaches): never dies to one readable barrel, forward takeoffs always cross inside the high-arc window unless wall-pinned, neutral hops always clear out before touchdown · drum-side retiree latched on the approach (intent-aware phantom read), never discovered point-blank · late sighting: no road-level forward takeoff while retreat room exists · round 5: sweep/late-sighting takeoff invariants re-pinned on the exact arcClear sim at the decision-read positions (runner pre-step, barrel post-step — the tick steps barrels first) · converging-pair regression geometry widened beyond one full arc (a 30 px gap at vx 1.0 equals JUMP_AIR — provably lethal for any player on open ground) · round 6: cluster with no jump window flees to the floor ladder, hides UP on the rungs, holds while the stream passes underneath, releases and resumes (never jumps, never dies) · slow stream with both landings covered flees and hides DOWN the ladder, climbs back once the deck above clears · escape refused while a descent occupies the shaft (falls back to the generic layer, never freezes) · escape refused against an inbound CHASE stream (the chaser would dive in after him)
03-flow.test.jsState machine & no-auto-actionREADY ignores releases · escape resets Runner/bonus/barrels but keeps barrel total and escape count · kill vs escape same-frame tie → kill · pause freezes physics/cooldowns/bonus · blur auto-pause · quit records nothing · best never worsens · no code path auto-throws or auto-selects a release type (Stacked auto-place guardrail) · press during heave ignored · cooling press ignored and not consumed · round 7: press during the master cadence ignored, never queued
04-economy.test.jsSR-2/SR-3Cooldown starts on press, ticks real seconds in PLAY only, frozen in pause, floor ≥ animation length · all three tier configs load and differ only in the documented parameters · named-constants audit (no unnamed magic numbers in tuned logic) · deck state matches internal cooldowns · full-table refusal does not consume cooldown · round 7: master cadence gates every type independent of per-type cooldowns, ticks in PLAY only / frozen in pause, first throw of an attempt ungated, anchors 0.9/1.1/1.3 with the ≥ heave-animation floor · wind-up rail starts exactly full and hides on expiry, all seven cells dim while winding
05-touch-layout.test.jsFR-08Breakpoint switch at 900 px: board-left/panel-right vs board-top/deck-bottom · deck key positions match numpad (1/2/3 bottom, 4/6 middle, 7/9 top) · tap targets ≥46 px · cooling/ready/committed states differ by sweep+border, not color alone · full match completable touch-only in the E2E layer

9.3 Policy players for the difficulty sim

PolicyDefinitionRole
IdleNever throwsSanity: Runner escapes indefinitely; bonus full; no accidental kills
Arcade-autoMimics the original ROM itself: releases on the native cadence with the native 15/16 + 1/16 and 12.8% drawsLower bound. The 1981 CPU opponent. A Runner that dies to this often is too weak — the arcade game is beatable by humans, so the lower bound is "escapes, reliably"
Roll-spamNative-lottery ROLL the instant off cooldown; never DEEP/CHASE/HURL/BLUEF6: uniform streams must be jumpable/ladderevable
Hurl-campHURL the instant off cooldown during any climb; ROLL neverF6: fixed-lane camping must whiff at high rate against routing
Blue-spamNative-lottery BLUE off cooldown; otherwise idleF6: area pressure alone must not close climbs
Chase-spamCHASE ROLL the instant off cooldown; otherwise idleF6: deterministic chasers are fully legible to the planner — shaft avoidance must hold at high rate
Oracle/predictionEvaluates every legal release moment by forward-simulating SR-4 against the Runner's actual policy; takes the moment with highest kill probability; two-beat setups allowedUpper bound on achievable lethality
Human-likeSame evaluation as oracle but top-3 moment/type choice weighted (≈0.6/0.3/0.1), with deliberate reaction delay and no access to the Runner's slip RNGThe real-player proxy; win bands apply to this policy

Each attempt sim caps at 5 escapes (attempt failure for measurement only — the shipped game has no cap). Seeded batches (≥60 at Beginner, ≥40 at Intermediate, ≥20 at Expert per policy), pinned RNG, telemetry per release: type, moment, target lane, result (whiff/jumped/ladder-descended/retired/fire/kill), and for every kill the throw-vs-commit timing gap that proves attribution. Shipped harness: tests/barrel-toss/sim.js — spam/mix/burst × 3 tiers × 12 seeded attempts on one loaded game instance (startAttempt reseeds per attempt), printing kills/N + median kill time per cell and re-running the first non-kill seed with the decision-trace tail; a full sweep runs in seconds.

9.4 Acceptance bands (fixed before tuning)

TierHuman-like kill rate within 5 escapesArcade-auto lower boundOracle upper boundF6 guard
Beginner≥ 70% target; median kill ≤ ~30 throwsEscapes ≥ 90% of climbs (not trivially killable)≥ 90% (the kill exists)Best degenerate policy's kill rate ≤ 60% of the human-like policy at that tier; no single release option accounts for >70% of human-policy kills at Beginner/Intermediate, and no option is dominated — the human policy must find a use for all seven (Expert HURL share may rise but lane-avoidance must remain measurable)
Intermediate40–65%Escapes ≥ 95%≥ 75%
Expert10–30%Escapes ≥ 98%≥ 50%

Round-2 revision (2026-10-17 play-test): shipped tuning supersedes the Expert band above — the Runner ships at maximum theoretical strength at every tier (body-only scaling, SR-3). Measured with the shipped script policies (roll-spam / learning-rotation / reactive-burst, 12 seeded boards each): Beginner and Intermediate fall 12/12 to every policy; Expert falls to spam and mix and holds the reactive-burst script to 0/12 — its escapes require spawn prediction, which is precisely the player-side skill the game teaches. If post-ship play-testing finds him too strong, the response is body-scaling per tier (SR-3 note), never decision sabotage.

Round-5 revision (2026-10-19 play-test, low native arc): the round-4 arc retuned to the original's barrel-skimming jump (SR-4 Jump geometry); all decision reads are exact sims. Measured (same policies): mix 12/12 and burst 12/12 at every tier (median ~10–20 s) — the forced-win path is intact; spam 12/12 Beginner, 11/12 + 1 escape Intermediate, 11/12 + 1 escape Expert. The two spam escapes are the accepted consequence of fixing the reported jump bugs: round-4's spam kills were partly parasitic on his own mistimed takeoffs, and a clean Runner does not die to a uniform-speed conga line — killing him requires varying releases, which still lands in ~10 s. No timeouts in any cell (no freeze regressions).

Round-6 revision (2026-10-20 play-test, escape-hatch hides): the cluster answer shipped (FR-05 escape hatch) — he flees unjumpable binds to a hide ladder and holds on the rungs at the slope-exact safe depth. Measured (same policies, 12 seeded boards per cell): mix 12/12 at every tier (median 12.8 / 9.9 / 10.0 s) and burst 12/12 at every tier (19.5 / 10.4 / 10.5 s) — the forced-win path is intact with the hide live; spam 12/12 Beginner, 11/12 + 1 escape Intermediate, 11/12 + 1 escape Expert (the same accepted round-5 spam outcome; CHASE natively punishes ladder play, so the hide does not leak lethality). No timeouts in any cell.

Round-7 revision (2026-10-21 play-test, master release cadence): the SR-2 global cadence shipped (gcd 0.9 / 1.1 / 1.3 s) — the opening alpha-strike (all seven types inside ~3 s) is gone; every release now waits the cabinet's opportunity pulse. The pre-chosen 1.2 / 1.4 / 1.6 ladder failed the forced-win invariant at Expert (burst 11/12 at 1.4–1.5, 5/12 at 1.6 — the spaced-out follow-ups let him thread the stream and escape), so the anchors were sim-probed down under the 1.4 s cliff. Measured with the shipped script policies (12 seeded boards per cell): mix 12/12 at every tier (median 10.7 / 9.9 / 8.8 s), burst 12/12 at every tier (11.3 / 11.0 / 20.9 s), spam unchanged (12/12 Beginner, 11/12 + 1 escape Intermediate, 11/12 + 1 escape Expert — cadence < ROLL cooldown by construction, so single-type spam is never gated). No timeouts in any cell. Browser: zero game console errors; wind-up rail + all-cell dim verified in the live DOM (rail on, fill draining at exactly gcd/cfg.gcd, refused press consumes nothing).

Tuning protocol: tune Beginner to its band first (joy of winning), then Intermediate, then Expert. Allowed knobs, in order: (1) the three Runner body parameters (SR-3), (2) the seven cooldown lengths and the master cadence (SR-2). Never allowed: changing physics, adding weapons/rules, making the Runner miss jumps he can see, or aiming releases. After every change re-run the full policy matrix, not just the failing tier, and append a tuning-history table to this spec (Saboteur §9.5 precedent). R-1's failure response applies if no knob setting lands the bands.

9.5 Browser-level E2E sweep (planned)

#ScenarioAsserts
1Boots to TITLETitle overlay, zero console errors
2Advance to TIER SELECT; all three unlockedThree cards, bests empty
3Start Beginner: READY → PLAYRunner spawns at native point, bonus full, board pixels match reference crop
4Key 1 / tap ROLLHeave animation plays, barrel on ROLL waypoints, bonus −1, cooldown sweep animates
5HURL timing + cooldownBallistic arc lands below DK's ramp; press during cooldown ignored; seconds readout matches elapsed
6BLUE → drumIgnition, one fireball after ~64 frames, drum relit state
7Esc pause/resumePhysics, cooldowns, bonus frozen across a measured interval
8Runner reaches top → escape loopEscape count increments, barrel total kept, bonus refills, play resumes
9Instrumented ladder-catch killKILLED → SUMMARY with correct kill class, throw type, stats; best persisted
10Touch-only match in mobile layoutDeck bottom, ≥46 px cells, tap throw → kill path reachable, overlay buttons tappable
11Mute persists; reload keeps bestslocalStorage round-trip
12Zero console errors across the sweepNo exceptions, no warnings

10. Internationalization

Barrel Toss uses the series' shared engine public/shared/i18n.js plus the per-page dictionary public/shared/lang/barrel-toss.js, namespace barrel_toss. Offline-safe, dependency-free, fixed top-right switcher.

10.1 Supported languages and detection

10.2 Authoring pattern

10.3 Test harness independence

The harness evals only the bare inline game script; external engine/dictionary tags are invisible to it, so window.RG_I18N is undefined there. The boot is guarded (if (window.RG_I18N) …) and T() returns its English default in the harness — deterministic tests and offline play are unaffected by localization.