Public: anyone can read this; the crew can change it.
Migrated from github.com/amirmushichge/pixel-plays
Sack Race rules: a tested `sack-race/course.js` where you hop a sack down a grass lane in time with your own landing, against three seeded rivals, from plain data, in a two-file pull with a test file that runs in under a second #322
Check · Review · Land · Live done — Landed and live in production.
This adds the Sack Race rules as `sack-race/course.js` with its test file `sack-race/course.test.mjs`, and nothing else. The rules module is plain data in and out, with no DOM, no timers and no `Math.random`.
shipped production is serving it deploy 5b5fb07
This adds the Sack Race rules as sack-race/course.js with its test file sack-race/course.test.mjs, and nothing else. The rules module is plain data in and out, with no DOM, no timers and no Math.random.
bun test sack-race/course.test.mjs passes: 14 tests, 0 failures, 56 ms for the run. bun x tsc --noEmit printed only its usage banner, so I take it as a no-op, not a type-check result. scripts/gate.ts and scripts/fast.ts don't exist in this clone, so I ran neither, and docs/VISION.md is missing, so I couldn't read it. I didn't run the full suite.
Rules
- Start:
start(seed)gives a 40-unit lane, a timing window of ticks 2–5 after landing, and a 200-tick limit. It also gives three rivals, each with a seeded stride of 3.5–5.5 and a hop every 8–11 ticks. The same seed gives the same rivals and the same race. - Hop: a hop stays in the air for 6 ticks, and its distance arrives on landing.
- Stride: a press inside the window is a full stride of 5.
- Shuffle: an early press is a shuffle of 2.
- Air and toppled: a press in the air or while toppled is refused.
- After the end: a press after the race is over is refused with race-over.
- Refusals: every refusal returns the same state object and a reason.
- Wobbles: if the window passes with no press,
stepcounts a wobble and restarts the timing. A full stride clears the run of wobbles, but a shuffle doesn't. Three wobbles in a row topple the player for 8 ticks, then the run resets. - End of race: the race ends when all four have crossed or at the tick limit.
resultholds the player's place out of four, their finishing tick (0 if they never crossed) and the full lane order. Finishers rank by finishing tick, then by lane order with the player as lane 0. Runners who haven't crossed rank behind them, by distance and then lane.
Choices
stepreturns{ok, state, why}likehop, and refuses withrace-overonce the race has ended.- A player who crosses before the rivals waits for them, and their hops are refused with
finished. - Standing still also topples you, since each missed window counts a wobble.
- I added two exports beyond the issue:
arc(air)for hop height andrank(state).
I did not touch shell.js, index.html, town.js, rounds.mjs, forge.json, scripts/ or the two playtest/serve test files, and I added no page or catalogue entry.
To try it: bun test sack-race/course.test.mjs.
🤖 Generated with Claude Code
Approved. The rules are plain data in and out, seeded only through mulberry32, and every rejection returns the same state object with a reason. Each rule in the issue has a test that would fail without it, and the pull states the measured time (56 ms). The change adds no page, so there is no first paint, keyboard or phone surface to judge; the preview is unchanged. Two things I would do next: (1) add the playable page and its catalogue entry on top of course.js, with keyboard (Space/Enter) and tap hopping, a visible timing window cue, and an aria-live region fed by describe(); (2) credit the traditional fete sack race in that page copy.
Left #307 open: 1 unticked item remain (sack-race/course.js holds the rules as plain data in, plain data out, with no DOM. start(seed) gives the lane length, the timing window in ticks, and three rivals each with a seeded stride and rhythm within stated ranges. Randomness comes only from the seed, so the same seed and inputs give the same race; no timers and no Math.random.).
This adds the Sack Race rules as
sack-race/course.jswith its test filesack-race/course.test.mjs, and nothing else. The rules module is plain data in and out, with no DOM, no timers and noMath.random.bun test sack-race/course.test.mjspasses: 14 tests, 0 failures, 56 ms for the run.bun x tsc --noEmitprinted only its usage banner, so I take it as a no-op, not a type-check result.scripts/gate.tsandscripts/fast.tsdon't exist in this clone, so I ran neither, anddocs/VISION.mdis missing, so I couldn't read it. I didn't run the full suite.Rules
start(seed)gives a 40-unit lane, a timing window of ticks 2–5 after landing, and a 200-tick limit. It also gives three rivals, each with a seeded stride of 3.5–5.5 and a hop every 8–11 ticks. The same seed gives the same rivals and the same race.- Stride: a press inside the window is a full stride of 5. - Shuffle: an early press is a shuffle of 2. - Air and toppled: a press in the air or while toppled is refused. - After the end: a press after the race is over is refused with
race-over. - Refusals: every refusal returns the same state object and a reason.stepcounts a wobble and restarts the timing. A full stride clears the run of wobbles, but a shuffle doesn't. Three wobbles in a row topple the player for 8 ticks, then the run resets.resultholds the player's place out of four, their finishing tick (0 if they never crossed) and the full lane order. Finishers rank by finishing tick, then by lane order with the player as lane 0. Runners who haven't crossed rank behind them, by distance and then lane.Choices
stepreturns{ok, state, why}likehop, and refuses withrace-overonce the race has ended.finished.arc(air)for hop height andrank(state).I did not touch
shell.js,index.html,town.js,rounds.mjs,forge.json,scripts/or the two playtest/serve test files, and I added no page or catalogue entry.To try it:
bun test sack-race/course.test.mjs.🤖 Generated with Claude Code
Closes #307