← Coverage hub REPS WITH FRIENDS Β· DOCUMENTS v1.1.0

Reps With Friends β€” Game Design Document

Version 1.0 Β· 2 Sep 2026 Β· Owner: Alexei Brown (build) + Ben (design/vision) Companion doc: docs/25_DESIGN_BRIEF.md β€” the 2-page handout. This is the bible.

What this document is. The complete game design of Reps With Friends as it exists today β€” a working system, not a concept deck. Every mechanic described here is implemented and tested somewhere in this repo, and every section says where. Where the design is genuinely undecided (five founder-level decisions), this document says so honestly and lays out both sides.

How to run everything in this repo (a reader who has never seen it):

bun serve.ts          # β†’ http://localhost:4173
RouteWhat it is
/figma-appThe offline test app β€” Ben's full design, every screen, running our real engine (lane F4)
/stylesFive-theme design exploration, side-by-side gallery
/systemComplete dissemination: tokens, 16 components, 36 elements A–G with status
/wikiThis documentation as browsable pages (Game Rules, App Screens, Bots, Verification, Avatars, Ops, Design, Status)
/atelierOutfit Atelier β€” avatar garment inspection tool (x-ray, seams, poses)
/demo, /debug, /hub, /connect, /slack, /avatars90-second match replay Β· live bot simulator Β· ops console Β· WhatsApp linking Β· Slack setup Β· avatar playground

The same content is publicly deployed at rwf.qalarc.com.

Source documents: Ben's original brief (summarised in docs/01_BUSINESS_ANALYSIS.md Β§2) Β· the founder-decision audit of his production Figma (figma/notes/analysis.md) Β· the element backlog (docs/07) Β· our MVP design spec (docs/13) Β· the wiki's own rules chapter (apps/wiki/game.html). Code references: packages/game-core (the TypeScript spec), apps/figma-app/engine.js (faithful browser port), apps/figma-app/daily.js (play-day/temporal layer), packages/bot-core (the chat bots).


1. The fantasy

Training stops being something you grind alone.

The target user is a 35–50 year old who used to be fit, isn't anymore, and has a group chat full of mates in exactly the same position. Every fitness app on earth sells them the same lonely loop: open app β†’ log workout β†’ stare at your own graph β†’ feel vaguely guilty β†’ churn by week three.

RWF replaces that with your mates. The gym session becomes a fixture of the group chat β€” with standings, deadlines, comebacks, theft, taunts and a charity pot. You are never training alone; someone is always ahead of you or coming for you. The game's promise, mechanically enforced (Β§4): everyone has a genuine shot until the last set.

Three load-bearing ideas arrived fully formed in Ben's original brief (docs/01 Β§2) and have survived every design pass unchanged:

  1. The "300" format β€” a real training-ground game Ben brought with him:<br>the group agrees on exercises, then any reps, any order, any mix, first to<br>300 total. Self-balancing (you choose your own mix), zero setup friction,<br>instantly understandable in one chat message. The name does half the<br>marketing (&quot;tonight we do a 300&quot; needs no explanation to anyone who has<br>ever seen <em>300</em>).
  2. The handicap system β€” effort and consistency compete, not raw fitness.<br>A couch player's push-up is <em>worth more</em> than an athlete's. This is the<br>moat: golf proved decades ago that handicaps keep weak players paying;<br>nobody in fitness does this well. It is also the hardest thing to get<br>right β€” which is why it's defensible (Β§4).
  3. Taunting as a feature, charity as the stakes. Banter is the content<br>engine; filming a mate mid-set is the ad. The pot gives trash talk real<br>consequence without gambling: the winner directs the pot to charity β€”<br>never receives it (Nico's idea, adopted; legal structure pending,<br>docs/01 Β§5). Losing money to a mate is a grudge; losing it to charity is a<br>story.

Everything else in this document is those three ideas, elaborated.


2. The core loop

β”Œβ”€ PREP ─────────────────────────────────────────────────────────────────────┐
β”‚ crew picks play days + exercises β†’ match created                           β”‚
β”‚ (waiting room + crew code) Β· bot: `rwf new` Β· app: create-battle screen    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
                                      β–Ό
β”Œβ”€ EVERY PLAY DAY (e.g. Mon/Wed/Fri) β€” loops ────────────────────────────────┐
β”‚                                                                            β”‚
β”‚  log reps ───▢ live standings ───▢ deadline pressure ──▢ day closes        β”‚
β”‚  (chat or app)   effort-adjusted    DZ1 ─▢ DZ2 ─▢ DZ3   21:00 AEST         β”‚
β”‚                   comeback armed   ≀3h    ≀1h   ≀30min       β”‚             β”‚
β”‚                                                              β–Ό             β”‚
β”‚  DAILY WINNER + RECAP β€” "YOU WON TUESDAY" Β· MOMENTS Β· next day opens       β”‚
β”‚                                                                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚  at any moment: the first RAW total to reach the target…
    β–Ό
β”Œβ”€ FINALE ──────────────────────────────┐        β”Œβ”€ SEASON Β· 4 weeks ────────────┐
β”‚ match CLOSES Β· closure bonus +15      β”‚        β”‚ 3/2/1 points Β· MVP +1         β”‚
β”‚ RESULT CARD + CHARITY POT             β”‚ ─────▢ β”‚ champion Β· A/B swap           β”‚
β”‚ (the winner directs the pot)          β”‚        β”‚ streaks Β· forgiveness $2      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
REMATCH β€” one tap, same crew, fresh pot ──▢ back to PREP

Each step, mapped to where it lives:

StepWhere it's implemented (screen / bot command / engine)
Group picks play days + exercisesApp: create-battle screen (/figma-app, shot 10-create-battle.png) β†’ config.playDays; bot: rwf new [target] (default 300) announces exercises + play days (packages/bot-core/src/bus.ts)
Match createdcreateMatch() β€” apps/figma-app/engine.js / packages/game-core/src/match.ts; waiting room with 5-char crew code (no 0/O/1/I) + QR/invite (18-waiting-room.png); bot link <code> binds the chat
Log reps from chatlog pushups 25! β€” trailing ! = camera-verified; chains applyComeback β†’ closure check (bus.ts cmdLog)
Log reps from appLog sheet: bottom sheet, preset chips 5/10/20/30/50, camera toggle (27-log-sheet.png, 57-camera-note.png)
Live standings (effort-adjusted)standings() in engine β€” raw reps, adjusted score, progress %, verified %; app battle screen (24-battle-live.png); bot s replies with medal bars + ⚑ comeback markers + πŸ‘ spectator count
Deadline pressure (danger zone)apps/figma-app/daily.js dangerLevel / dzCopy / deadlineClock β€” 1-second app ticker; DZ1/2/3 chrome (Β§3.3)
Daily winner + recapcloseDay / settleDay / recapFor in daily.js β€” the day auto-closes at the 21:00 Australia/Sydney deadline, winner recorded to dailyHistory, recap card renders (d48-recap-you-won.png, d59-recap-sam-took.png)
Match closes at targetlogReps() β€” the first log taking a player's raw total to β‰₯ target flips status to complete, stamps closedBy, logging locks
Result + charity potfinalStandings() (closure bonus applied) β†’ result screen (38-39-result-final.png), pot screens (41-43-result-pot*.png); engine pot.ts; bot result writes a 1200Γ—675 SVG share card, pot 500 = "$5.00 banked. Winner picks where it goes."
Season arcpackages/game-core/src/season.ts β€” recordMatch 3/2/1+MVP, seasonLadder, endSeason; app ladder screen (48-49-season-ladder.png); bot season new / ladder
RematchOne tap in app (45-46-rematch-live.png); bot rematch / again / runitback β€” same roster/rules, fresh pot

The loop is deliberately chat-first: every broadcast (standings, result cards, taunts) is an ad seen by the whole group chat; spectators convert via watch <code> (docs/01 Β§2 β€” "the second moat").


3. The battles

This is the founder's explicit ask: map out the battles. There are five battle forms β€” three shipped, one designed, one genuinely open.

3.1 The Classic 300 (shipped β€” the canonical form)

entry value   = reps Γ— tierMultiplier Γ— (comeback? Γ—1.2) Γ— (lightning? Γ—3)
match winner  = max(Ξ£ entry values), +15 to the player who closed

Worked match (real numbers, from the live bot transcript β€” wiki β†’ Bots): Target 150. Ben (couch Γ—1.5) logs 40 push-ups β†’ 60 adj. Nico (athlete Γ—0.85) logs 80 squats β†’ 68 adj. Ben is now >30% behind on raw (40 vs 80 β†’ gap 50%) β†’ comeback armed ⚑. Ben logs 110 push-ups: 110 Γ— 1.5 Γ— 1.2 = 198, and his raw (150) hits the target β†’ match closes, +15 closure bonus. Final: Ben 273 (60 + 198 + 15), Nico 68. Ben closed and won β€” but against a fitter closer, the upset is live: if Nico had also finished, the adjusted column decides, not the clock.

3.2 Fast Battle vs Custom Battle (the two creation paths)

Ben's Figma defines both; the app ships the creation flow, the bots ship the one-line version.

Fast BattleCustom Battle
SetupOne screen: name, days, exercise pack, target tier8-step wizard: schedule & clock β†’ format β†’ exercises & scoring β†’ power-ups β†’ stakes β†’ review ("THE RULES, IN PLAIN ENGLISH") β†’ create
Use it when95% of matches β€” Tuesday-night energyCorporate leagues, charity specials, weird stakes
Where/figma-app create flow (Figma FLOW-02/03); bot new [target] is the chat-native fast pathDesigned in full fidelity in Ben's Figma; rules lock at start, everyone accepts the summary before joining
Stakes stepDefaults (bragging)Bragging Β· dares Β· charity Β· cash-18+ (cash feature-flagged OFF pending legal, Β§6)

3.3 Daily-rhythm multi-day battles (shipped in the app β€” the hybrid, see 3.5)

Worked day (numbers via the documented formula): You (casual Γ—1.25) log 40 push-ups at 7 AM β†’ 50 adj. Sam (fit Γ—1.0) logs 60 squats at 6:30 PM β†’ 60 adj. DZ2 fires at 8 PM. You log 20 push-ups at 8:20 β€” you're >30% behind on raw (40 vs 60), comeback armed β†’ 20 Γ— 1.25 Γ— 1.2 = 30 β†’ You 80, Sam 60. Day closes at 9 PM: "YOU WON TUESDAY" Β· "You beat Sam by 20 RUF today. They're coming for you." Β· pot grew $3 since yesterday's close Β· "NEXT BATTLE DAY: WEDNESDAY β€” logs open now".

3.4 Crew vs Crew (shipped in bots; corporate form designed)

3.5 The rhythm decision β€” his model vs ours, honestly (OPEN, Β§9 D3)

This is the one genuine structural fork left in the battle design, and it deserves a straight presentation rather than a dodge:


4. The handicap system (the moat)

Thesis, verbatim from the code comment: effort and consistency compete, not raw fitness.

4.1 Tier multipliers (v1 β€” live)

Chosen at onboarding (self-set, socially policed), applied to every rep:

TierMultiplier100 raw reps score asExpected weekly volume (anti-sandbag baseline)
CouchΓ—1.5150150 reps
CasualΓ—1.25125250 reps
FitΓ—1.0100400 reps
AthleteΓ—0.8585550 reps

TIER_MULTIPLIERS β€” apps/figma-app/engine.js / packages/game-core/src/handicap.ts; expected volumes: TIER_EXPECTED_WEEKLY_REPS in baseline.ts.

4.2 The comeback multiplier Γ—1.2 β€” "everyone has a shot", mechanically enforced

More than 30% behind the leader in raw reps while the match is live β†’ your next entry is tagged Γ—1.2. Once per player per match. Eligibility recomputes on every log; bot standings mark eligible players ⚑ so the group can see it coming. The boost is tagged onto the entry at log time, not a global multiplier β€” so it composes (a comeback entry inside a Lightning window scores Γ—1.2 Γ—3 = Γ—3.6, Β§5).

COMEBACK_THRESHOLD = 0.3, COMEBACK_MULTIPLIER = 1.2 β€” comeback.ts / engine.js comebackEligible / applyComeback.

4.3 The closure bonus +15

The player whose raw total hits the target first gets +15 adjusted points β€” urgency and glory for the closer, but deliberately small enough that real effort still beats raw speed. A closer who sandbagged their tier wins nothing: +15 doesn't rescue a thin adjusted column. (Found by the test suite β€” a bug in the original bonus placement was caught and fixed before it ever shipped.)

CLOSURE_BONUS = 15 β€” engine.js / match.ts finalStandings / winner.

4.4 Effort handicap v2 β€” %HRR blend (engine-ready, waiting on strap data)

When an entry carries a measured avgHrrPct (heart-rate reserve %, Karvonen, from a BLE strap) and the player has a learned baseline, the multiplier blends live evidence with the declared tier:

effortMultiplier = 0.7 Γ— (avgHrrPct / baselineHrrPct) + 0.3 Γ— tierMultiplier

70% measured, 30% declared. This makes effort itself the score β€” a couch player red-lining scores like a champion; an athlete phoning it in doesn't. The blend math is implemented and tested; it activates the day heart-rate straps land (docs/05 phasing).

HRR_WEIGHT = 0.7 β€” handicap.ts / engine.js effortMultiplier.

4.5 Anti-sandbagging β€” baseline drift (engine-ready)

Self-reported tiers have a loophole: a ringer declares "couch" and farms the Γ—1.5. Two drift correctors close it (baseline.ts, gated to at most one tier step per week):

4.6 The couch-beats-athlete worked example

The full thesis in one table (target 150, from the bot transcript):

Ben β€” couch Γ—1.5Nico β€” athlete Γ—0.85
Log 140 push-ups β†’ 60.0 adjβ€”
Log 2β€”80 squats β†’ 68.0 adj
State40 raw vs 80 raw β†’ 50% behind β†’ comeback armed ⚑leading
Log 3110 push-ups β†’ 110Γ—1.5Γ—1.2 = 198.0 β†’ raw 150 β†’ CLOSESβ€”
Final273.0 (60+198+15 closure) β€” WINNER68.0

The athlete did the bigger single set and led most of the match. The adjusted column says: Ben tried harder per rep, from further back, when it counted. That sentence is the product.


5. Power-ups & the economy

Four cards, live in the app (/figma-app, Figma FLOW-05; engine.js β€” figma-app only, deliberately not yet in game-core). All activations are pure functions returning a new match state + a result card; every activation is audit-logged to powerLog.

CardRarity (drop %)Exact effect
⚑ Lightning Roundlegendary (5%)Reps count Γ—3 for 10 minutes. One activation per player per match β€” the window can expire, the once-per-match flag never resets.
πŸ—‘ Rep Stealepic (15%)Take 10% of the leading rival's raw reps (floor, min 1 while they're above zero), instantly. Both raw ledgers move; entries are logged as ledger transfers, so tier multipliers apply β€” a couch thief gains more adjusted than the athlete victim loses.
πŸ›‘ Shieldcommon (50%)Blocks one Rep Steal against you, then breaks. A blocked steal never fires: the shield is consumed, the thief keeps their card β€” the shield buys the block, not a free kill.
⏱ Time Freezerare (30%)+30 minutes on the play-day deadline, for everyone. The one card that bends time.

Drop odds (DROP_ODDS): common 50 / rare 30 / epic 15 / legendary 5 β€” daily-drop style, one card per battle day in the Figma economy. Rarity is cosmetic in v1: chip colour + drop odds only, no mechanical effect.

The counter-web is the design. Lightning is capped at one activation so it can't be chained. Steal targets only the current leading rival (preview shows exactly what you'd take and whether a shield is up β€” stealPreview), so you can't snipe someone harmless. Shield makes Steal honest: theft against a prepared leader is a wasted card, so steals happen when someone has actually run away with it β€” which is exactly when the table wants a correction. Steals never trigger the closure check; only real logged reps close a match.

Stacking rule: comeback and lightning both tag the entry, so they multiply β€” a couch player's comeback set inside a lightning window is Γ—1.5 Γ—1.2 Γ—3 = Γ—5.4 per rep. Rare, loud, and earned from behind. That's the point.

What is deliberately unpriced. Nothing in the current build can be bought: cards are granted (grantPowerUp / daily drop), never sold. Ben's Figma designs the full economy β€” store packs ($1.99–$4.99), daily loot chest, Reps Pro subscription with 12 inventory slots β€” under one hard rule we adopt verbatim: "Cosmetic and convenience only β€” you can't buy the win." Whether any of that ships in the MVP is open decision #4 (Β§9).


6. The social layer


7. Progression & retention

The retention stack answers the week-3 cliff (docs/01 Β§5) with four layers β€” each one cheap, each compounding:

  1. Seasons (live). 4-week series. Every finished match feeds<br><code>recordMatch</code>: 1st +3, 2nd +2, 3rd +1, MVP vote +1. Ladder sorts<br>points β†’ wins β†’ MVPs. At season end: champion crowned (the belt β€”<br>physical trophies proposed, docs/07 F), and <strong>A/B division swap</strong> β€”<br>bottom of A ↔ top of B β€” so mid-table stays meaningful all season.<br><code>season.ts</code>; app <code>48-49-season-ladder.png</code>; bot <code>season ladder</code>.
  2. Streaks + forgiveness (live). Streaks count played weeks (you chose<br>your days β€” no streak guilt for resting). A streak about to break can be<br>saved <strong>once per season</strong> by topping the charity pot <strong>β‰₯ $2</strong> β€”<br>preserved, not extended. Guilt converts to pot money; no free rides<br>(<code>FORGIVE_MIN_CENTS = 200</code>, <code>forgiveStreak</code>).
  3. The avatar/creature system β€” the identity layer (in active build).<br>The founder's tamagotchi concept: your avatar evolves with your<br>training. The reference creature is the dragon β€” three stages already<br>modelled and rigged: <strong>Hatchling</strong> (round, stub-winged, all eyes) β†’<br><strong>Fledgling</strong> (wings come in, nub horns, first spikes) β†’ <strong>Elder</strong><br>(<code>site/avatar-styles/dragon2.js</code> β€” stages driven by a <code>stage</code> param,<br>ready to wire to season/streak milestones). Learned the hard way<br>(documented post-mortem, <code>notes_avatars_investigation.md</code>): creatures are<br>built on a <strong>dedicated creature rig</strong> (<code>site/creature-rig.js</code>, horizontal<br>silhouette β€” a dragon doesn't squat), never as humans-in-suits. The same<br>pipeline already carries 11 humanoid styles + 12 rigged GLBs across<br>species; the Outfit Atelier (<code>/atelier</code>) stress-tests garments through<br>every extreme exercise pose. Design intent: your creature grows when you<br>show up β€” the identity layer makes absence <em>visible</em> and progress<br><em>adorable</em>.
  4. Awards & badges (designed, from Ben's file β€” adopt). Auto-computed on<br>final results: πŸ… most consistent Β· πŸš€ best comeback Β· ⚑ most active Β·<br>πŸ’ͺ personal best β€” zero coordination, covers the &quot;effort β‰  winning&quot; value<br>the MVP vote serves. Plus PB detection cards for the non-competitive half<br>(docs/07 G-29).

Non-winner retention is a stated design lane in Ben's architecture ("the loop must create a social ripple even when you lose") and everything above feeds it: comeback, MVP, awards, nemesis, revenge rematch prompts, daily fresh starts.


8. Verification & fairness β€” the trust ladder

The existential technical problem (docs/01 Β§5): if reps are self-reported the game is a lying contest; if verification is annoying nobody plays. The answer is a ladder, not a gate β€” every rung is optional and every rung is visible:

RungMechanismStatus
1 Β· HonourSelf-report + socially-policed tier + crew chat visibility (liar gets roasted)Live β€” the default
2 Β· CameraIn-browser pose counting (MoveNet; push-ups/squats via angle-threshold state machine). On-device only β€” no video ever leaves the phone. Entry logs verified:true; standings show each player's verified %Live in app (apps/web/src/verify/); bot grammar log pushups 25!
3 Β· Heart rateBLE straps via Web Bluetooth (GATT 0x180D), Karvonen %HRR per set β†’ avgHrrPct on the entry β†’ feeds the v2 handicap blend (Β§4.4) β€” effort becomes verifiable even when reps aren'tEngine live; strap hardware pending (docs/05)
4 Β· PeersLong-press any log β†’ challenge (typo / impossible pace / repeated pattern), anonymous, 3 per battle, misuse lowers your trust score; dispute timeline; admin queueDesigned in Ben's Figma (V1) β€” sequence decision Β§9
5 Β· CloudApple Watch / HealthKit, WHOOP/Garmin cross-check, recovery-adjusted handicapPhase 3 (docs/05)

Server-authoritative everything (Ben's engineering posture, adopted): the server computes all scoring, multipliers and deadlines; the client never decides a danger-zone level. The camera rung is deliberately the positive verification ("honest by design" β€” show off) with peer challenge as the social backstop, in that order (the sequencing itself is one of the adjacent open calls, Β§9).


9. The five open decisions

Full evidence and the 30-row divergence log: figma/notes/analysis.md (Β§4, Β§6 β€” the founder-call one-pager). The five that reshape the game:

β‘  Fairness model β€” the deepest divergence

β‘‘ Platform / distribution

β‘’ Battle rhythm

β‘£ Power-ups in the MVP?

β‘€ Brand β€” gold vs lime

Adjacent decisions already leaning resolved β€” money at launch (charity-only, wagers flagged; D17) and verification sequence (camera MVP β†’ peer challenges V1; D10) β€” both with Ben's craft adopted and our shipped work kept.


10. Numbers appendix β€” every constant, one table

All values verified against the code on 2 Sep 2026. Any change to a number here must update this table, the wiki Game Rules page, and the tests.

ConstantValueWhere it lives
Default match target300 raw reps ("Light" = 150)match.ts / engine.js createMatch Β· bot new [target]
Tier multiplierscouch Γ—1.5 Β· casual Γ—1.25 Β· fit Γ—1.0 Β· athlete Γ—0.85TIER_MULTIPLIERS β€” engine.js / handicap.ts
Comeback threshold>30% behind leader (raw)COMEBACK_THRESHOLD = 0.3 β€” comeback.ts / engine.js
Comeback multiplierΓ—1.2, once per player per match, tagged on entryCOMEBACK_MULTIPLIER β€” comeback.ts / engine.js
Closure bonus+15 adjusted, to the closer onlyCLOSURE_BONUS β€” engine.js / match.ts
Lightning window10 minutes, Γ—3 rep value, 1 activation/player/matchLIGHTNING_MS, LIGHTNING_MULTIPLIER β€” engine.js
Rep Steal share10% of leading rival's raw (floor, min 1)STEAL_SHARE = 0.1 β€” engine.js
Time Freeze+30 min on the deadlineFREEZE_MS β€” engine.js
Power-up drop oddscommon 50% Β· rare 30% Β· epic 15% Β· legendary 5%DROP_ODDS β€” engine.js
Play-day deadline21:00 Australia/Sydney (dual clock; freeze extends)daily.js deadlineFor Β· state.js playDayEndMs
Danger zone rampDZ1 ≀3h gold Β· DZ2 ≀1h orange Β· DZ3 ≀30min reddaily.js dangerLevel
Daily tie-breakadjusted β†’ raw reps β†’ earliest first entrydaily.js dailyStandings
HR blend weight0.7 measured + 0.3 declared tier (v2)HRR_WEIGHT β€” handicap.ts / engine.js
Baseline HR driftmax 10% per updateHR_DRIFT_MAX = 0.1 β€” baseline.ts
Sandbag volume ratio>1.3Γ— expected weekly reps β†’ drift one tier fitter (max 1 step/week)VOLUME_SANDBAG_RATIO = 1.3 β€” baseline.ts
Expected weekly reps/tier150 / 250 / 400 / 550 (couch→athlete)TIER_EXPECTED_WEEKLY_REPS — baseline.ts
Photo finishtop two within 5% of leader's adjustedPHOTO_FINISH_PCT = 5 β€” photo-finish.ts
Season length4 weeksseason.ts createSeason
Season points1st +3 Β· 2nd +2 Β· 3rd +1 Β· MVP +1recordMatch β€” season.ts / engine.js
Streak forgivenessβ‰₯ $2 to pot, once per season, preserves (not extends)FORGIVE_MIN_CENTS = 200 β€” season.ts / engine.js
Nemesis eligibilityβ‰₯2 shared matches, β‰₯1 lossnemesis.ts
Result card1200Γ—675 (16:9), SVG from bots / PNG from apppackages/bot-core/src/card-image.ts
Crew codeCREW-XXXXX, 5 chars, alphabet excludes 0/O/1/I (design rule)docs/13 Β§21 Β· generated in apps/figma-app/state.js crewCode()
Bot commands15 commands + aliases (s,h,again,runitback,nem,monday)bus.ts COMMANDS / ALIASES Β· wiki β†’ Bots

Proof of correctness (run any of these yourself):

cd packages/game-core && bun test   # 59 tests, 134 expect() calls β€” engine spec
cd apps/figma-app   && bun test engine.test.js   # 23 parity tests: TS vs JS side by side
cd packages/bot-core && bun test    # 64 tests β€” command bus, cards, AI fallback
cd apps/figma-app   && bun e2e.mjs && bun e2e-daily.mjs   # full app walks, injected time

90+ green across the repo, CI-gated on every push (docs/17).


Cross-links: docs/25_DESIGN_BRIEF.md (design handout) Β· docs/01 (Ben's brief + market) Β· docs/02 (phasing) Β· docs/05 (verification research) Β· docs/07 (element backlog A–G) Β· docs/08 (legal) Β· docs/11 (systems) Β· docs/13 (our MVP spec + 30-row divergence log) Β· docs/17 (progress) Β· figma/notes/analysis.md (the Figma audit + founder-call one-pager) Β· wiki: /wiki/game.html, /wiki/app.html, /wiki/bots.html, /wiki/verification.html, /wiki/avatars.html, /wiki/status.html.