Consolidated edition 1.9 · Pre-production
Fizz Frenzy
Full Specification
A 4-player grid brawler for iOS and Android. Anthropomorphic critters called Chuggers drop shaken soda cans and run. Two seconds later the can bursts, spraying foam down four lanes. Last Chugger fizzing wins.
Fizz Frenzy — Full Specification¶
Consolidated edition 1.9 — Pre-production Compiled 28 July 2026
A 4-player grid brawler for iOS and Android. Anthropomorphic critters called Chuggers drop shaken soda cans and run. Two seconds later the can bursts, spraying foam down four lanes. Last Chugger fizzing wins.
This document is the single source of truth. It is a compilation of five component documents, each preserved whole and unedited so that the set can be split apart again at any time.
Component Documents¶
| Part | Document | Version | Source file |
|---|---|---|---|
| Part 1 | Gameplay Rules & Mechanics | 0.4 | fizz-frenzy-gameplay-rules.md |
| Part 2 | Application Framework & Flow | 0.11 | fizz-frenzy-framework-and-flow.md |
| Part 3 | Design Style Guide | 0.6 | fizz-frenzy-style-guide.md |
| Part 4 | Visual Assets & Design Architecture | 0.17 | fizz-frenzy-asset-architecture.md |
| Part 5 | Economy & Progression | 0.3 | fizz-frenzy-economics.md |
The order is deliberate. Parts 1 and 2 are the technical half — what the simulation does, and what the application around it does. Parts 3 and 4 are the production half — what it looks like, and how the assets that make it look that way are cut and shipped. Reading straight through crosses from build to craft exactly once, instead of alternating. Part 5 stands apart from both: the economy — what a player earns, owns and spends across their lifetime on the game.
Consolidated edition 1.9 is not a sixth version — it is Parts 1–5 at the versions above, bound together. When a part is revised, its own version number moves and the consolidated edition is recompiled.
Edition 1.9 closes one question — the age rating, at 13+ (decision 83, Part 2 §15 question 6) — and corrects two stale cross-references in Part 5. Part 2 moves to 0.11, Part 5 to 0.3. No rule of play changed.
How This Document Splits¶
Each part is a level-1 heading. Nothing else in the document uses level 1. To divide the specification again:
- Split on each standalone HTML-comment line containing the words PART BREAK. Match whole lines, not the phrase — this paragraph mentions it in prose, and only the comment lines are delimiters. Do not split on the heading itself: Part 4's Appendix A contains worked map files whose ASCII grids begin with hash characters inside fenced code blocks, and a naive heading splitter will cut a map example in half
- Discard this front matter, which is everything above the first delimiter line
- Restore each part's original heading by replacing its "Part N" title line with the "FIZZ FRENZY" title shown in the table above
- Save under the source filename in the table above
A note from experience: an earlier revision of these instructions reproduced the delimiter verbatim, which meant the front matter contained a copy of its own separator. Every recompilation then split there and silently dropped the six sections below this one. If you edit this section, do not paste the literal delimiter into it.
Within each part, headings begin at level 2 and nest from there. No renumbering was applied during compilation — section numbers are exactly as they appear in the standalone documents, which is what makes the split lossless.
Reading section references¶
- A bare reference — §6.5 — points to a section of the same part
- A reference naming a document — Gameplay Rules §9.1 — points into another part
This convention is unchanged from the standalone documents and survives both compilation and splitting.
Who Reads What¶
| Role | Read first | Then |
|---|---|---|
| Gameplay programmer | Part 1, entire | Part 2 §§7–8 |
| Netcode programmer | Part 2 §7 | Part 1 §13 |
| Server programmer | Part 2 §§6–7, 11, 17 | Part 1 §13, Part 5 §§5, 7 |
| Client programmer | Part 2 §§3–5, 13 | Part 4 §§9–10, Part 2 §17.11, Part 5 §6 |
| Level designer | Part 4 Appendix A | Part 1 §§3, 8 |
| Artist, character | Part 3 §§1–6 | Part 4 §§2–4, 7 |
| Artist, environment | Part 3 §§1–3, 7–9 | Part 4 §§5, 12 + Appendix A |
| UI designer | Part 3 §§4, 11 | Part 2 §5, §17.12, Part 5 §6 |
| Producer | This front matter | Part 4 §13, Part 2 §1, Part 5 entire |
Part 1 is the document a programmer implements the game from. Part 2 is the application around it; Parts 3 and 4 are production references.
Ownership¶
Where two parts describe the same subject, this table decides which one governs.
| Subject | Owner |
|---|---|
| Simulation rules, movement, collision, blast, pickup effects and caps, balance constants, resolution order | Part 1 — Gameplay Rules |
| Screens, navigation, state machines, netcode, session model, persistence, player count, grid dimensions | Part 2 — Framework & Flow |
| Leagues, leaderboards, weekly progression, club allocation | Part 2 — Framework & Flow |
| Colour values, silhouette rules, character design, arena mood, VFX budget, audio | Part 3 — Style Guide |
| Asset slots, naming, canvas sizes, atlases, draw order, map file format, validation, pickup appearance | Part 4 — Asset Architecture |
| Currency, XP, prices, yields, level curve, rollover payout policy | Part 5 — Economy & Progression |
Two spans worth stating explicitly:
- HUD — Part 3 owns appearance, Part 2 owns structure and position
- Pickups — Part 1 owns effect, cap and drop weight; Part 4 owns silhouette, animation and legibility
- Leagues — Part 2 owns the ladder, the rollover and the screen structure; Part 3 §3.4 owns tier and band colour; Part 4 §6.6 owns the slots. Badges are Core, non-variant — no pack, skin or flavour may override one
Locked Decisions¶
Every decision taken across the five documents, in one table. Each is expanded in the part cited.
Product¶
| # | Decision | Where |
|---|---|---|
| 1 | Title Fizz Frenzy; player characters are Chuggers; in-world soda brand is Frenzy Cola | P3 §2 |
| 2 | Launch arenas: Diner and Beach Kiosk | P3 §§8–9 |
| 3 | Launch cast: four Chuggers — Rax, Puff, Nim, Bolt | P3 §5 |
| 4 | 4 players. Six is a costed version 2 refactor, not a deferred switch | P2 §1.1 |
| 5 | Grid is 11 columns × 13 rows, portrait, fixed for every map | P2 §1.2, P1 §3.2 |
| 6 | Platforms: iOS and Android, phone and tablet. Unity + Nakama | P2 §0 |
The board¶
| # | Decision | Where |
|---|---|---|
| 7 | The map file defines every tile — solids, destructibles, spawns, hazards. No procedural fill, no engine-placed lattice, no forced corners | P1 §3.1 |
| 8 | Corridor exit rule — every walkable tile has a perpendicular exit within 3 tiles. Replaces the fairness guarantee the pillar lattice silently provided | P1 §3.4, P4 §A.7.16 |
| 9 | players is spawn capacity, not match size. A match may seat fewer, never more |
P4 §A.4 |
| 10 | Fewer players than spawns → maximum pairwise separation subset, reading-order tie-break | P1 §3.3 |
| 11 | Every round replays the identical authored board. No reseeding | P1 §11 |
| 12 | Maps: bundled baseline, server adds, additive only. IDs permanent and namespaced; server maps validated on upload | P4 §11.1, P2 §10 |
| 13 | No dedicated 2-player maps in v1. Duels run on 4-spawn maps | P4 §11.2 |
| 14 | Version 1 supports one hazard type — a periodically lethal tile (H) |
P1 §12.1 |
Gameplay¶
| # | Decision | Where |
|---|---|---|
| 15 | Continuous movement with grid-aligned collision and corner assist. Not tile-stepping | P1 §§4.1–4.2 |
| 16 | Players pass through each other | P1 §4.3 |
| 17 | Base speed 3.0 tiles/s. No permanent speed upgrade | P1 §4.4 |
| 18 | Every can is pre-shaken. Fixed 2.0s fuse, single-tap drop, no charge input, no meter | P1 §5.3, P2 §8.7 |
| 19 | Walk-off rule — step off a can you placed, never back onto it | P1 §5.2 |
| 20 | Blast is a cross; each arm stops at the first destructible or solid block | P1 §6.2 |
| 21 | Your own blast kills you | P1 §6.4 |
| 22 | Damage window 0.15s; VFX 0.4s. Only the window is lethal | P1 §6.3 |
| 23 | Chain detonation is immediate, resolved to a fixed point within one tick | P1 §5.4 |
| 24 | Blasts destroy uncollected pickups. Arm continues | P1 §6.5 |
| 25 | A sliding can entering a live blast tile detonates on contact | P1 §9.1 |
| 26 | A kicked can keeps its original fuse; sliding cans do no contact damage | P1 §9 |
| 27 | One life per round. No lives system, no respawn | P1 §7 |
| 28 | Eliminated players spectate, with Leave available. Ghost mode is post-launch | P1 §7.1 |
| 29 | Simultaneous death is a draw — no point awarded, round replayed | P1 §7, §11.1 |
| 30 | Sudden death from T−30s, one indestructible ring every 4s | P1 §12 |
| 31 | 2-player duels use the identical arena, grid and ruleset as 4-player | P1 §11.2 |
| 32 | The Beach Kiosk seagull power-up theft is cut; seagulls are ambient only | P1 §12.2, P3 §9 |
Pickups¶
| # | Decision | Where |
|---|---|---|
| 33 | Six effects, two classes. Buffs visible on the field; debuffs only inside a Surprise Cube | P1 §8.1, P4 §6.2 |
| 34 | Caps — Shaker 6 (max range 7), Extra Can 5 (max 6 cans), Ice Cube 3 | P1 §8.2 |
| 35 | Timed effects refresh, never extend. One movement modifier at a time, last wins | P1 §8.3 |
| 36 | Glue is 3s, Coffee Bean and Honeycomb Wax 5s | P1 §8.3 |
| 37 | Cube pool: all six effects at flat weight — 2/3 buff, worth opening | P1 §8.4 |
| 38 | The cube pool and the drop table are separate tables. A cube never contains a cube | P1 §8.4, P4 §11.0 |
| 39 | First cube of a new account is rigged to roll a buff. Server rule, no assets | P1 §8.4, P4 §11 |
| 40 | One drop source — every pickup spawns from a destroyed block, at that tile | P1 §8.5 |
| 41 | Drop weights are declared in the map header and required there. No pack default, no global config | P1 §8.5, P4 §A.4 |
| 42 | Power-ups are non-variant Core assets — identical in every arena and skin | P4 §1 |
| 43 | At cap, a pickup is still collectable and plays a capped pop | P1 §8.2 |
Production¶
| # | Decision | Where |
|---|---|---|
| 44 | Five variant axes — Core, Arena, Character, Blast, Can Flavour | P4 §1 |
| 45 | A variant fills a fixed slot manifest and never introduces new assets. Build-validated | P4 §1 |
| 46 | Only Classic can flavour ships at launch; the axis is reserved | P4 §5.4 |
| 47 | Blast skins are cosmetic-only. Store copy may not imply mechanical advantage | P4 §5.3 |
| 48 | Every character skin ever authored must ship a kick animation — build-enforced | P4 §4 |
| 49 | Three arena tiers — solid, destructible, hazard. The Reinforced two-hit tier is not built in v1 | P3 §7, P2 §16.2 |
| 50 | Maps are plain ASCII text files scoped to an arena pack | P4 Appendix A |
| 51 | Greyscale legibility at 32px is a release blocker, not a guideline | P4 §8 |
| 52 | No lives asset — hud/lives is vestigial, cut or repurposed as round pips |
P1 §7, P4 §6.4 |
| 74 | Pack sources live outside the engine project, at Content/; Core lives inside it at Assets/Game/Art/Core/. Maps live inside the pack directory |
P4 §2 |
| 75 | The three raster exports are sibling directories art/@1x\|@2x\|@3x, same filename in each. Core is exempt and authors one @3x set |
P4 §4 |
| 76 | Procedural fill stays cut in the tooling too — no fill_density, fill_weights or drop_rate fields anywhere. drop_weights is the only tuning key and it is required per map |
P4 §A.4, Pack Studio T4 |
Application¶
| # | Decision | Where |
|---|---|---|
| 53 | Silent device authentication. Registration is account linking, never a gate | P2 §6 |
| 54 | Authoritative server match handler, TypeScript, 15 ticks/s | P2 §7.2 |
| 55 | Clients predict movement only — never cans, blasts or pickups | P2 §7.2 |
| 56 | No pause in multiplayer. Single player only | P2 §8.4 |
| 57 | AI difficulty comes from behaviour and reaction delay, never stat cheating | P2 §8.5 |
| 58 | Menu screens are panels in a stack, not Unity scenes. Only Match loads and unloads |
P2 §13 |
| 59 | Currency and ownership are server-write only | P2 §11 |
| 60 | Chat exists in Lobby and Results only, and is quick-chat only — a fixed phrase set, no free text. Upgraded from a recommendation to a lock by decision 83 | P2 §9, §15 Q6 |
| 61 | Arena packs are playable when unowned, purchasable to host | P2 §10 |
| 62 | TLS is required before store submission — cleartext ws:// fails iOS ATS |
P2 §6.4 |
| 83 | Age rating 13+. No COPPA verifiable-consent path and no Designed for Families track. Chat stays quick-chat-only with server-side filtering, analytics is a consent-gated GDPR flow, and no ad SDK ships at launch | P2 §15 Q6, P5 §1.3 |
Progression¶
| # | Decision | Where |
|---|---|---|
| 63 | Six league tiers — Tin, Bronze, Silver, Gold, Diamond, Elite. Every account starts in Tin | P2 §17.2 |
| 64 | Clubs of 50. A tier shards into as many clubs as its population needs; assignment is a random shuffle seeded on the week ID, never score-seeded | P2 §17.4 |
| 65 | One league point per round won, plus one for completing the match. Nothing else scores, and points never decrease | P2 §17.3 |
| 66 | Rollover is Sunday 22:00 UTC, a fixed instant with no daylight adjustment. Clients show a duration, never a wall clock | P2 §17.5 |
| 67 | Demotion is 30% at every tier except Tin. Promotion is 30% below Gold and 20% at Gold and Diamond — 15 up from Tin, Bronze and Silver, 10 from Gold and Diamond. Tin never demotes, Elite never promotes | P2 §17.2 |
| 67a | The taper is what makes the ladder a pyramid. Flat counts settle every tier at equal population; 20-up-against-30-down settles the top two at two-thirds of the tier below, putting Elite near 8.7% | P2 §17.2.1 |
| 68 | Only matchmade Quick Match awards league points. Private, browsed and single-player matches award zero — this is the anti-collusion rule, not a scope cut | P2 §17.9 |
| 69 | Leagues are server-authoritative in full. The client displays a standing it never computes and never writes; cut lines arrive as ranks from the server | P2 §17.10, §17.11 |
| 70 | Cut counts, point yield, club size and the cutoff instant are server config, tunable without a client build | P2 §17.10 |
| 71 | League art is Core, non-variant — six tier badges carried by silhouette, not fill. ~40 assets | P4 §6.6, P3 §3.4 |
| 72 | The demotion band never uses the warm hazard band. Warm means lethal, everywhere, permanently | P3 §3.4 |
| 73 | No rank, badge or band renders on the play field or in the match HUD. Status is a menu concept | P3 §3.4, §11 |
Economy¶
| # | Decision | Where |
|---|---|---|
| 77 | One currency: Sugar Cubes, earned and purchased. No premium-only currency | P5 §1 |
| 78 | Sugar Cubes never buy anything competitive — no points, XP, promotion or power | P5 §1.1 |
| 79 | XP is lifetime; player level = 35·n² cumulative, no cap. Level-ups pay Sugar Cubes | P5 §2 |
| 80 | Single-player rewards capped at 5 matches per UTC day | P5 §1.2 |
| 81 | The rollover pays nothing in v1 — leagues ship as pure status; the reward strip stays reserved | P5 §3.2 |
| 82 | Yields, prices and caps are server config (economy_config), tunable without a client build |
P5 §7 |
Open Questions¶
Nothing on this list is a rule. Every open rule was closed; what remains is tuning, scope and commercial policy.
Tuning — only playtesting settles these¶
| # | Question | Part |
|---|---|---|
| 1 | Corner assist threshold — 0.35 tiles? | P1 §4.2 |
| 2 | Base speed — 3.0 tiles/s? Changing it re-verifies the escape equation | P1 §4.4, §10 |
| 3 | Sudden-death ring interval — 4s? No reference point exists | P1 §12 |
| 4 | Corridor exit distance — 3 tiles hard, 6 advisory? Derived, but verify against a real authored map | P1 §3.4 |
| 5 | Drop table weights against the Shaker cap of 6 | P1 §8.5 |
| 5a | Economy numbers — yields, prices, curve coefficient. All starting values in server config; soft-launch data settles them | P5 §11 |
Scope¶
| # | Question | Part |
|---|---|---|
| 6 | How many maps in the bundled baseline? Three is the manifest floor. With no procedural fill and no reseeding, map count is replayability — five or six per pack is a better aim | P4 §5.1 |
| 7 | ~~Does single player feed progression?~~ Closed: yes, capped. No league points (P2 §17.9); full XP and Sugar Cubes for the first 5 single-player matches per UTC day, zero beyond | P5 §1.2 |
| 7a | Is 22:00 UTC the right instant for the intended launch region? The fixed-offset decision stands regardless of the number | P2 §17.5 |
| 7b | Does the taper hold in practice? Elite at 8.7% is an equilibrium reached over several weeks and distorted upward by frozen accounts. If it inflates after four weeks of live data, Diamond's promotion count is the first lever | P2 §17.2.1 |
| 7c | Quick Match must never expose a round-count option while the participation point is flat, or short matches become the optimal grind. A standing constraint rather than a question — recorded here because it will be forgotten | P2 §17.3 |
Commercial and legal¶
| # | Question | Part |
|---|---|---|
| 8 | Region routing. One server in Germany means poor US and bad APAC latency | P2 §15 |
| 9 | IAP validation — through Nakama's hooks or a separate service. Owned by implementation-plan M0, because a separate service is a second host and a second certificate — the same decision as the TLS hostname | P2 §15 |
| 10 | ~~Age rating and COPPA~~ Closed: 13+ — decision 83. The chat policy, the consent flow and the SDK list follow from it; the no-gambling half was already closed by P5 §1.3. Residual: GDPR Article 8's digital-consent age is 16 in Germany, the soft-launch region, so consent-based analytics for 13–15 year olds in the EU needs parental authorisation or a lawful basis that is not consent | P2 §15 |
Known Gap — Closed¶
The economy specification exists. Part 5 defines the three numbers that live on an account — XP (lifetime), league points (weekly, owned by Part 2 §17) and Sugar Cubes (the currency) — along with prices, yields, the level curve, purchase validation, the match-end reward flow and the anti-farm rules. The Store, the Profile screen and the reward step of Match Results are unblocked.
The rollover-payout question the gap left open is decided: leagues pay nothing in version 1. Sugar Cubes stay sparse and the ladder ships as pure status (Part 5 §3.2). The Leagues screen keeps its reserved reward strip, and a tier × movement payout shape is pre-approved if payouts are ever added — without touching Part 2 §17's placement logic.
Glossary¶
| Term | Meaning |
|---|---|
| Chugger | A player character. Four at launch: Rax, Puff, Nim, Bolt |
| Frenzy Cola | The in-world soda brand. All cans are Frenzy Cola |
| Can | The bomb. Pre-shaken, fixed 2.0s fuse |
| Blast | The foam explosion. A cross of four cardinal arms |
| Blast range | Arm length in tiles. Base 1, +1 per Shaker, max 7 |
| Shaker | Class A pickup, +1 blast range, cap 6 |
| Extra Can | Class A pickup, +1 simultaneous can, cap 5 |
| Ice Cube | Class A pickup, unlocks kick at stack 1, cap 3 |
| Coffee Bean | Class B timed buff, speed ×1.4 for 5s. Cube-only |
| Honeycomb Wax | Class B timed debuff, speed ×0.6 for 5s. Cube-only |
| Glue | Class B timed debuff, no can placement, 3s. Cube-only |
| Surprise Cube | Opaque ? container. Rolls one of six effects at flat weight |
| Drop table | Per-map weights for what a destroyed block yields. Five entries |
| Cube pool | Fixed six-effect flat table for what an opened cube contains. Not the drop table |
| Kick | Walking into a can while holding an Ice Cube, sending it sliding |
| Corner assist | Automatic alignment nudge into an adjacent open lane |
| Corridor exit rule | Every walkable tile needs a perpendicular escape within 3 tiles |
| Walk-off rule | You may leave the tile of a can you placed; you may not return |
| Spawn capacity | A map's spawn count. A match may seat fewer players, never more |
| Sudden death | Closing rings of indestructible blocks from T−30s |
| Arena pack | A downloadable bundle: one theme, its slot manifest, and its maps |
| Variant axis | One of five dimensions along which assets vary |
| Slot manifest | The fixed list of assets a variant must supply to build |
| Live-blast tile set | Tiles lethal for 0.15s. The only simulation state crossing ticks |
| League tier | One of six rungs: Tin, Bronze, Silver, Gold, Diamond, Elite |
| Club | A 50-player cohort within one tier, competing for one week. The whole of the leaderboard a player sees |
| League point | One per round won, plus one for completing a matchmade game. The unit of weekly progression. Spent on nothing, converted to nothing — P5 §3.1 |
| Rollover | Sunday 22:00 UTC. Promotions and demotions resolve, clubs are re-dealt, scores reset |
| Cut line | The promotion or demotion rank in a club. Promotion is 30% below Gold and 20% above; demotion is 30% except in Tin. Sent by the server, never derived by the client |
| The taper | Promoting 10 rather than 15 from Gold and Diamond. What makes the ladder a pyramid instead of a column |
| Frozen | An account that played nothing this week. Holds its tier rather than demoting |
| Sugar Cube | The currency. Earned from matches and level-ups, purchasable in bundles, spent in the store. Never buys anything competitive — P5 §1 |
| XP | Lifetime experience. Never resets, never decreases. Earned per match in every mode, single-player capped daily — P5 §2 |
| Player level | Derived from XP: cumulative 35·n², no cap. Pays Sugar Cubes on level-up. Menu-only status — P5 §2 |
Part 1 — Gameplay Rules & Mechanics¶
Version 0.4 — Pre-production
Changes in 0.4: Drop weights are declared per map and required (§8.5). Spawn subset selection added (§3.3) — with no duel maps in v1, a 2-player match runs on a 4-spawn map and must pick the right two. Cube pool and drop table explicitly separated (§8.4). Changes in 0.3: The map defines everything. No engine-placed pillar lattice, no procedural fill, no forced corner spawns — every tile is authored (§3). Rounds no longer reseed. Elimination locked to spectate (§7.1). New corridor-exit validation rule (§3.4) to replace the fairness guarantee the lattice was silently providing. Changes in 0.2: Six open questions closed — seagull cut, blasts destroy pickups, Shaker cap lowered to 6, drop weights are per-pack with per-map override, 2-player uses the 4-player arena and ruleset, remote detonator scheduled for v2. New rule: a sliding can entering a live blast detonates on contact (§9.1). Live-blast tile set added to the resolution order.
One of five companion documents, and the one a programmer implements from. The Style Guide says what things look like, the Asset Architecture says how they are cut up and shipped, the Framework & Flow says what screens exist and who talks to the server. This document says what happens on the grid.
Orientation: this is a classical Bomberman ruleset. Where a rule is a genre convention, it is stated as such and adopted without argument — conventions in this genre are load-bearing, and players arrive already knowing them. Where Fizz Frenzy deliberately diverges, the divergence is marked ▲ DIVERGENCE and justified. Everything else is invention, and there is deliberately very little of it.
1. Ownership¶
| Subject | Owner |
|---|---|
| Simulation rules, movement, collision, blast, timing, balance constants | This document |
| Pickup effects, caps, stacking, drop table | This document |
| Pickup silhouettes, animation, icon rules, shape law | Asset Architecture §6.2 |
| Colour law, readability, VFX budget | Style Guide |
| Screens, netcode, session, match lifecycle | Framework & Flow |
Where this document and the asset architecture describe the same pickup, this one governs behaviour and that one governs appearance. Neither is a copy of the other.
2. The Core Loop¶
- Four Chuggers spawn at the positions the map defines, on an 11×13 grid dense with destructible blocks
- Each starts with one can, blast range 1, and no upgrades
- Dropping a can destroys blocks, opens paths, and reveals pickups
- Pickups increase range, can count, and unlock the kick
- The last Chugger alive wins the round
- First to win a majority of the configured rounds wins the match
A round is won by being alive, not by scoring. There are no points, no kill counts and no objectives. This is the genre contract and it is not negotiable — every mechanic below exists to make survival interesting.
3. The Grid¶
3.1 The map defines everything¶
Every tile on the board comes from the map file. The engine places nothing. There is no pillar lattice, no fill density, no random seeding, no assumed corner spawn. A .map file is a literal, complete description of the starting board.
| Element | Source |
|---|---|
| Solid blocks | Map glyphs #, %, @, & — at any position the author chooses |
| Destructible blocks | Map glyphs 1–4 — explicit, never probabilistic |
| Empty floor | Map glyph . |
| Player spawns | Map glyph S — anywhere on the board, not necessarily corners |
| Hazard tiles | Map glyph H |
The single exception is pickup seeding, which remains procedural: pickups spawn from destroyed blocks against the drop table (§8.5), seeded from the match seed so all clients agree.
This is a real simplification and it buys three things:
- What the author sees in the text file is what plays. No mental model of "this
?might become a block." A map can be reasoned about by reading it - Validation becomes exact rather than probabilistic. Connectivity, spawn safety and corridor length can be checked as facts about the file instead of as properties that hold for most seeds
- Full customisation. Asymmetric maps, duel maps, tutorial maps, unusual geometries — all become authoring decisions rather than engine features
3.2 Dimensions¶
11 columns × 13 rows, fixed. ▲ This is the one thing the map does not define.
The portrait layout budget (Framework & Flow §5.8.1) derives tile size from a fixed grid: 11 columns at ~98 logical px on the reference phone, against an 80px minimum for touch accuracy. A map declaring 13×17 would push tiles to 76px and break the minimum; a map declaring 9×11 would waste a fifth of the screen.
Version 1 maps are all exactly 11×13. The validator enforces it. Variable dimensions are a version 2 feature that arrives with a camera-fit rewrite, not a map format change.
- Columns index
x0–10 left to right, rows indexy0–12 top to bottom - The border — row 0, row 12, column 0, column 10 — must be solid in every map. A blast leaving the grid is undefined behaviour, and the map makes it impossible by construction
- That leaves 99 interior tiles for the author to fill however they like
3.3 Spawns¶
Spawns are S glyphs, placed anywhere, assigned in reading order (top-left first). A map declares players: 2–4 and must carry exactly that many.
Corners are conventional, not required. A map may spawn players along one edge, in a ring, or clustered — the validator checks fairness, not shape.
What the validator does enforce (Asset Architecture §A.7):
- Each spawn has at least two orthogonally adjacent empty tiles, each with an empty neighbour of its own. A player who can be boxed in by their own first can has been killed by the level designer
- Minimum spawn separation of 6 tiles Manhattan
- Every spawn can reach every other spawn with all destructibles removed
A note for whoever authors maps: on an 11×13 field, corner spawns are not equidistant — vertical pairs sit 12 tiles apart, horizontal pairs 10. If you use corners, compensate through layout.
Spawn subset selection¶
players in the map header is spawn capacity, not match size. Every v1 map declares 4 and carries four spawns, and no dedicated 2-player maps ship in version 1 — so a 2- or 3-player match runs on a 4-spawn map and the engine must choose which spawns to use.
Rule: choose the subset of size N that maximises the minimum pairwise Manhattan distance between chosen spawns. Ties break by reading order, so the result is deterministic across clients.
On a conventional corner map with two players this selects a diagonal pair, which is the correct opening. Reading order would have selected the two top corners — adjacent, 10 tiles apart on a board where the diagonal is 22 — and turned a duel into a brawl at tick zero.
The rule is one line of code and it is doing real design work. Without it, every 2-player match on every map opens in the wrong place.
3.4 The corridor rule — what the lattice was quietly doing¶
Removing the fixed odd/odd pillar lattice removes something that was never written down: a guarantee that a perpendicular escape route was never more than one tile away.
That guarantee is load-bearing. The escape equation (§10) assumes a player caught in a blast lane can step sideways out of it. With arbitrary author-placed solids, a long straight corridor with no side exits is now expressible — and at max blast range against a slowed player, it is an inescapable death lane that looks perfectly innocent in a text editor.
So the validator has to enforce directly what the lattice used to enforce structurally:
Every walkable tile must have a perpendicular exit within 3 tiles along its corridor.
Three tiles is derived, not chosen: a player under Honeycomb Wax moves 1.8 tiles/s, so a 2.0s fuse buys 3.6 tiles of travel. Three tiles to a side exit leaves margin. Beyond that, the map can kill a player who did nothing wrong.
- Hard failure above 6 tiles. No excuse; that corridor is a trap
- Advisory between 4 and 6. Legitimate for a deliberately tense chokepoint, but it should be a decision someone made on purpose
This is the most important new validation rule in the format, and the easiest one to forget. The lattice made it invisible; authored maps make it a live risk on every file.
4. Movement¶
4.1 The model¶
Continuous movement with grid-aligned collision. ▲ This is the genre convention and it is what the game must do, but it directly contradicts Framework & Flow §8.1 as currently written ("grid coordinates are integers"). This document supersedes it.
A Chugger has a floating-point position and moves smoothly. Collision is resolved against the tile grid. The player is not stepping tile to tile — that model feels rigid, removes corner assist entirely, and is not what anyone means when they say "like Bomberman."
- Position is a float
(x, y)in tile units - The player occupies a collision box of 0.7 × 0.7 tiles, centred on position — narrower than a tile, which is what makes corner assist possible
- Movement is 4-directional only. Diagonal input resolves to the dominant axis; ties keep the current direction
- No acceleration, no momentum, no inertia. Input starts and stops movement on the same frame
4.2 Corner assist¶
The single most important feel mechanic in the genre. When a player moves toward a corridor they are not perfectly aligned with, the game nudges them into alignment rather than stopping them against a pillar.
- If the player presses a direction that is blocked, and the perpendicular offset from the nearest open lane is ≤ 0.35 tiles, the player slides perpendicular toward that lane at full speed until aligned, then proceeds
- Assist only ever helps toward an open tile. It never pushes a player into a blast, and it never moves them further from their input direction
- Assist does not apply if both perpendicular neighbours are open — that is a junction, and the player is already free
Without this, players die to geometry and blame the game. With it, they die to their own decisions. Budget real tuning time for the 0.35 threshold — it is the number playtesters will feel and never be able to name.
4.3 Player-player collision¶
Chuggers pass through each other. Genre convention. Solid players make body-blocking a viable grief tactic, make corridors deadlocked, and interact terribly with rollback in netcode.
4.4 Speed¶
| State | Speed | Time per tile |
|---|---|---|
| Base | 3.0 tiles/s | 0.33s |
| Coffee Bean (+40%) | 4.2 tiles/s | 0.24s |
| Honeycomb Wax (−40%) | 1.8 tiles/s | 0.56s |
▲ DIVERGENCE: there is no permanent speed upgrade. Classic Bomberman has Roller Skates; Fizz Frenzy has only the 5-second Coffee Bean. This is deliberate. A permanent speed stat means the escape equation (§9) changes match to match and every other constant has to be safe across the whole range. Holding base speed constant means the danger radius of a can is a fixed, learnable distance for the entire match. If playtesting says the game is too slow late, raise the base — do not add a stat.
5. The Can¶
5.1 Placement¶
A player may place a can when all of these hold:
- Their active can count is below their limit (base 1, +1 per Extra Can, cap 5 → max 6)
- The tile they occupy contains no can
- They are not affected by Glue
The can snaps to the centre of the tile the player's centre is in. It is placed instantly on the input frame — no wind-up delay in the simulation, whatever the animation does.
5.2 The walk-off rule¶
A player may step off the can they just placed, but not back onto it. Genre convention, and it is most of what makes the game tactical.
Implementation: a can placed under a player is non-solid for that player only until they fully vacate its tile. The moment their collision box clears the tile, it becomes solid to everyone including them. It is solid to all other players from the instant of placement.
5.3 The fuse¶
2.0 seconds, fixed, on every can, always. No charge, no variance, no pickup that modifies it. See Framework & Flow §8.7.
- Armed state (visual only) begins at T−0.8s
- The fuse ring is the only timing information in the game
5.4 Chain detonation¶
A blast reaching a live can detonates it immediately on that tick, ignoring its remaining fuse. Chains resolve to a fixed point within a single tick — a four-can chain is one simultaneous event, not four sequential ones.
Genre convention, and the alternative (shortening the fuse instead) reads as network lag on a mobile screen.
5.5 Can-player collision¶
A can is solid. Walking into one stops you — unless you hold an Ice Cube, in which case you kick it (§8).
6. The Blast¶
6.1 Shape¶
A cross: the centre tile plus four cardinal arms. Arms extend up to the player's blast range, which is 1 at base, +1 per Shaker.
There is no diagonal component, no radius, no falloff.
6.2 Propagation, per arm¶
Walking outward one tile at a time, the arm resolves each tile in this order and stops when told to:
| Tile contains | Result |
|---|---|
| Empty floor | Blast fills it, arm continues |
| Solid block | Blast does not fill it, arm stops |
| Destructible block | Blast fills it, block is destroyed, arm stops |
| Live can | Blast fills it, that can detonates this tick, arm stops |
| Player | Blast fills it, player dies, arm continues |
| Pickup or cube | Blast fills it, see §6.5, arm continues |
The destructible rule — one block per arm, then stop — is the genre convention and it is the reason blast range feels valuable rather than absurd. An arm that cleared a whole row would flatten the arena in three cans.
6.3 Timing¶
| Phase | Duration |
|---|---|
| Damage window | 0.15s |
| Blast VFX | 0.4s |
Everything lethal happens in the first 0.15 seconds. Foam visible on screen after that is decoration. A player who enters a blast tile during the window dies; one who enters after it does not. This gap is deliberate — it is the difference between "I got away" and "the game lied to me," and it must be tuned so the foam that is still rising is lethal and the foam that is settling is not.
6.4 Self-damage¶
Your own blast kills you. Genre convention, absolutely central, and stated here because no other document says it.
There is no friendly-fire distinction, no owner immunity and no grace period. The player who placed the can is treated identically to everyone else.
6.5 Blasts and pickups — LOCKED: destroyed¶
A blast destroys any uncollected pickup or Surprise Cube it touches. Genre convention. The arm continues past it.
- Blast range gains a second use: upgrade denial. Blasting a Shaker your opponent is running toward is now a real play, and it gives high-range players something to do with reach they can't otherwise spend
- Requires one asset:
pickup_destroyed(8 frames), which must not resemblepickup_pop— a player who sees the collect animation and gets no stat change will report it as a bug - The endgame no longer silts up with uncollected items, which matters because the board is closing at that point anyway (§12)
Ordering note: destruction resolves in step 8, before collection in step 11. A player entering the tile on the same tick the blast lands does not collect the pickup — it is already gone, and they are already dead.
7. Death and Round End¶
- A player caught in a blast dies instantly. One life per round — there is no life pool, no respawn, no continues
- Death drops nothing. Upgrades are lost with the player
- The round ends when one or zero players remain
- Simultaneous death is a draw. No player is awarded the round
▲ This resolves a contradiction: the asset architecture specs hud/lives with three states. There is no lives system. That asset is vestigial and should be cut or repurposed as the round-pip indicator.
7.1 What an eliminated player does — LOCKED: spectate¶
An eliminated player stays in the match as a spectator until the round ends. No ghost mode, no interference, no early exit to results.
What this requires:
- A spectator camera state. The board is fully visible at all times on an 11×13 portrait field, so this is a HUD change rather than a camera system: the control zone is replaced, the player's own pickup strip is dimmed
- The player's own avatar slot goes to a dead state in the HUD roster, same as it appears to everyone else
- Leave is available — a spectating player can drop to Match Results at any time. Trapping someone in a match they have lost is worse than letting them leave. If they leave, they still receive their placement and rewards
- Chat stays unavailable during live play, spectating or not (Framework & Flow §9)
The cost to accept: a player eliminated at 15 seconds watches for up to 105 seconds. That is the worst churn moment in the game, and it is the reason best-of-3 with a 120s timer is a better structure than single elimination — the wait is bounded and another round is coming.
Ghost mode remains the first post-launch feature (§14). It is the better answer to this problem, but it is a system with its own balance pass, and shipping spectate first is the correct order.
8. Pickups — Consolidated¶
Six effects, two delivery routes. All non-variant: identical in every arena, every match, every skin.
8.1 The delivery split¶
Buffs sit on the field as themselves. Debuffs only ever arrive inside a Surprise Cube.
A visible debuff is fair because you can path around it. A Surprise Cube is fair because the risk is disclosed even though the outcome is not — the same contract as a Mario Kart item box. What the game must never do is deliver a debuff from a container that looked safe. The ? glyph is doing that work.
▲ DIVERGENCE from classic Bomberman, and an improvement. Bomberman's skull disease sits on the floor looking collectable and is the most-hated pickup in the genre's history.
8.2 Class A — permanent upgrades¶
Persist until death. Stack to a hard cap. Shown as HUD counters. Drop visibly from destroyed blocks.
| Pickup | Effect | Base | Cap | Max |
|---|---|---|---|---|
| Shaker | +1 blast range | 1 | 6 | range 7 |
| Extra Can | +1 simultaneous can | 1 | 5 | 6 cans |
| Ice Cube | Unlocks kick at stack 1; stacks 2–3 raise can slide speed | 0 | 3 | speed tier 3 |
The Shaker cap is 6, giving a maximum range of 7. Lowered from 8 because range 9 spanned an 11-wide grid from any position, making the top two stacks imperceptible. At 7 the cap is reachable, felt, and still short of full board width — a maxed player dominates a lane without owning the whole row.
This is now a HUD constraint too: the counter needs to display 0–6, not 0–8, and the cap-reached state has to be reachable often enough in a real match that pickup_pop_capped is worth its eight frames. Verify against the drop table.
Cap behaviour. At cap, a Class A pickup is still collectable — an uncollectable pickup squatting on a tile is worse than a wasted one. It plays pickup_pop_capped so the player learns the ceiling without a message.
8.3 Class B — timed effects¶
Do not stack. Shown as a depleting band, not a counter. Only ever obtained from a Surprise Cube.
| Effect | Result | Duration | Type |
|---|---|---|---|
| Coffee Bean | Move speed ×1.4 | 5s | Buff |
| Honeycomb Wax | Move speed ×0.6 | 5s | Debuff |
| Glue | Cannot place cans | 3s | Debuff |
Rules:
- Refresh, never extend. A second Coffee Bean resets to 5s; it never accumulates. This is what stops a debuff chain from locking a player out of the match
- One movement modifier at a time, last one wins. Coffee Bean clears Honeycomb Wax and vice versa. That gives the bean a rescue role and makes cubes worth opening even while slowed
- Glue is orthogonal and coexists with either movement state. Glued-and-fast is useful; glued-and-slow is the worst place in the game to be
- All timed effects clear on death — irrelevant with one life per round, but stated for when ghost mode arrives
- Effects persist across the round end? No. Every round starts clean
8.4 The Surprise Cube¶
Opaque ? container. All six effects at flat weight — so a cube is 1/3 debuff, 2/3 buff, and worth opening. Including the Class A permanents in the pool is exactly what makes that true.
The cube pool is not the drop table. They are two separate tables and conflating them is a silent bug:
| Drop table (§8.5) | Cube pool (here) | |
|---|---|---|
| Answers | What spawns when a block breaks? | What does an opened cube contain? |
| Entries | 5 — including nothing and Surprise Cube | 6 — all effects, flat, no nothing |
| Source | Map header, per map | Fixed constant, never authored |
A cube can never contain a cube, and a cube is never empty. If cube contents are rolled from the drop weights, a map that weights Surprise Cube highly starts producing cubes inside cubes, and it will present as a vague balance complaint rather than as an error.
- A cube that rolls a capped Class A effect plays the capped pop after the reveal, so a wasted roll still reads as a roll rather than a bug
- The reveal is public — it plays at world scale above the collecting player. Everyone sees who just got glued
- First-cube rigging: a new account's first-ever cube is rigged to roll a buff. Three of six effects can only be learned by experiencing them, and a Glue on someone's opening match is a bad first impression that costs nothing to avoid
8.5 The drop table¶
One source, one position. Every pickup and cube spawns from a destroyed block, at that block's tile. Nothing seeds at round start; nothing spawns on an empty tile.
| Entry | Weight (starting point) |
|---|---|
| Nothing | 62 |
| Shaker | 12 |
| Extra Can | 10 |
| Ice Cube | 6 |
| Surprise Cube | 10 |
These are starting values for tuning, not locked balance. Three things to hold while tuning:
- Weights are declared in the map header and are required there.
drop_weights: 62,12,10,6,10— nothing, Shaker, Extra Can, Ice Cube, Surprise Cube. There is no pack default and no global fallback. A map's block density and its drop weights decide the same thing, the rate at which power arrives, and keeping them in one file is what stops a pack from feeling wrong for reasons nobody can locate. The table above is a starting point to copy into a new map, not a default the engine supplies - The arena is the pacing mechanism. Dense early board, poor in pickups; mid-game opens and floods; endgame has nothing left to break. That curve comes free and should not be fought
- Ice Cube is weighted lowest deliberately — it caps at 3, and the kick is a capability, not a stat. One is transformative; the third is marginal
8.6 Spawn sequencing¶
Three rules the shared spawn position forces:
- Debris first, pickup second. The break animation completes, then the spawn burst fires. A pickup materialising inside its own debris cloud is invisible for exactly the moment the player is watching that tile
- Debris never occludes a settled pickup. Debris lifetime is capped and no particle comes to rest on a tile
- Chain reactions spawn into live foam. The spawn burst waits for the blast on that tile to clear. A pickup appearing under active foam is a pickup nobody sees
9. The Kick¶
Unlocked by the first Ice Cube.
Trigger: walk into a can while holding at least one Ice Cube. The can slides in the direction of travel.
| Ice Cube stack | Slide speed |
|---|---|
| 1 | 6.0 tiles/s |
| 2 | 8.0 tiles/s |
| 3 | 10.0 tiles/s |
A sliding can keeps its original fuse. Kicking moves a problem; it does not postpone one. This is what makes the kick offensive rather than defensive.
A slide stops when the can hits:
| Obstacle | Result |
|---|---|
| Solid or destructible block | Bounce, settle on the last open tile |
| Another can | Bounce, settle. The struck can does not move |
| A player | Settle on the tile before them. No damage |
| The border | Bounce, settle |
A sliding can that detonates mid-slide blasts from whatever tile it occupies at that instant, snapped to the nearest tile centre.
9.1 Sliding into a live blast¶
A sliding can that enters a tile within its live damage window detonates immediately, exactly as a stationary can does when a blast reaches it.
This is the right rule and it closes a hole. Without it, a can can be kicked through an explosion it should have set off, which is both physically absurd and a genuine exploit — a player with an Ice Cube could launch cans across a contested board and have them survive the fight in the middle.
Two constraints it imposes:
- A blast tile stays "live" for the damage window (0.15s ≈ 2–3 ticks), not for the VFX duration (0.4s). Cans and players obey the same window. Foam that cannot kill a player must not detonate a can, or the game is teaching two different things with one visual
- The engine must hold a live-blast tile set that persists across ticks, keyed by tile with an expiry. Stationary cans are caught by the chain pass in the tick the blast is computed; a sliding can may arrive one or two ticks later and needs something to arrive at. See step 3 in §13
Tunnelling is not a risk at these speeds. The fastest can travels 10 tiles/s, which at 15 ticks/s is 0.67 tiles per tick — under one tile, so no can can skip over a live blast tile between ticks. This is a real constraint on the Ice Cube speed table: any future slide speed above 15 tiles/s would need swept collision.
Sliding cans do not damage on contact. Adding contact damage makes the kick a ranged weapon and turns every corridor into a shooting gallery — a different game.
10. The Escape Equation¶
The relationship that decides whether the game is playable at all. Everything above is tuned to satisfy it.
A player who places a can must be able to reach safety within 2.0 seconds.
At base — range 1, speed 3.0 tiles/s — safety is 2 tiles away and takes 0.67s. Comfortable, which is correct: at base the player should die from trapping themselves, not from being outrun.
The tight case is maximum range plus Honeycomb Wax, where the player must clear the lane entirely — up to 2 tiles along, 1 tile perpendicular, so 3 tiles at 1.8 tiles/s = 1.67s of a 2.0s fuse. Survivable with 0.33s of margin, and it should feel like nearly dying.
The three numbers that must never be changed independently: fuse duration, base speed, and the Honeycomb Wax multiplier. Any change to one requires re-verifying this case. If the wax multiplier drops to 0.5, the tight case becomes 2.0s exactly and the debuff becomes a death sentence.
11. Round and Match Structure¶
| Setting | Value |
|---|---|
| Rounds per match | 1 / 3 / 5, host-selected |
| Round timer | 120s |
| Sudden death begins | T−30s (i.e. at 90s elapsed) |
| Inter-round | 5s results, auto-advance |
| Round start | 3-2-1 countdown, no spawn invulnerability |
Between rounds: the board resets to the map's authored layout exactly. All upgrades reset to base. Nothing carries over.
▲ Every round of a match is played on an identical board. With no procedural fill there is nothing to reseed. This is the classic Bomberman behaviour and it is fine — but it relocates replayability from randomisation within a map to the number of maps, which makes the map count in each pack a content decision rather than a nice-to-have. The three-map pack minimum in the asset architecture is now a floor, not a target.
The only per-round variation left is the drop table, which still rolls fresh from each destroyed block.
11.1 Ties¶
- A draw awards no round point and does not count toward the total. The round is replayed
- Hard cap:
configured_rounds + 2. If the cap is reached without a majority, the match is declared a draw
Without the cap, three players drawing repeatedly can play forever.
11.2 Two-player duels¶
A 2-player match uses the same arena, the same 11×13 grid, the same drop table and the same round count as a 4-player match. No separate tuning, no altered round structure — and, in version 1, no duel-specific maps at all.
A duel therefore runs on a standard 4-spawn map with two of its spawns occupied, selected by the maximum-separation rule in §3.3.
Two things to hold:
- Two players on a 4-player board is sparse. The interior that feels tight with four feels open with two, so early rounds are slower and the sudden-death ring does more of the work of ending the match. Acceptable for v1, and the strongest argument for authoring real duel maps later
- This makes §3.3 load-bearing rather than a detail. With no duel maps, spawn selection is the only thing making a 2-player match open sensibly. It is not optional polish
12. Sudden Death¶
Triggered at T−30s. The arena closes from the outside in.
- One ring every 4 seconds. An 11×13 grid has 6 rings, so the field is fully closed at 24s — leaving 6s of margin before the timer expires
- A closing ring fills every tile in that ring with an indestructible block
- Anything on a filled tile is destroyed: players die, cans detonate immediately, pickups vanish, destructible blocks are replaced
- Closing blocks are placed clockwise from the top-left of the ring, one tile at a time, so players can read the direction and run ahead of it
This exists to end stalls. Two cautious players with full upgrades will circle each other indefinitely, and a 90-second match becomes four minutes. It is not a flourish.
The Beach Kiosk tide (style guide §9) is the same mechanic reskinned — the tide is the closing ring. It is not a second system.
12.1 Arena hazards¶
Version 1 supports exactly one hazard type: a tile that is periodically lethal on a pack-defined timer, placed with map glyph H. It kills anything standing on it during its active phase and detonates cans.
That covers the Diner's soda fountain and the Beach Kiosk's tide. Teleporters, ricochet and persistent floor states need map format 2 and are not in v1.
12.2 The seagull — CUT¶
The Beach Kiosk seagull that stole uncollected power-ups is removed. With §6.5 locked, blasts already handle uncollected pickups, and the seagull was a second solution to a solved problem that additionally broke the rule that pickups behave identically in every arena.
Seagulls remain as background dressing — style guide §9, ambient, non-interactive, never on a tile.
13. Deterministic Resolution Order¶
The server simulates at 15 ticks/s. Every tick resolves in exactly this order. Order matters more than any individual rule — get it wrong and two clients disagree about who died first.
- Apply movement inputs; resolve collision and corner assist
- Resolve kicks initiated this tick
- Advance sliding cans; resolve slide collisions; mark any can now occupying a live-blast tile for detonation (§9.1)
- Resolve can placements
- Decrement fuses; mark expired cans for detonation
- Resolve chain detonations to a fixed point — repeat until no new can is marked
- Compute the full blast tile set for all detonating cans simultaneously
- Destroy blocks; destroy pickups and cubes in blast tiles (§6.5)
- Apply damage to every player in a blast tile — all deaths this tick are simultaneous
- Advance sudden-death ring if due
- Resolve pickup collection
- Tick timed effects; expire finished ones
- Queue deferred spawn bursts for tiles whose blast has cleared
- Expire live-blast tiles whose damage window has elapsed
- Evaluate round end
The live-blast tile set is written in step 7 and read in step 3 of subsequent ticks, expiring in step 14 after 0.15s. It is the only piece of state that crosses tick boundaries, and it is what lets a can arriving two ticks late still detonate correctly.
Step 6 before step 7 is what makes a chain read as one event. Step 9 as a single simultaneous pass is what makes mutual kills a genuine draw rather than a race decided by iteration order over a player array.
14. Deliberate Omissions¶
Classic Bomberman pickups not in version 1, and why. Listed so the absences read as decisions rather than oversights.
| Omitted | Reason |
|---|---|
| Remote detonator — scheduled for version 2 | Invalidates the fixed fuse, which is version 1's only timing signal. Arriving in v2 as a limited-use pickup, not a permanent stat: a player holds N remote charges, and a detonate button appears only while one is armed. That keeps the fixed fuse as the default reading of the board and makes the remote an event rather than a mode |
| Roller skates (permanent speed) | Destabilises the escape equation, §4.4 |
| Bomb pass / wall pass | Removes the trap mechanic that is the core of the game |
| Invincibility vest | Removes tension for its duration; also an unreadable state at 32px |
| Punch / throw | A whole second control verb and a second targeting model. Ghost mode wants it first |
| Skull disease | Superseded by the Surprise Cube, which delivers the same chaos with disclosed risk |
| Line bomb | Multi-tile placement needs a second input verb |
Version 2 scope, in rough order of value:
- Ghost mode — eliminated players lob cans from the sidelines
- Remote detonator — limited-use pickup, confirmed for v2
- Punch / throw
- 6-player — costed in Framework & Flow §1.1.1
The remote detonator has a consequence worth noting now rather than in a year: it is the first mechanic that would put a second action button in the control cluster alongside DROP and KICK. Three contextual buttons in the lower-right is the point where the control zone needs a layout rethink, not another icon.
15. Open Questions¶
15.1 Still open¶
| # | Question | Blocks | Recommendation |
|---|---|---|---|
| 1 | Corner assist threshold — 0.35 tiles? (§4.2) | Feel | Playtest. Nothing else will settle it |
| 2 | Base speed 3.0 tiles/s? (§4.4) | Every other constant, via §10 | Playtest. Change it early or not at all |
| 3 | Sudden-death ring interval — 4s? (§12) | Endgame pacing | Playtest at 4s; it is the only number here with no reference point |
| 4 | Corridor exit distance — 3 tiles hard, 6 advisory? (§3.4) | Map authoring | Derived from the escape equation, but verify against a real authored map before pack one |
Every remaining open question is now a tuning question that only playtesting answers. There are no unresolved rules.
15.2 Closed in 0.4¶
| Question | Decision |
|---|---|
| Where do drop weights live? | Map header, required. No pack default — §8.5 |
| Cube pool membership | All six effects, flat. Separate table from the drop table — §8.4 |
| Which spawns does a 2-player match use? | Maximum pairwise separation, reading-order tie-break — §3.3 |
| Dedicated duel maps in v1? | No. Duels run on 4-spawn maps — §11.2 |
15.3 Closed in 0.3¶
| Question | Decision |
|---|---|
| What does an eliminated player do? | Spectate. §7.1 |
| Who places solid blocks, destructibles and spawns? | The map file, entirely. §3.1 |
| Does the board reseed between rounds? | No. Every round is the authored layout. §11 |
15.4 Closed in 0.2¶
| Question | Decision |
|---|---|
| Do blasts destroy uncollected pickups? | Yes, destroyed. §6.5 |
| Shaker cap — 8 or 6? | 6, max range 7. §8.2 |
| Does the seagull survive? | Cut. §12.2 |
| Drop weights per-map or per-pack? | Per-pack default, per-map override, all-or-nothing. §8.5 |
| Does a 2-player duel differ? | No. Same arena, same grid, same ruleset, same round count. §11.2 |
| Remote detonator? | Version 2. §14 |
16. Contradictions This Document Resolves¶
| Was | Now |
|---|---|
| Framework §8.1: "grid coordinates are integers, simulation does not interpolate" | Continuous movement, grid collision (§4.1) |
Asset arch §6.4: hud/lives with 3 states |
No lives system. One life per round (§7) |
| No document stated self-damage | Own blast kills you (§6.4) |
| No document stated blast propagation | §6.2 |
| No document stated the walk-off rule | §5.2 |
Engine-placed odd/odd pillar lattice; ? fill; forced corner spawns |
The map file defines every tile (§3.1); corner spawns are conventional, not required (§3.3) |
| The lattice implicitly guaranteed a side exit within 1 tile | Explicit corridor-exit validation rule (§3.4) |
| Nothing stated what happened to a can sliding through a blast | Detonates on contact within the damage window (§9.1) |
| Product summary "90–180s sessions" vs §8.3 "120s round timer" | 120s round, 3 rounds, ~5s between: a match is 4–7 minutes. The product summary is wrong and should read 120s per round |
Part 2 — Application Framework & Flow¶
Version 0.10 — Pre-production
Changes in 0.10: Cross-references updated for the new fifth companion, Economy & Progression — §15 questions 7 and 11 closed (single-player rewards capped daily; the rollover pays nothing in v1), the §17 scope boundary and §5.17 repointed to it, §5.10's rewards line defined, §16.1 ownership extended. No rule in this document changed.
Changes in 0.9: Leagues & Leaderboards added as §17 — six tiers, 50-player clubs, promotion and demotion resolved at 22:00 UTC every Sunday. Points are 1 per round won plus 1 per completed match. Promotion tapers to 10 at Gold and Diamond against a flat 15 demotion, making the ladder a pyramid — Elite settles near 8.7% of the player base. New Leagues screen (§5.17), additions to Home, Match Results and Profile, new persistence rows and a new league_get RPC. Only matchmade Quick Match awards points, which closes the league half of open question 7. Tier badge slots added to Asset Architecture §6.6.
Changes in 0.8: Map delivery locked — bundled baseline, server adds, additive only. Lobby now fetches missing maps. Open question 8 closed.
Changes in 0.7: §8.1 rewritten — the map file defines every tile; no engine-placed lattice, no fill density, no forced corner spawns. Grid fixed at 11×13 and validated. Round reseeding removed. Elimination locked to spectate.
Changes in 0.6: Sliding cans detonate on entering a live blast — new EVENT case. Blasts destroy uncollected pickups. Cross-document register updated; no rules remain open.
Changes in 0.5: Gameplay Rules & Mechanics created as the fourth companion document; simulation rules move there. §8.1 movement model superseded — movement is continuous, not tile-stepped. Session length corrected. Ownership register updated.
Changes in 0.4: Pre-placement mechanic cut. Cans are pre-shaken; drop is a single discrete input with a fixed 2.0s fuse. No Pressure meter, no charge input, no accelerometer. Canonical timing constants added (§8.7).
Changes in 0.3: Player count simplified to 4, full stop. The build-for-6 contract is withdrawn — six players becomes an explicit version 2 refactor with a stated cost (§1.1). Spawn glyph error corrected: spawns are S, not digits. Cross-document consistency register added (§16).
Changes in 0.2: Grid locked at 11×13 portrait. Portrait layout budget added (§5.8.1).
One of five companion documents. The Design Style Guide says what things look like. The Asset Architecture says how they are cut up, named and shipped. This document says what screens exist, how the player moves between them, and what the client and server each own.
0. Product Summary¶
| Title | Fizz Frenzy |
| Player characters | Chuggers |
| Genre | Grid brawler (Bomberman-lineage), real-time |
| Modes | Single player vs AI, online multiplayer |
| Platforms | iOS, Android — phone and tablet |
| Engine | Unity 3D (2D presentation, URP) |
| Backend | Nakama, self-hosted |
| Session length | 120s per round; 3-round match runs 4–7 minutes |
Store description (locked copy)¶
Welcome to the grid, Chugger.
Grab a can, give it a shake, and drop it before the pressure gets to you. Two seconds later it blows — foam spraying down four lanes, taking out walls, crates, and anyone slow enough to still be standing there.
Trap your friends. Chain your cans. Be the last Chugger fizzing.
• Fast 4-player matches you can finish on a bus ride • Grab power-ups: Shaker for reach, Extra Can for more firepower, Ice Cube to kick cans across the floor • Dozens of arenas and flavours to unlock • Simple to pick up, brutal to master
1. Locked Foundations¶
Both are closed. Everything downstream depends on them.
1.1 Player count — four, and only four¶
Version 1 is a 4-player game everywhere: content, code, HUD, netcode, matchmaking, maps. Six players is a version 2 feature and is scoped as a refactor, not as a switch waiting to be flipped.
This is the simpler decision and it is defensible. Building for a player count you have not designed content for costs real time in exchange for flexibility you may never use, and half-built abstractions rot: an untested 6-slot code path is not a head start, it is a source of bugs that only appear the day you finally enable it.
One exception, and it is nearly free. Keep max_players as a match config field, not a hard-coded literal, and have the match handler loop over the actual roster length rather than a fixed array of four. This costs an hour today, is testable at 4, and removes the single most invasive part of the eventual refactor — the part that otherwise means touching every gameplay file. Everything else is authored, tuned and validated at 4.
Everything below is explicitly 4-only:
| System | Version 1 |
|---|---|
| Player colours | 4 — Red, Blue, Green, Orange, deuteranopia-verified (style guide §3.2) |
| Shape badges | 4 — circle, square, triangle, star |
| HUD roster | 4 slots, sized to fill the band properly on a phone |
| Map header | players: 2–4, validated |
| Spawn markers | S, assigned in reading order, max 4 per map |
| Matchmaker | 4-player pools |
| Lobby / browser | 2–4 slots |
| Balance | Drop weights and round timer tuned at 4 |
1.1.1 The version 2 refactor, costed now¶
Written down so the decision stays informed rather than forgotten. When 6-player is scheduled, expect to touch:
| Area | Work | Size |
|---|---|---|
| Player colours 5 and 6 | Two new hues that survive deuteranopia, protanopia and tritanopia, avoid the warm hazard band (style guide §3.3), and stay distinct from the existing four and from Sunbeam | Hard. This is the genuinely difficult one |
| Shape badges 5 and 6 | Two more shapes readable at 32px — hexagon and diamond are the obvious candidates | Small |
| HUD roster | Redesign, not resize. Six portraits across a phone top band puts labels under the 28px minimum, so the band gets a new layout — probably two rows or portrait-only slots with no names | Medium |
| Map format | players widened to 2–6, plus A–F explicit spawn assignment from the format 2 reserve. New maps required; existing 4-player maps stay valid |
Medium |
| Balance | Drop rate, fill density, round timer and sudden-death timing all re-tuned. 6 on an 11×13 field is 1 player per 24 tiles versus 1 per 36 | Medium |
| Grid | Possibly a larger field for 6, which would be a second grid size and a second layout budget | Decide before committing |
| Mode design | 6-player FFA on this field is cramped and may play better as 3v3 — which is a second scoring model, a team HUD state and friendly-fire rules | Design work, not engineering |
The honest summary: the expensive parts of 6-player are the colour palette and the mode design, and neither of them gets cheaper by writing flexible code now. That is why deferring is the right call.
1.2 Grid — 11 columns × 13 rows, portrait¶
Locked. Portrait, one-handed-capable, HUD band above the field and control zone below it. Landscape becomes a tablet presentation option only — same grid, wider margins, repositioned controls. It is never a second layout with different tile counts.
Consequences:
- Aspect ratio. The field is 11:13, or 0.846. On a 19.5:9 phone (0.462) the field cannot fill the screen vertically and horizontally at once — it fills width and leaves vertical room, which is exactly where the HUD and controls go. On a 4:3 tablet the field fills height and leaves horizontal margin, which is where the tablet HUD goes. Both work; neither needs a separate grid
- The field is width-constrained on phones, height-constrained on tablets. Camera fit logic must handle both, not just scale to width
- Fixed dimensions are a layout constraint, not just a design one. The 80px minimum tile size in §5.8.1 is what pins the grid at 11×13; a larger field breaks touch accuracy on the reference phone. Maps define every tile within that frame but never the frame itself — Gameplay Rules §3.2
- Spawns are wherever the map puts them. Corners are conventional, not required. Where corners are used, note they are not equidistant on an 11×13 field — vertical pairs sit 12 tiles apart, horizontal pairs 10
- Vertical chains read better than horizontal ones on this field, which favours the taller axis for trap corridors. Worth telling whoever authors maps
See §5.8.1 for the vertical layout budget.
2. Screen Inventory¶
Your original list had eleven entries. Below is the full set — the additions are marked, and each is there because something in the design already implies it exists.
| # | Screen | In original list | Why it exists |
|---|---|---|---|
| 1 | Boot / Splash | added | Nakama session restore, asset manifest check, pack integrity |
| 2 | Home | yes | Root hub |
| 3 | Single Player Setup | yes ("Single Player Page") | Arena, map, AI count, difficulty |
| 4 | Multiplayer Hub | yes ("Multi Player Page") | Quick Match vs Browse vs Private |
| 5 | Account | yes ("Registration Page") | Device auth is silent; this screen is upgrade & link, not gate |
| 6 | Match Browser | yes | Lobby list, filter, join |
| 7 | Match Lobby | added | Pre-match room: roster, ready state, arena vote, chat |
| 8 | Loading / Handoff | added | Pack download, map parse, spawn assignment |
| 9 | Game Engine (Match) | yes | The actual game |
| 10 | Pause | added | Single player only — see §8.4 |
| 11 | Round Results | added | Between-round scoreboard in best-of-N |
| 12 | Match Results | added | Winner, rewards, rematch, exit |
| 13 | Character Select | added | Four Chuggers, skins per §Asset Arch |
| 14 | Store | added | Arena packs, character skins, blast skins, flavours |
| 15 | Profile / Collection | added | Owned cosmetics, stats, equipped loadout |
| 16 | Settings | added | Audio, haptics, control scheme, language, account |
| 17 | How to Play | yes | Pickup vocabulary — asset arch §6.5 depends on this existing |
| 18 | About | yes | Version, legal, links |
| 19 | Credits | yes | Child of About |
| 20 | Chat | yes | Not a screen — an overlay component. See §9 |
| 21 | Network | yes | Not a screen — a service. See §7 |
| 22 | Player AI | yes | Not a screen — a system. See §8.5 |
| 23 | Leagues / Leaderboard | added | Weekly club standing, tier, cut lines — §17 |
Cut from consideration: a separate Registration gate. See §6.
3. Navigation Graph¶
Boot
└─ Home ──────────────────────────────────────────────┐
│ │
├─ Single Player │
│ └─ Setup ─→ Loading ─→ MATCH │
│ ├─ Pause ─→ (resume|quit)
│ └─ Match Results ─┬─ Rematch → Loading
│ └─ Home
│
├─ Multiplayer ─→ [session check]
│ ├─ Quick Match ─→ Matchmaker ─→ Lobby ─→ Loading ─→ MATCH
│ ├─ Browse ─→ Match Browser ─→ Lobby ─→ Loading ─→ MATCH
│ └─ Private ─→ Create/Join by code ─→ Lobby ─→ Loading ─→ MATCH
│ │
│ MATCH ─→ Round Results ─(loop)
│ └─→ Match Results ─┬─ Rematch → Lobby
│ └─ Home
│
├─ Character Select ─→ (equip) ─→ Home
├─ Store ─→ Item Detail ─→ [purchase] ─→ Store
├─ Leagues ─→ (standing) ─→ Home
├─ Profile ─→ Collection | Stats | Leagues
├─ Settings ─→ Account (link/unlink)
└─ About ─┬─ How to Play
├─ Credits
└─ Legal
Rules for the graph:
- Home is the only root. Every screen has a path back to Home in at most three steps. Android hardware back never exits the app from anywhere except Home, where it prompts.
- MATCH is a trapdoor. You cannot navigate out of a live multiplayer match by normal means — only Forfeit (confirmed) or disconnect. There is no back button during a match.
- Loading is never a destination. It has no back affordance and is entered only from a screen that has already committed the player.
- Store and Character Select are reachable from Home and from Match Results. The moment after a loss is where cosmetic intent is highest; a dead-end results screen wastes it.
- Leagues is reachable from Home, Profile and the league beat on Match Results, and from nowhere else. It is a destination, never a step on the way to a match — nothing about queueing for a game may route through a leaderboard.
4. Application State Machine¶
The client is always in exactly one of these. Illegal transitions are assertion failures, not silent no-ops.
BOOTING → AUTHENTICATING → IDLE ⇄ MATCHMAKING → LOBBY → LOADING → IN_MATCH → POST_MATCH → IDLE
↓ ↓
OFFLINE ←──────── RECONNECTING ←─────────────┘
| State | Socket | Description |
|---|---|---|
BOOTING |
none | Unity init, addressables catalogue, local config |
AUTHENTICATING |
none | Nakama session restore or device auth |
IDLE |
connected | Menus. Socket open, chat live, presence visible |
MATCHMAKING |
connected | Ticket submitted, waiting on pool |
LOBBY |
connected + match joined | Roster assembling, not yet simulating |
LOADING |
connected | Pack resolve, map parse, prewarm |
IN_MATCH |
connected, high traffic | Simulation running |
POST_MATCH |
connected | Results, rewards, rematch window |
RECONNECTING |
dropping | Backoff retry, see §7.4 |
OFFLINE |
none | Single player only, queued telemetry |
Single player runs the same machine with a null network layer: IDLE → LOADING → IN_MATCH → POST_MATCH. This is deliberate — one match lifecycle, one set of bugs. The AI opponents inject inputs at the same layer a remote player's inputs arrive at.
5. Screen Specifications¶
5.1 Boot / Splash¶
Owns: Unity bootstrap, addressables catalogue fetch, session restore, minimum-version check.
- Studio logo → game logo, ~2s minimum, dismissible after assets are ready
- Silent Nakama device authentication (§6.1). No UI unless it fails
- Version gate: if server reports
min_client_versionabove the build, hard-block with a store link. This RPC must exist from day one — it's the only lever you have when a netcode bug ships - On network failure: proceed to Home in offline mode, do not block. Single player must work on a plane
Exit: Home, always.
5.2 Home¶
The hub. Everything the player does starts here, and the layout should make the split obvious in under a second.
Primary (large, thumb-reachable lower half): - PLAY — single player, one tap to Setup - ONLINE — multiplayer hub
Secondary (upper band): - Player badge: equipped Chugger, name, level → Profile - League chip: tier badge and current club rank → Leagues (§5.17). Carries a notification dot when a rollover result has not been seen - Currency counters → Store - Settings gear
Tertiary (small, bottom edge): - Store, How to Play, About
Ambient: - Equipped Chugger idle-animates on screen. This is the cheapest place to make an unlocked skin feel like it was worth buying - Connection pip: green connected / amber reconnecting / grey offline. Small, always visible, never a modal
Notification surfaces: unclaimed rewards, friend invite, new pack. Badge counts only — no interstitial popups on the hub.
5.3 Single Player Setup¶
- Arena — horizontal carousel of owned packs, locked packs shown greyed with a price tag
- Map — thumbnail grid of maps in the selected pack, plus Random
- Opponents — 1 / 2 / 3 AI (up to
max_players - 1) - Difficulty — Easy / Normal / Hard / Frenzy. Per-slot difficulty is a nice-to-have; a single global setting ships first
- Rounds — 1 / 3 / 5
- START → Loading
Remembers last configuration. A returning player should be two taps from a match.
5.4 Multiplayer Hub¶
Three routes, presented in order of how much friction they carry:
- Quick Match — matchmaker ticket, no configuration. This is the default and should be visually dominant
- Browse Matches → Match Browser
- Private Match — Create (generates 4-character code) or Join by code
Below: friends-online strip if the friends system ships, otherwise omit entirely rather than showing an empty rail.
On entry, check session validity. If the socket is down, show the reconnect state inline on this screen rather than bouncing the player back to Home.
5.5 Match Browser¶
Lists open lobbies from Nakama's match listing, filtered to open == true and size < max_players.
| Column | Source |
|---|---|
| Host name | Match label |
| Arena / map | Match label |
| Players | size / max_players — 2–4 |
| Rounds | Match label |
| Ping band | Client-measured, three tiers: good / fair / poor |
| Lock | Private matches excluded from listing entirely |
- Pull-to-refresh, plus 10s auto-refresh while visible
- Filters: arena pack, has-space, ping band
- Tapping a full match shows Full and refreshes the row rather than failing on join
Implementation note: Nakama match listing queries the match label, so the label must be a structured JSON string containing every filterable field. Decide the label schema before the match handler is written — changing it later invalidates every live match.
{"mode":"ffa","pack":"diner","map":"diner_crossroads","rounds":3,"open":true,"region":"eu"}
5.6 Match Lobby¶
The room between joining and simulating. Exists for multiplayer only.
- Roster —
max_playersslots (2–4), each showing Chugger, name, ready state, shape badge, assigned player colour - Empty slots — Waiting… or Add AI if the host allows it
- Arena/map — host-selected, or vote if you want the extra system. Ship host-selected
- Ready toggle per player; host gets START enabled when ≥2 players are ready
- Chat panel — see §9
- Countdown — 5s auto-start when all slots ready, cancellable by anyone un-readying
- Leave — always available, returns to Multiplayer Hub
Host migration: if the host leaves pre-start, promote the longest-connected player. If nobody remains, dissolve the match. This is server-side logic in the match handler, not client.
5.7 Loading / Handoff¶
Runs between commit and simulation. Not skippable, but should rarely exceed 3 seconds.
Sequence:
1. Resolve arena pack — download if missing, show progress with byte count
2. Fetch the .map file if this client does not have it, then parse and validate against Appendix A of the asset architecture
3. Preload pack assets, prewarm blast VFX pool and audio
4. Report client_ready to the match handler
5. Server waits for all clients or times out at 20s, dropping non-ready players
6. Countdown 3-2-1, then simulation start
Never simulate until every client has confirmed ready. A client that starts late in a real-time grid game is a client that dies to a can it never saw.
Loading screens carry a rotating gameplay tip. Cheap, and it's where most players actually learn the Ice Cube kick.
5.8 Game Engine (Match)¶
Detailed in §8. Screen-level furniture only:
HUD, top band: - Player roster — 4 slots, filling the band. Each slot: portrait, shape badge, player colour, alive/dead state, name - Round timer, tabular figures per style guide §4 - Round pips (best-of-N)
Four slots across a phone top band is the layout the style guide's 28px text minimum was written for. Size them to fill it — this band is not reserving space for anything.
5.8.1 Portrait layout budget¶
Reference device: 6.1" phone, 1170×2532 logical, 19.5:9. Everything below is a proportion of usable height after safe areas, not a pixel constant.
| Band | Height | Contents |
|---|---|---|
| Safe area top | device | Notch / dynamic island. Never draw here |
| HUD band | 12% | Roster, timer, round pips |
| Playfield | 58% | 11×13 grid, fitted to width |
| Pickup strip | 8% | Shaker / Extra Can / Ice Cube counts, active effect chips |
| Control zone | 22% | Joystick origin area left, DROP + KICK right |
| Safe area bottom | device | Home indicator. Never place a button here |
Playfield sizing. The field fits to width, not height. At 1170 logical width with a 4% margin each side, tile size lands near 98 logical px, and the field is 11 × 98 = 1078 wide by 13 × 98 = 1274 tall. That comfortably fits the 58% band on a tall phone and leaves the surplus as vertical letterbox — fill it with arena backdrop, never by stretching tiles.
Three rules this budget exists to enforce:
- The control zone never overlaps the playfield. No transparent joystick floating over live tiles. In a game where the bottom row is a legitimate hiding spot, a thumb over the field is a death the player will blame on you
- The pickup strip sits between field and controls, not in the top band. It is glanced at constantly; the top of a 6.1" phone is the furthest point from the thumb and the hardest to read mid-panic
- Tile size never drops below 80 logical px. On the smallest supported device this is the constraint that decides minimum spec. If a device can't hit it, the field is scrolling or the device is unsupported — decide which before QA finds out
Tablet. Same 11×13 field, fitted to height instead of width, HUD and controls move into the horizontal margins. This is a layout variant, not a second scene.
HUD, bottom control zone: - Movement input — see §8.6 - DROP button, thumb-side - KICK button, appears only once an Ice Cube is held - Pickup stack indicators: Shaker ×N, Extra Can ×N, Ice Cube ×N - Active timed effect chips (Coffee Bean / Honeycomb Wax / Glue) with drain rings
No pause in multiplayer. A menu button opens a slim overlay: forfeit, settings, mute — the simulation continues underneath.
5.9 Round Results¶
Between rounds in a best-of-N. Fast — 5 seconds, auto-advance, skippable when everyone taps.
- Round winner callout
- Score pips updated
- One stat line ("Blocks blasted: 14")
5.10 Match Results¶
- Winner with victory animation
- Final standings, placement, per-player stats
- League beat — points earned this match, new club total, rank delta, tappable to Leagues. Quick Match only; silent in private, browsed and single-player matches, which award nothing (§17.9). It animates alongside the standings and never gates REMATCH
- Rewards: currency, XP, unlock progress — yields and flow defined in Economy & Progression §5
- REMATCH — single player: straight back to Loading. Multiplayer: returns the party to Lobby with the roster intact
- HOME
- Store hook: one contextual card, e.g. the arena that was just played, if unowned
5.11 Character Select¶
- Four Chuggers, each with owned skins in a sub-carousel
- Full-body preview with idle and victory animations
- Locked skins show source (store / reward / seasonal)
- Equip is instant and persists to Nakama storage
5.12 Store¶
Four shelves matching the variant axes:
| Shelf | Contents | Note |
|---|---|---|
| Arena packs | Themed pack, all maps included | Largest download; show size |
| Character skins | Per-Chugger | Must ship a kick animation — build-enforced |
| Blast skins | Foam colour/particles | Copy rule: no comparative language implying advantage |
| Can flavours | Reserved, post-launch | Only Classic ships |
Store copy rule from the asset architecture stands: cosmetic items must never be described in terms that imply mechanical benefit. No "sharper," no "faster-looking," no "intimidating." This is a store-review risk as much as a fairness one.
5.13 Profile / Collection¶
Equipped loadout, owned cosmetics grid, lifetime stats (matches, wins, blocks blasted, favourite arena), account status (guest vs linked) with a link prompt if guest.
League block: current tier, best tier ever reached, and the last 8 weeks as a compact strip of tier badges with the movement each week. This is the only place league history is surfaced — the Leagues screen itself is strictly about the week in progress.
5.14 Settings¶
Audio (music/SFX separate), haptics, control scheme (§8.6), language, colourblind assist, reduced motion, account link/unlink, privacy policy, data deletion request, restore purchases, delete local data.
Data deletion must be reachable in two taps from Settings. GDPR, and you are shipping in the EU.
5.15 How to Play¶
Per asset architecture §6.5 — icons redrawn at UI scale, silhouettes matching the field exactly. Sections: movement, drop & the two-second fuse, blast lanes, blocks, the three permanent pickups, the Surprise Cube and its six outcomes, kick.
Offered once automatically after first launch, then permanently available from Home and About.
5.16 About / Credits / Legal¶
Version string and build number (must be copyable — it's the first thing support asks for), studio, credits list, third-party licences, privacy policy, terms, support contact.
5.17 Leagues¶
The weekly standing. Rules and server behaviour are §17; this is the screen.
Header: - Tier badge and name — Tin through Elite - Countdown to rollover, rendered as a duration in device-local terms ("2d 14h"), never as a wall-clock time. §17.5 - Your rank and points, pinned and always visible
Body — the standing:
- All 50 rows: rank, Chugger portrait, display name, points
- Your row is sticky. It docks to the top or bottom edge when scrolled past. In a 50-row list this is the difference between a screen you read and a screen you hunt through
- Promotion band across the top rows and demotion band across the bottom, drawn from the server's promote_cut and demote_cut ranks — never from a hard-coded 15
- Late joiners carry a small marker rather than looking like they lost badly
- Frozen inactive members render greyed at the bottom (§17.7)
States:
- Offline or stale — last cached standing, staleness marker, countdown disabled
- Rollover in progress — closes_at in the past means "Results are being calculated", not a stuck or negative timer
- No placement yet — a brand-new account before its first Quick Match sees the ladder explained and a single CTA into Quick Match. Never an empty list
Two things this screen must not do:
- It never blocks anything. A failed
league_getshows an inline retry and nothing else breaks. Home loads, Play works - It carries no rewards UI at launch. The rollover pays nothing in version 1 (Economy & Progression §3.2), and the screen is laid out so a reward strip can be added under the header without a redesign
6. Identity & Session Model¶
Your original flow gates multiplayer behind Registration. Don't. Account creation before first online match is the single largest drop-off point in casual mobile multiplayer.
6.1 Silent device authentication¶
At boot, authenticate with Nakama using the device identifier. No screen, no friction. The player has an account and a persistent Nakama user ID before they reach Home, and they never knew it happened.
Boot → session in local secure storage?
├─ yes → restore; expired? → refresh token
└─ no → authenticateDevice(deviceId, create: true)
→ prompt for a display name on first Home visit
6.2 Upgrade to a real account¶
Linking (email, Apple, Google Play Games) is offered — never forced — at the moments where the player has something to lose:
- After first multiplayer win
- After first purchase (required — an unlinked purchase that vanishes with the device is a refund and a one-star review)
- From Profile, permanently available
The Account screen is therefore account linking, not registration. Same screen handles sign-in on a new device.
6.3 Session handling¶
- Store the session and refresh tokens in platform secure storage — Keychain / Keystore. Not PlayerPrefs
- Refresh proactively when the token is within 5 minutes of expiry, not reactively on 401
- Session lost mid-match → §7.4
6.4 Security note on the server key¶
yokicloud is embedded in every shipped client and is trivially extractable. That is normal — the Nakama server key is not a secret and is not an authorisation boundary. Two consequences:
- All authority lives in the authoritative match handler and in RPCs, never in the client. Any client can connect and send any message; the server decides what is true
85.215.123.235:7350over cleartext will not pass App Store review. iOS App Transport Security blocks non-TLS connections, andws://fails the same check. Put a TLS-terminating reverse proxy in front of Nakama with a real certificate on a real hostname before you build a submission candidate. Retrofitting this after the client is written means touching every connection path
7. Network Component¶
A single service, owned by the app, not by any screen. Screens subscribe; they never talk to Nakama directly.
7.1 Responsibilities¶
| Concern | Detail |
|---|---|
| Session | Auth, refresh, secure storage |
| Socket | Connect, heartbeat, presence |
| Matchmaking | Ticket add/remove, matched callback |
| Match | Join, leave, send/receive match state |
| Chat | Channel join/leave, message send/receive |
| Storage | Loadout, settings, collection sync |
| RPC | Version check, config fetch, reward claim, league_get (§17.11) |
| Health | Latency sampling, connection state broadcast |
League points are not an opcode. They are written by the match handler at MATCH_END and read back over RPC; nothing about the ladder travels on the match socket. See §17.10.
7.2 Match architecture¶
Authoritative, not relayed. A relayed match means every client trusts every other client's claim about where it is and when its can exploded — which in a game decided by tile-level timing is an invitation.
- Match handler runs server-side; Nakama supports Go, TypeScript or Lua. TypeScript is the right choice here unless you have Go on the team — the logic is simple, iteration speed matters more than throughput at this scale, and the runtime is well-supported
- Tick rate 15/s. Grid movement is cheap and the simulation is coarse; 15 is comfortable on mobile networks and leaves headroom
- Clients send input intent, not position.
{dir, wantDrop, wantKick}. The server simulates and broadcasts authoritative state - Clients predict local movement and reconcile on divergence. Snapping on a grid is visually forgiving as long as you correct over 2–3 frames rather than teleporting
- Cans, blasts and pickups are never predicted. A blast the client drew that the server didn't is a death the player will not accept. Latency on the drop feedback is better than a phantom explosion
7.3 Opcode table¶
Lock this early; it's cheap now and painful later.
| Op | Direction | Payload |
|---|---|---|
1 MATCH_CONFIG |
S→C | Map, pack, seed, roster, colours |
2 CLIENT_READY |
C→S | Loaded and prewarmed |
3 ROUND_START |
S→C | Countdown complete, tick zero |
4 INPUT |
C→S | {seq, dir, drop, kick} |
5 STATE_DELTA |
S→C | Positions, cans, blasts, pickups since last ack |
6 STATE_FULL |
S→C | Complete snapshot — join and reconnect only |
7 EVENT |
S→C | Discrete: pickup taken, pickup destroyed, player out, block destroyed, can detonated. Never delta-compressed — these drive audio and VFX |
8 ROUND_END |
S→C | Winner, scores |
9 MATCH_END |
S→C | Final standings, rewards |
10 FORFEIT |
C→S | Voluntary exit |
11 PING |
C↔S | RTT sampling |
Deltas for the common case, full snapshots for joins and recovery. EVENT is separate from STATE_DELTA because events drive audio and VFX and must not be dropped by delta compression.
7.4 Disconnection & reconnection¶
| Situation | Behaviour |
|---|---|
| Brief drop in match (<10s) | Chugger stands still, is still killable. Client retries with backoff, rejoins via STATE_FULL |
| Drop >10s | Player marked out for the round, may rejoin for the next round |
| Host drops in lobby | Host migration, longest-connected player |
| App backgrounded in match | Treated as a drop. iOS suspends the socket regardless |
| Drop in menus | Silent retry, connection pip goes amber, no modal |
| Server unreachable at boot | Offline mode, single player only, banner on Home |
Never show a blocking error dialog for a transient network state. The pip and an inline banner carry it. Modals are for actions that failed and need a decision.
8. Game Engine¶
8.1 Grid¶
- 11 columns × 13 rows, portrait, fixed — §1.2. Every map is exactly this size and the validator enforces it
- The map file defines every tile: solids, destructibles, empty floor, spawns and hazards are all authored glyphs. The engine places nothing and there is no procedural fill — Gameplay Rules §3.1
- Spawns are
Sglyphs assigned in reading order, 2–4 per map, at any position the author chooses - The border ring is solid in every map, enforced at validation
- Movement is continuous with grid-aligned collision, including corner assist — see Gameplay Rules §4. An earlier draft of this line specified integer tile-stepping; that was wrong and is superseded
One consequence for the loader: with fill removed, the board is fully deterministic from the file and needs no shared randomness to build. The match seed now covers only the drop table and cosmetic variant picks. That removes a whole class of desync from the boot path — see asset architecture §A.8.
8.2 Match state machine¶
INIT → COUNTDOWN → PLAYING → ROUND_OVER → (next round | MATCH_OVER)
↓
SUDDEN_DEATH
Sudden death triggers at T-30s. Closing walls, or the Beach Kiosk's tide mechanic from the style guide — either way, the field shrinks inward one ring at a time. Without this, two cautious players stall forever and your 90-second match becomes four minutes.
8.3 Round & match structure¶
- Best of 3 by default, 1 or 5 selectable
- Round timer 120s, then sudden death at 90s
- Round ends when one Chugger remains, or all remaining die simultaneously (draw — no point awarded)
- Inter-round: 5s results, then straight back in. Pickups reset; the board resets to the map's authored layout. Every round is played on an identical board — with no procedural fill there is nothing to reseed, which makes map count the replayability lever (Gameplay Rules §11)
8.4 Pause¶
Single player only. Multiplayer gets a non-blocking overlay. If pause exists in multiplayer at all, someone will use it as a stalling tactic within a week of launch.
8.5 Player AI¶
Runs client-side in single player, injecting inputs at the same interface remote players use. Four difficulty tiers, differentiated by behaviour, not by cheating:
| Tier | Behaviour |
|---|---|
| Easy | Random walk with blast avoidance. Drops cans near blocks. Ignores players |
| Normal | Pathfinds to pickups, drops on blocks, retreats to safe tiles. No chains |
| Hard | Targets players, sets traps in corridors, chains cans, contests pickups |
| Frenzy | Above, plus kick usage and predictive cutoff of escape routes |
Never give AI more range, speed or cans than the pickups it has actually collected. Difficulty comes from decision quality. Players detect stat cheating immediately and it destroys trust in the multiplayer matchmaking too.
Reaction delay per tier (300ms / 180ms / 90ms / 40ms) is the cheapest single lever for tuning difficulty.
8.6 Controls¶
Two schemes, selectable in Settings, floating joystick as default:
- Floating joystick — thumb anywhere in the lower-left zone, origin set on touch. Snaps to 4 directions with a small dead zone. Best for the one-handed portrait promise
- Swipe-to-move — directional swipe queues one tile of movement. Better for accessibility, worse for panic
DROP is a fixed button in the lower-right. One tap, one can, no hold, no charge — see §8.7. KICK appears in the same cluster only when an Ice Cube is held — a button that does nothing 60% of matches is a button players stop looking at.
Haptic on drop, on pickup, on death. Nothing else, or it becomes noise.
8.7 The can — timing and inputs¶
There is no pre-placement mechanic. Every can is already shaken. The player taps DROP, the can is placed on the current tile, and it detonates 2.0 seconds later. There is no hold-to-charge, no accelerometer, no meter, and no variable fuse.
This is the right call and it buys more than it costs:
- One discrete input. DROP is a tap, not a gesture with a duration, so it never competes with the movement thumb. The whole point of the floating joystick is that the left thumb is busy; a charge input on the right thumb would fight it
- No motion-sensor permission and no mechanic that breaks when the phone is flat on a table or the player is lying down
- Netcode gets simpler. A drop is a single timestamped event with a deterministic outcome. The server places the can at tick
Tand everyone knows it bursts atT + 30ticks. There is no charge state to reconcile, and no way for two clients to disagree about how shaken a can was - Threat assessment is uniform. Every can on the grid is the same 2-second clock, so a player reads the board by counting fuse rings, not by remembering who charged what. In a 200ms-readability game that matters more than the depth a variable fuse would add
Consequences to hold:
- The fuse ring is the entire timing UI (style guide §6). With no meter and no variable fuse, that ring is the only thing telling a player how long they have. Its priority in the draw order is now non-negotiable
- Depth moves to position and chaining, which is where it should be in this genre. The interesting decision is where and when to drop, plus how the blast propagates through other cans — not how long you held a button
- The
place_cananimation carries the shake as fiction, not as input: wind-up, shake, throw, recovery hop. The store copy's "give it a shake" stays honest because the Chugger does it, in one motion, on your behalf
8.8 Canonical timing constants¶
Three documents reference these. This table is the source; everything else cites it.
| Constant | Value | Notes |
|---|---|---|
| Fuse, total | 2.0s | From placement to burst. Fixed, never modified by any pickup |
| Armed state begins | T−0.8s | Swelling, seam glow, pitch rise (style guide §6) |
| Burst flash | 1 frame | Full-opacity white |
| Blast damage window | 0.15s | The only lethal period |
| Blast VFX duration | 0.4s | Damage window is over long before the foam is |
| Chain detonation | immediate | A blast reaching a live can detonates it that tick, ignoring its remaining fuse. A sliding can entering a live blast tile detonates the same way — Gameplay Rules §9.1 |
| Kicked can | keeps its original fuse | Sliding does not reset or extend the clock — an Ice Cube kick is a way to move a problem, not to postpone it |
| Timed effects | 5s / 5s / 3s | Coffee Bean, Honeycomb Wax, Glue |
Chain detonation is the one that needs a decision recorded rather than assumed. Immediate propagation is the Bomberman convention and it is what makes chains feel like chains. The alternative — a short fuse-shortening instead of instant detonation — reads as lag on a mobile screen.
9. Chat Component¶
An overlay, not a screen. Available in Lobby and Match Results only.
- Nakama channel scoped to the match ID, created on lobby open, destroyed on match end
- No global chat. No direct messages. No chat during live play
Why no in-match chat: nobody types during a 90-second real-time round, the panel occludes the field, and it is a moderation surface with no gameplay benefit.
Quick-chat only — locked, not recommended. A fixed set of 8–12 canned phrases and emotes, and no free text anywhere in the app. This was a recommendation until decision 83 set the age rating at 13+ (§15 Q6); it is now the thing that makes that rating defensible in a game whose art will attract players younger than it. It is also instant on mobile where typing is not, and is what most games in this genre converge on anyway. Free text can be added later; it cannot be taken away later, and adding it reopens the rating.
Moderation is still a launch requirement, not a phase two. A closed phrase set removes the authoring surface, not the reporting one — a player can still spam, and display names are user-generated text:
- Profanity filter, server-side, on send — client-side filtering is cosmetic. It governs display names in full, and the phrase set trivially, since a fixed set is filtered once at authoring time
- Report and block, with the reported message captured
- Mute in Settings
- Display name filtering at the same standard
10. Content Delivery¶
- Base app ships: core assets, HUD, four Chuggers with base skins, one arena pack, Classic flavour, Classic blast
- Additional packs: Unity Addressables, remote catalogue, downloaded on purchase or on first selection
- Maps: bundled baseline, server adds, never replaces. The base app ships a complete playable set for every bundled pack; the server may add maps to the rotation but may never replace or remove one. Three consequences for the loader: map IDs are permanent and namespaced (
diner/crossroads), a missing map is fetched at Lobby before the ready check (a.mapis under 4 KB), and server-added maps are validated server-side on upload — a bad bundled map breaks a build, a bad server map breaks a live match. See asset architecture §11.1 - Multiplayer pack rule: every player in a match needs the arena's assets. Either the host's arena is restricted to packs everyone owns, or the pack downloads for all players at Lobby. Recommendation: downloadable-on-demand and free to play, purchasable to host. Getting a taste of a pack you don't own is the strongest possible store pitch, and it removes the worst failure case — a lobby that can't start
11. Persistence¶
| Data | Where | Notes |
|---|---|---|
| Session tokens | Device secure storage | Keychain / Keystore |
| Settings | Local + storage sync | Local wins on conflict |
| Equipped loadout | Nakama storage, user-owned | Server authoritative |
| Owned cosmetics | Nakama storage, server-write only | Never client-writable |
| Currency | Nakama storage, server-write only | Client-writable currency is free currency |
| Stats | Nakama storage / leaderboards | Written by the match handler |
| Match history | Server | Last 20, for Profile |
| League points | Nakama leaderboard per club, server-write only | incr at MATCH_END. §17.10 |
| League placement | Nakama storage league, server-write only |
Tier, club ID, week ID |
| League history | Nakama storage league_history, server |
Last 8 weeks |
| League config | Nakama storage, server | Cut counts, point yield, club size, cutoff. Tunable without a client build |
| Single player progress | Local | No server dependency |
Rule: anything a player could gain by cheating is written only by the server. Client writes are limited to preferences and the equipped-cosmetic pointer, and even that is validated against ownership.
12. Development Environment¶
- Unity MCP server installed for agent access. Useful for scaffolding scenes, wiring prefabs and bulk asset operations — keep gameplay-critical code under normal review regardless of who wrote it
- Nakama dev instance should be separate from
85.215.123.235. Match handler changes take a server restart; you do not want that on the instance a playtest is running against - Build config: dev / staging / prod, differing in server host, log level and cheat menu availability. The cheat menu (spawn pickup, set range, kill player, skip round) pays for itself in the first week of tuning
13. Unity Scene Architecture¶
Three scenes, additive:
| Scene | Contents | Lifetime |
|---|---|---|
Bootstrap |
Network service, audio manager, state machine, DI root | Permanent |
UI |
All menu screens as prefab panels under one canvas stack | Permanent |
Match |
Grid, entities, VFX, match HUD | Loaded/unloaded per match |
Menu screens are panels in a navigation stack, not scenes. Scene loads on a mid-range Android phone cost hundreds of milliseconds and a hub-and-spoke menu that pays that on every tap feels broken.
Only Match is loaded and unloaded, which is also where the memory pressure is — unloading it between matches gives you a clean slate and a natural GC point.
14. Decision Log¶
| # | Decision | Status |
|---|---|---|
| 1 | Portrait 11×13 grid, landscape is a tablet presentation only | Locked — §1.2 |
| 2 | 4 players, version 1 only. Six is a costed v2 refactor | Locked — §1.1 |
| 3 | Silent device auth; registration is linking, never a gate | Proposed — §6 |
| 4 | Authoritative server match handler, TypeScript, 15 tick | Proposed — §7.2 |
| 5 | Client predicts movement only; never cans or blasts | Proposed — §7.2 |
| 6 | Chat in Lobby and Results only; quick-chat only, no free text | Locked — §9, §15 Q6 |
| 7 | No pause in multiplayer | Proposed — §8.4 |
| 8 | Sudden death from T-30s | Proposed — §8.2 |
| 13 | No pre-placement mechanic. Fixed 2.0s fuse, single-tap drop | Locked — §8.7 |
| 14 | Chain detonation is immediate; a kicked can keeps its fuse | Locked — §8.8 |
| 15 | A sliding can detonates on entering a live blast tile | Locked — Gameplay Rules §9.1 |
| 9 | AI difficulty via behaviour and reaction delay, never stats | Proposed — §8.5 |
| 10 | Menu screens are panels, not scenes | Proposed — §13 |
| 11 | Currency and ownership are server-write only | Proposed — §11 |
| 12 | TLS required before submission | Blocking — §6.4 |
| 16 | The map file defines every tile; grid fixed at 11×13 | Locked — Gameplay Rules §3 |
| 17 | Eliminated players spectate; Leave always available | Locked — Gameplay Rules §7.1 |
| 18 | Maps: bundled baseline, server adds, additive only | Locked — §10 |
| 19 | Six league tiers — Tin, Bronze, Silver, Gold, Diamond, Elite. Everyone starts Tin | Locked — §17.2 |
| 20 | Clubs of 50; tiers shard when they exceed it. Random shuffle, seeded on week ID | Locked — §17.4 |
| 21 | One league point per round won, plus one per completed match. No placement table, no subtraction | Locked — §17.3 |
| 22 | Rollover Sunday 22:00 UTC, fixed, no DST adjustment | Locked — §17.5 |
| 27 | Promotion tapers above Silver — 15 up from Tin, Bronze and Silver; 10 from Gold and Diamond. Demotion is 15 everywhere except Tin | Locked — §17.2.1 |
| 23 | Tin never demotes; Elite never promotes | Locked — §17.2 |
| 24 | Only matchmade Quick Match awards league points. Private, browsed and single-player award zero | Locked — §17.9 |
| 25 | Inactive players are frozen, not demoted; removed from allocation after two silent weeks | Proposed — §17.7 |
| 26 | Cut counts, point yield and club size are server config, not constants | Locked — §17.10 |
| 28 | Age rating 13+. No COPPA consent path, no Designed for Families track; quick-chat only, consent-gated analytics, no ad SDK at launch | Locked — §15 Q6 |
15. Open Questions¶
- Rounds or single elimination? Best-of-3 assumed. Single-round matches are faster but harsher on a player eliminated in the first 20 seconds — and spectating your friends for 70 seconds is a churn moment.
- ~~What does an eliminated player do?~~ Closed: spectate, with Leave available at any time. Gameplay Rules §7.1. Ghost mode is the first post-launch feature.
- Region routing. One server in Germany means playable EU latency, poor US, bad APAC. Fine for soft launch, needs a plan before wide release.
- ~~Maps server-delivered, bundled, or hybrid?~~ Closed: bundled baseline plus additive server maps — §10.
- Regional store presence. Nakama has no IAP validation for every store variant out of the box; decide whether purchases validate server-side through Nakama's IAP hooks or a separate service.
- ~~Age rating and COPPA.~~ Closed: the store listing targets 13+ — decision 83, taken in implementation-plan M0 so that the three things downstream of it are built once. A cartoon soda game will still attract under-13 players; targeting 13+ is a statement about who the app is offered to, and the design has to earn it rather than declare it. What follows: - Chat is quick-chat only, permanently. The fixed phrase set of §9 with server-side filtering on send is not a launch simplification any more — it is the thing that makes 13+ defensible. Free text is the change that would reopen the rating - Analytics is a consent-gated GDPR flow, not a COPPA parental-consent flow: one opt-in screen, refusable without losing access to the game, and no SDK that fires before the answer - No ad SDK at launch (Economy & Progression §8). If ads are ever added they are opt-in rewarded only, and this decision is re-examined first - No COPPA verifiable-consent path and no Google Play Designed for Families track are built - The gambling half of this question was already closed independently — no loot boxes, no gacha, no paid Surprise Cubes, ever (Economy & Progression §1.3). That holds whatever the rating is
One residual, and it is not the rating. A 13+ store rating is not a 13+ consent age. GDPR Article 8 lets each member state set the digital-consent age between 13 and 16, and Germany — the intended soft-launch region — sets it at 16. Consent-based analytics for 13–15 year olds in the EU therefore needs parental authorisation or a lawful basis that is not consent. That is a consent-flow problem for the submission checklist, not a reason to move the rating. 7. ~~Does single player feed progression?~~ Closed in full. Single player awards no league points (§17.9), and pays full XP and Sugar Cubes for the first 5 matches per UTC day, zero beyond — the capped daily award named here as the usual compromise. Economy & Progression §1.2. 8. ~~Should Elite be scarce?~~ Closed: yes. Promotion tapers to 10 at Gold and Diamond against a flat 15 demotion, settling Elite near 8.7% of the player base rather than 17% — §17.2.1. What remains is a tuning question: if Elite still inflates after four weeks of live data, Diamond's promotion count drops before anything else moves. 9. ~~Participation point?~~ Closed: yes, +1 per completed match — §17.3. It carries one standing constraint: Quick Match must never expose a round-count option unless the participation point is scaled with it, or short matches become the optimal grind. 10. Is 22:00 UTC the right instant? It is 23:00 or midnight in Berlin depending on the season, and mid-afternoon in the Americas. If the intent is "Sunday night" for a specific launch region, the value changes; the fixed-offset decision does not. Confirm the number against the soft-launch region before the first week runs. 11. ~~Rollover rewards.~~ Closed: the rollover pays nothing in version 1. Leagues ship as pure status; the Leagues screen keeps its reserved reward strip, and a tier × movement payout shape is pre-approved if payouts are ever added. Economy & Progression §3.2.
16. Cross-Document Consistency Register¶
Five documents, one project. This section says who owns what, so that when two documents disagree there is a rule rather than an argument.
16.1 Ownership¶
| Subject | Authoritative document |
|---|---|
| Colour values, silhouette rules, character design, arena mood, VFX budget, audio | Design Style Guide |
| Asset slots, naming, canvas sizes, atlases, draw order, map file format, validation gates | Asset Architecture |
| Screens, navigation, state machines, netcode, session model, persistence, player count, grid dimensions | Framework & Flow (this document) |
| Leagues, leaderboards, weekly progression, club allocation | Framework & Flow (this document) — §17 |
| Simulation rules, movement, collision, blast, pickup effects, balance constants, resolution order | Gameplay Rules & Mechanics |
| Currency, XP, prices, yields, level curve, rollover payout policy | Economy & Progression |
Where a subject spans two documents — HUD layout, for instance — the style guide owns appearance and this document owns structure and position. For pickups: Gameplay Rules owns effect and cap, Asset Architecture owns silhouette and animation.
The simulation is specified in Gameplay Rules, not here. §8 of this document covers the match as an application state; the rules of the game itself — movement model, blast propagation, self-damage, the walk-off rule, drop table, resolution order — live in the fourth document and it supersedes §8 wherever they overlap.
16.2 Live contradictions to resolve¶
Found while reconciling the three documents at version 0.3. None are blocking today; all get more expensive with every asset authored against them.
1. The Reinforced tier is unbuildable as specified. Style guide §7 opens with "three tiers… no arena adds a fourth" and then lists four, including Reinforced (two blasts — ice blocks in the Diner, ice buckets at the Beach Kiosk). But the asset architecture slot manifest (§5.1) has no reinforced slots, and the map format (§A.5) has no glyph for it. As written, a two-hit block cannot be placed in a map or shipped in a pack.
Recommendation: cut Reinforced from version 1. It adds a damage-state system, a second break animation set per family, a new glyph, and a new validation rule, to deliver a block that is a slower version of one you already have. Reserve a glyph in format 2 and revisit. If you keep it instead, it needs slots and a glyph before pack one is authored.
2. Half the Diner and Beach hazards have no representation in the map format. The style guide specifies bendy straw teleporters (paired), syrup puddles, ricocheting bottle caps and a timed soda fountain. The map format has exactly one hazard glyph, H, which resolves to ambient_animated with collision. Teleport pairs are T, reserved for format 2. Ricochet and floor-state puddles have no representation at all.
Recommendation: version 1 hazards are whatever
Hcan express — a tile that is periodically lethal, on a pack-defined timer. That covers the soda fountain and the Beach Kiosk tide, which is enough for launch. Teleporters, ricochet and persistent floor states move to format 2 with the glyphs already reserved for them.
3. No rules remain open. Everything left in Gameplay Rules §15.1 is a tuning number that only playtesting answers. Two netcode consequences of the decisions taken there:
- Blasts destroy uncollected pickups — a
pickup_destroyedcase on theEVENTopcode, distinct frompickup_taken - The live-blast tile set crosses tick boundaries (Gameplay Rules §9.1) — it is the only simulation state that does, and it must be included in
STATE_FULLor a reconnecting client can walk a can through a blast the server knows about and it does not
4. Leagues need art that no slot manifest had a slot for — RESOLVED. §17 requires six tier badges (Tin through Elite), promotion and demotion band treatments, and a small tier chip for the Home badge. None of these existed in any manifest.
Resolved: tier badges are Core, non-variant assets — identical in every arena, every skin, every flavour, per Asset Architecture §1. The slots are specified in Asset Architecture §6.6 and the colour law in Style Guide §3.4. Two constraints those sections carry, restated here because they are the ones most likely to be lost: the band colours must survive the deuteranopia verification applied to the four player colours (Style Guide §3.2) and stay clear of the warm hazard band (§3.3), and the six badges must stay distinguishable in greyscale at 32px, which is a release blocker under Asset Architecture §8.
5. Round points are now load-bearing outside the match. Gameplay Rules §11 defines the round point as a match-local scoring pip. §17.3 makes it the unit of weekly progression, which means tuning the round structure now tunes the ladder. Changing rounds-per-match, or the draw rule in §11.1, changes the rate at which the entire player base climbs.
No change required today — the pip is still owned by Gameplay Rules and §17 consumes it without redefining it. But the dependency should be noted in that document the next time it is revised, because it is invisible from there.
16.3 Resolved by this version¶
| Was open in | Question | Resolution |
|---|---|---|
| Style guide | Final arena tile dimensions | 11×13 portrait grid; 192px authoring reference (asset arch §4); ~98 logical px on the reference phone (§5.8.1) |
| Framework 0.1 | Player count | 4, version 1 only (§1.1) |
| Framework 0.1 | Grid orientation | 11 columns × 13 rows, portrait (§1.2) |
| Style guide | Hold-to-charge or device shake? | Neither. The mechanic is cut — cans are pre-shaken, fuse is a fixed 2.0s (§8.7) |
17. Leagues & Leaderboards¶
This is the retention system. Everything else in this document serves a single match; this section is the reason a player opens the app on a Tuesday. It is almost entirely server-side — the client displays a standing it never computes and never writes.
Scope boundary. This section defines placement: how points are earned, how players are grouped, how the ladder moves. It does not define rewards. Rollover payouts belong to Economy & Progression, which decides the question as none in version 1 (Part 5 §3.2) — and §17 is written so that rewards can be bolted on later without touching the placement logic.
17.1 The model in one paragraph¶
Every player belongs to exactly one club — 50 players competing over one week. A club sits at one of six league tiers. You earn league points by playing matchmade games: one per round won, plus one for finishing the match. At 22:00 UTC on Sunday the week closes: the top of every club moves up a tier, the bottom 15 move down, the rest hold. Everyone is then re-dealt into fresh 50-player clubs at their new tier and the score resets to zero.
The promotion count narrows at the top. Fifteen promote out of Tin, Bronze and Silver; only ten promote out of Gold and Diamond. Demotion is fifteen everywhere. That asymmetry is what makes the ladder a pyramid rather than a column — §17.2.1.
17.2 The ladder¶
Six tiers, bottom to top:
| # | Tier | Promotes to | Promote | Hold | Demote | Demotes to |
|---|---|---|---|---|---|---|
| 1 | Tin | Bronze | 15 | 35 | — | — (floor) |
| 2 | Bronze | Silver | 15 | 20 | 15 | Tin |
| 3 | Silver | Gold | 15 | 20 | 15 | Bronze |
| 4 | Gold | Diamond | 10 | 25 | 15 | Silver |
| 5 | Diamond | Elite | 10 | 25 | 15 | Gold |
| 6 | Elite | — (ceiling) | — | 35 | 15 | Diamond |
Counts are given at the reference club size of 50. As proportions: 30% promote below Gold, 20% promote from Gold and Diamond, 30% demote everywhere except Tin. Under-strength clubs scale by proportion (§17.4); nothing hard-codes 15 or 10.
- Every new account starts in Tin. There is no placement match, no seeding, no MMR-based entry. A player's first week is the placement
- Tin never demotes. The bottom 15 of a Tin club hold their tier
- Elite never promotes. The top of an Elite club holds
- The narrowing happens at Gold. Below that, the ladder is a conveyor and it should be — a player who plays regularly should reach Gold without being filtered. Above it, promotion is a cut
- Tier is a display and grouping concept only. It is not matchmaking input in version 1 — see §17.9
17.2.1 Why the taper exists, and what shape it produces¶
The counts above are not arbitrary, and the reasoning should survive the first person who proposes simplifying them.
Flat counts produce a column, not a ladder. Between any two adjacent tiers, the up-flow is promote_rate × population_below and the down-flow is demote_rate × population_above. Those balance when the populations are in the ratio promote_rate ÷ demote_rate. With 15 up and 15 down at every boundary, that ratio is 1 — every tier holds the same number of players and one in six ends up in Elite. A top tier containing 17% of the player base is not a top tier.
Promoting 10 from Gold and Diamond fixes it for the cost of two config values. The ratio above Silver becomes 20% ÷ 30% = two-thirds, so each of the two highest tiers settles at two-thirds the size of the one below.
| Tier | Share of players at equilibrium |
|---|---|
| Tin | 19.6% |
| Bronze | 19.6% |
| Silver | 19.6% |
| Gold | 19.6% |
| Diamond | 13.0% |
| Elite | 8.7% |
Roughly one player in eleven reaches Elite instead of one in six, and the four lower tiers stay flat — which is correct, because those tiers exist to move a regular player upward, not to filter them.
Three caveats on those numbers.
- It is an equilibrium, not week one. Everyone starts in Tin, so the distribution fills from the bottom and takes roughly as many weeks as there are tiers to approach these shares. Do not read the week-3 numbers as a failure of the model
- Inactivity distorts it. Frozen players (§17.7) hold their tier without contributing to either flow, which slows the whole system and thickens the middle. The live distribution will be flatter than the table above
- These are the levers if it drifts. Promotion and demotion counts are per-boundary server config (§17.10). If Elite still inflates after four weeks of real data, drop Diamond's promotion to 8 before touching anything else — it is the single highest-leverage number in the section
17.3 League points¶
One league point per round won, plus one for completing the match. No separate scoring model, no placement table, no kill counters — the round pips a player already watched accumulate on the Round Results screen are the points, and they are credited in a lump at MATCH_END.
| Event | Points |
|---|---|
| Round won | +1 |
| Match completed | +1, regardless of result |
| Round drawn (simultaneous death) | 0 — the round is replayed, Gameplay Rules §11.1 |
| Round lost | 0 |
| Match won | 0 additional — the match win is already worth more rounds |
| Forfeit, or disconnect beyond the §7.4 threshold | 0 for the whole match, retroactively — including the participation point and any rounds already won |
The participation point is what makes the week survivable for the median player. Without it, someone who loses every round scores zero across a whole evening and can finish a week on nothing, which reads as the game telling them not to bother. One point for finishing means showing up always moves the bar — a losing player still climbs, just far more slowly than a winning one. A best-of-3 pays 1 to the player who won nothing and 4 to the player who swept it, which is a wide enough gap to keep winning worth it.
Two things this deliberately does not do. It is not awarded for a forfeit or a long disconnect, or quitting a losing match would pay the same as finishing one. And it is not scaled by match length — see below.
Consequences to accept deliberately:
- A best-of-5 yields more round points than a best-of-3. Roughly proportional to time spent, which is the correct relationship. No normalisation
- The participation point is flat, and that would favour short matches — except Quick Match does not let the player choose. A fixed +1 on a 3-minute match is worth more per minute than the same +1 on a 10-minute one, so a player able to pick best-of-1 would grind those exclusively. Quick Match takes no configuration (§5.4) and the matchmaker sets the round count, which closes the exploit structurally rather than with a rule. If Quick Match ever gains a round-count option, the participation point has to scale with it or the option has to be dropped — this is the constraint that will be forgotten
- Points never decrease. There is no subtraction, ever. A losing streak costs a player rank relative to their club, which is punishment enough and reads as fair. Visible point loss in a casual game is the single most reliable source of one-star reviews
- Every point yield is a config key.
point_yield: {round_won: 1, match_completed: 1}lives inleague_config(§17.10). Tuning the ladder's climb rate must never require a client build
17.4 Clubs¶
A club is 50 players. It is the whole of the leaderboard a player ever sees, and the number is chosen so that the standing fits comfortably in a scrollable list, a rank feels legible ("31st of 50" means something) and both cut lines are visible after a few flicks.
| Property | Value |
|---|---|
| Target size | 50 |
| Minimum viable size | 30 |
| Lifetime | One week. A club is created at rollover and destroyed at the next |
| Identity | {week_id}:{tier}:{shard_index} — server-generated, never reused |
| Membership | Fixed at rollover, plus mid-week backfill (§17.8) |
Sharding. When a tier holds more than 50 players — which it will, immediately, in every tier below Elite — the tier's population is dealt into as many clubs as it takes. Assignment is a uniform random shuffle seeded on the week ID, so the job is reproducible if it has to be re-run.
- Random, not score-seeded. Grouping the tier's strongest 50 into one club builds a hidden second ladder inside every tier and makes promotion depend on which shard you landed in far more than on how you played
- Remainder rule: no club below 30. If the final shard would be short, redistribute across the tier's clubs so every club at that tier sits between 30 and 50. A tier with fewer than 30 players total forms one under-strength club and that is fine
- Cut lines scale with club size, and differ by tier. Promotion is 30% of the club below Gold and 20% at Gold and Diamond; demotion is 30% everywhere except Tin. At the reference size of 50 that is the 15 / 10 / 15 of §17.2. Under-strength clubs use
round(rate × size). Never hard-code either number, and never assume promotion and demotion counts match - Regional bucketing is a post-launch option. Sharding by region before shuffling would align club activity with a shared evening and make standings move when players are watching. It also fragments small tiers. Not v1
17.5 The weekly rollover¶
Sunday, 22:00 UTC. The single most important line in this section, and the one most likely to be got wrong.
- UTC, fixed, no daylight adjustment. A job scheduled against a DST-observing zone either runs twice or not at all one week a year, and both failures land on a live leaderboard. The cutoff is an absolute instant
- The client renders the countdown in device-local time, never as "22:00". A Berlin player sees 23:00 in winter and midnight in summer; a São Paulo player sees Sunday evening. The countdown is a duration, and durations do not have this problem
- Matches are credited to the week that is open when
MATCH_ENDfires. A match still running at 22:00:00 credits to the new week. Announce it that way in the UI — "results after the reset count toward next week" — rather than trying to drain in-flight matches. Matches run 4–7 minutes; a drain window is a five-minute race condition in exchange for nothing
The job, in order. Compute everything against a snapshot, then apply.
- Freeze at 22:00:00. Close point writes to the outgoing week
- Snapshot every club at every tier, resolved to final rank order (§17.6)
- Resolve movements for all clubs — promote / hold / demote — against the snapshot only
- Collect each tier's incoming population: holds, plus promotions from below, plus demotions from above
- Deal each tier into clubs of 50 (§17.4)
- Write new placements, zero the scores, open the new week
- Publish each player's outgoing result — old rank, points, movement — for the post-rollover notification
Step 3 is where the bug lives. If movements are applied tier by tier as they are computed, a player promoted out of Silver can be picked up again as part of Gold's population and evaluated twice in one rollover. Resolve against the frozen snapshot, apply once.
The job must be idempotent and resumable. It touches the entire player base at a fixed instant, which means it will eventually be interrupted by a deploy, a restart or an outage. Key every write on week_id so a re-run overwrites rather than double-applies, and checkpoint per tier.
17.6 Rank order and tie-breaks¶
At the cut lines, ties are not a display nicety — they decide who moves. The order is fully deterministic:
- League points, descending
- Earliest timestamp at which the player reached that point total — first to arrive holds the higher rank
- User ID, lexicographic ascending — a final tie-break that can never itself tie
Rule 2 is worth the extra column. It rewards playing early in the week rather than sniping the boundary on Sunday evening, it is intuitive when explained, and it makes the standing stable: a player's rank can only be taken by someone who actually overtakes them, so the list stops shuffling under a reader's thumb for no visible reason.
Store the reach-timestamp on the same write as the point increment. Reconstructing it later from match history is not possible once points have been aggregated.
17.7 Inactivity¶
| Situation | Behaviour |
|---|---|
| Played at least one scoring match this week | Ranked normally, promotes or demotes as the standing says |
| Played none | Frozen. Holds tier, does not demote. Occupies the bottom of the standing at 0 points and is drawn greyed |
| Two consecutive silent weeks | Removed from club allocation. Tier is retained on the account |
| Returns after removal | Placed into a club at their retained tier at the next rollover, or backfilled immediately per §17.8 |
Not demoting the inactive is the deliberate half. A player who took a week off and returns to find they have been knocked down a tier for not playing has been punished for having a life, and that is a churn event. The system's job is to reward playing, not to penalise absence.
Removing them after two weeks is the other half, and it matters more than it looks. Dead accounts sitting at zero make every club look abandoned and quietly guarantee that the bottom 15 is mostly ghosts — which makes demotion meaningless for the active players above them. Clubs must be built from people who are actually going to play this week.
17.8 Mid-week joins and backfill¶
New and returning players do not wait for Sunday.
- A player needing a club is placed into an existing club at their tier with a free slot, preferring clubs opened this week and least-full first
- If no club at that tier has a slot, open a new one. It may sit under 50 for the rest of the week; the 30-minimum applies at rollover, not at creation
- A backfilled player joins at 0 points with the week already in progress, is eligible for promotion and demotion on the same terms, and is flagged
joined_latein the payload so the client can render a small marker rather than leaving them looking like they lost badly
A brand-new account therefore lands in a Tin club within seconds of finishing its first match, which is the point — the ladder has to be visible in session one or it is not a retention system.
17.9 What earns points, and what farming this invites¶
Only matchmade Quick Match results award league points. Everything else awards zero:
| Mode | Awards league points |
|---|---|
| Quick Match (matchmaker ticket, §5.4) | Yes |
| Match Browser lobby | No |
| Private match by code | No |
| Single player vs AI | No |
This is not a scope cut, it is the anti-collusion rule. Two accounts in a private lobby can trade rounds indefinitely; there is no clever detection that beats a determined pair, and the only robust answer is that hand-assembled matches never touch the ladder. It also settles the league half of §15 question 7 — single player cannot feed the league, whatever the economy document decides about currency.
Three further guards:
- Daily soft cap. After 20 scoring matches in a UTC day, yield halves. Legitimate heavy play is unaffected; scripted farming stops being linear
- Forfeit and long disconnect score zero for the entire match, retroactively, including rounds already won and the participation point. Otherwise the optimal play is to win round one and quit
- A match in which the player issued no input scores zero. The participation point creates an incentive that did not previously exist — queue, stand still, collect 1, repeat. The match handler already sees every
INPUTmessage, so requiring at least one movement or drop input per round is a cheap check. Without it, the cheapest farm in the game is doing nothing - Queue-sync collusion is a real soft-launch risk and is not solved here. With a small population, two accounts queueing simultaneously will land in the same match often enough to be exploited. Instrument it — log the frequency of repeat roster sets per rolling hour — and hold the countermeasure until the data says it is needed. Do not build a heuristic against a population that does not exist yet
Tier is not matchmaking input in v1. A Tin player can and will be matched against Elite. Splitting the matchmaker by tier at soft-launch population sizes means queue times measured in minutes, which is a worse problem than an uneven match. Revisit when concurrency supports it.
17.10 Server implementation¶
Nakama has most of this. Do not build a bespoke scoring store.
| Concern | Mechanism |
|---|---|
| Club standing | One Nakama leaderboard per club, ID league:{week_id}:{tier}:{shard}, created on demand at rollover |
| Point credit | leaderboardRecordWrite with the incr operator, called from the match handler at MATCH_END. Never from the client |
| Tie-break metadata | Record subscore = reach-timestamp; metadata carries joined_late and match count |
| Placement | Nakama storage collection league, key = user ID, server-write only. Holds tier, club ID, week ID |
| History | Storage collection league_history, last 8 weeks. Enough for a "best tier reached" line on Profile |
| Rollover | A scheduled server job, not a Nakama tournament reset — the re-sharding step has no native equivalent |
| Config | league_config storage record: per-boundary promote/demote counts, point_yield, club size, daily cap, cutoff instant. All tunable without a client build |
Nakama's tournament feature is the wrong tool here despite the surface fit. It gives resets and durations, but it does not give per-shard cohorts of 50 that are re-dealt every cycle, and bending it into that shape costs more than the scheduled job does.
Load. The rollover reads and rewrites one record per active player at a fixed instant, once a week. At soft-launch scale this is trivial; at scale it is the single largest write burst the backend ever takes. Batch per tier, checkpoint between tiers, and do not schedule anything else at 22:00 UTC Sunday.
17.11 Client responsibilities¶
The client displays. It does not compute.
Everything below arrives in one RPC, league_get:
{
"week_id": "2026-W31",
"closes_at": 1785362400,
"tier": "silver",
"club_id": "2026-W31:silver:0042",
"club_size": 50,
"promote_cut": 15,
"demote_cut": 36,
"you": { "rank": 22, "points": 14 },
"standings": [
{ "rank": 1, "name": "…", "chugger": "nim", "skin": "…", "points": 41, "joined_late": false }
]
}
- The cut lines come from the server, as ranks. The client must never derive them from a hard-coded 15 — the Gold and Diamond taper (§17.2.1) and under-strength clubs both break that assumption immediately. The example above is a Silver club, where
promote_cutis 15; the same client renders a Gold club withpromote_cut10 and no code change - Payload is ~4 KB for a full 50-row club. Fetch it whole; there is no pagination
- Cache for 60 seconds. Refresh on entering the screen, on
POST_MATCH, and on app foreground - Offline shows the last cached standing with a staleness marker and a disabled countdown. Leagues never block Home and never gate Play
- The countdown is computed from
closes_atagainst device clock, and must tolerate a wrong device clock by clamping at zero rather than displaying negative time
17.12 Where it appears¶
| Screen | Addition |
|---|---|
| Home (§5.2) | Tier badge on the player badge chip → Leagues. Notification dot when an unseen rollover result is waiting |
| Match Results (§5.10) | The league beat — points earned, new total, rank delta. Quick Match only; silent in every other mode |
| Profile (§5.13) | Current tier, best tier reached, last 8 weeks |
| Leagues (§5.17) | The standing itself |
The Match Results beat must not delay REMATCH. It animates alongside the standings, not as a gate in front of them. A rank ticker that has to finish before a player can queue again is a system that punishes engagement.
17.13 Failure modes to design against¶
- The rollover job does not run. Players see a countdown at zero and a frozen standing. The client must render
closes_atin the past as "Results are being calculated", not as a negative number or a stuck timer. Alert on the job, not on the symptom - A club empties. Everyone in a 30-player club goes silent by Wednesday. Nothing breaks — the cut still resolves — but the screen is a wall of zeroes. Backfill (§17.8) preferring least-full clubs is the mitigation
- A player's placement record is missing. Treat as a new player: allocate a Tin club, log loudly. Never show an empty leagues screen and never fail Home because of it
- Two devices, one account, simultaneous matches. The
incroperator is atomic, so points are correct. The reach-timestamp is whichever write landed second — acceptable, and not worth a lock
Part 3 — Design Style Guide¶
Version 0.6 — Pre-production
Changes in 0.6: §3.4 added — colour law for league tiers and the promotion/demotion bands, required by the leagues system in Framework & Flow §17. The demotion band is explicitly barred from the warm hazard band. §11 notes the league chip on Home. Changes in 0.5: Seagull power-up theft cut — seagulls remain as ambient dressing only. Changes in 0.4: Cross-referenced to the new Gameplay Rules & Mechanics document. Changes in 0.3: Pre-placement mechanic cut. All cans are pre-shaken with a fixed 2.0s fuse — no Pressure meter, no bubble-streak shake level, no charge input. §6 rewritten around a single can behaviour. Closing open question removed. Changes in 0.2: Reconciled with the Asset Architecture and the Framework & Flow documents. Player count confirmed at 4 for version 1. Grid confirmed at 11×13 portrait. Shake meter renamed Pressure per asset architecture §6.2. HUD section rewritten for the portrait layout. Reinforced tier flagged as unbuildable in version 1. Tile dimension question closed.
A 4-player grid brawler. Anthropomorphic critters called Chuggers drop shaken soda cans and run. Two seconds later the can bursts, spraying foam down four lanes. Last Chugger fizzing wins.
1. Design Principles¶
Every art decision on this project answers to five rules. When a choice is contested, the earlier rule wins.
1. Readable in 200 milliseconds. The player is scanning a grid mid-panic on a 6-inch screen. Every tile must be classified — safe, breakable, solid, deadly — by silhouette and colour alone, at arm's length, with a thumb covering the bottom third of the display. If it only reads when the player stops to look at it, it has failed.
2. Danger is warm. Safety is cool. Anything that will hurt the player trends orange-red-yellow. Anything inert trends blue-grey-teal. This mapping is never broken for aesthetic reasons, in any arena, in any seasonal skin.
3. Silhouette before detail. Every asset must be identifiable as a pure black shape. Detail is a reward for players who look closely — it is never load-bearing.
4. Cartoon physics, honest hitboxes. Art can squash, stretch, wobble and overshoot as much as it likes. The collision grid underneath is rigid, square, and never lies. Foam VFX must never extend visually past the tiles it actually damages.
5. Funny, not silly. The humour is in the situation — a shaken can, a bad decision, a friend's timing. It is not in random gags, memes, or references. The world takes itself seriously; the player does not.
2. Brand Identity¶
2.1 The In-World Brand¶
All cans in the game are Frenzy Cola — a fictional soda brand that owns the fiction. This gives us a logo we control, a mascot for the can itself, and a clean in-world reason for flavour variants that double as monetised skins.
Frenzy Cola's own visual identity is deliberately retro-commercial: mid-century diner signage, hand-painted lettering, sun-faded advertising. It looks like a brand that has been around since 1955 and has never updated its packaging. This is what makes both arenas cohere — the Diner is where Frenzy Cola came from, the Beach Kiosk is where it goes on holiday.
2.2 Logo¶
The Fizz Frenzy wordmark is not the Frenzy Cola logo. Keep them separate.
- Heavy rounded sans, custom-drawn, slight upward arc
- The two Zs in "Fizz" carry the personality: angled like bubbles rising, never symmetric with each other
- Bubble cluster escaping the top-right of the mark — always odd-numbered, never a tidy arc
- Minimum clear space: the height of the cap F on all sides
- Never: outlined-only, rotated past 5°, on a busy photographic background, recoloured outside the approved palette
3. Colour¶
3.1 Core Palette¶
| Role | Name | Hex | Use |
|---|---|---|---|
| Primary | Frenzy Red | #E63B2E |
Logo, cans, primary CTAs |
| Primary | Cola Brown | #4A2618 |
Can bodies, deep shadow, outlines in warm areas |
| Accent | Fizz Cream | #FFF4E0 |
Foam, highlights, UI text on dark |
| Accent | Bottle Teal | #1FA5A0 |
Safe zones, cool props, secondary UI |
| Accent | Sunbeam | #FFC42E |
Power-ups, rewards, currency |
| Neutral | Slate | #2B3440 |
Indestructible walls, HUD chrome |
| Neutral | Chrome | #C9D4DC |
Metal, highlights on solid geometry |
3.2 Player Identity Colours¶
Locked and never reused for anything else on the field.
| Player | Hex | Notes |
|---|---|---|
| P1 | #E63B2E Red |
|
| P2 | #2E7FE6 Blue |
|
| P3 | #3FBF54 Green |
|
| P4 | #F2A23C Orange |
Deliberately warmer than Sunbeam so it never reads as a pickup |
Colour-blind support is mandatory, not optional. Each player also carries a permanent shape badge — circle, square, triangle, star — on the body and on every UI element referencing them. The player colours above are chosen to survive deuteranopia; verify P3/P4 in simulation before locking any skin.
Four is the version 1 player count and these four colours are the complete set. A fifth and sixth colour is a version 2 problem, and a genuinely hard one — two more hues that survive all three colour-blindness simulations, stay clear of the warm hazard band in §3.3, and remain distinct from Sunbeam and from each other. Do not pre-author them; see Framework & Flow §1.1.1.
3.3 Hazard Colour Law¶
- Live can, armed: pulses Frenzy Red → Sunbeam, accelerating as the fuse burns
- Foam blast: Fizz Cream core, Frenzy Red rim
- Blocked / solid: never uses any warm hue above 20% saturation
- Power-up: always Sunbeam-cored, regardless of arena
An arena skin may change everything else. It may never change these four.
3.4 League & Progression Colour¶
The six league tiers and the two standing bands are the only place in the product where colour carries status rather than danger. That makes them a real risk to the hazard law above, and the rules below exist to contain it.
| Tier | Base | Hex | Treatment |
|---|---|---|---|
| Tin | Dull grey-blue | #8A97A3 |
Flat, matte, no specular. It should look cheap |
| Bronze | Warm brown | #A5703C |
Low specular. Kept desaturated so it never reads as a hazard cue |
| Silver | Cool light grey | #C9D4DC |
Chrome from the core palette. Broad soft highlight |
| Gold | Deep yellow | #D9A21B |
Darker and less saturated than Sunbeam #FFC42E |
| Diamond | Pale cyan | #7FD8E8 |
Translucent read, faceted highlight |
| Elite | Fizz Cream on Slate | #FFF4E0 / #2B3440 |
The only two-tone badge, and the only animated one |
Four rules, in priority order:
- Gold is not Sunbeam. Sunbeam
#FFC42Emeans power-up, everywhere, permanently. A Gold badge at chip scale sitting near a currency counter must not read as a collectable. Hold Gold at least 15% darker and 20% less saturated, and verify the two side by side before locking - The demotion band is never in the 10°–45° hazard band. Use a desaturated slate-mauve — drab and sagging, per the debuff motion vocabulary in §10. Red on a leaderboard teaches the player that warm sometimes means merely bad, and that lesson costs a life on the grid
- The promotion band is cool and rising, drawn from Bottle Teal at low saturation. It must not use any player identity colour from §3.2 — a green promotion band next to P3's row is a false association
- All six badges survive greyscale at 64px. Bronze, Gold and Tin collapse toward the same mid-value when desaturated, so silhouette carries the tier, not fill — see Asset Architecture §6.6.1. Verify in deuteranopia, protanopia and tritanopia simulation, the same gate the player colours passed
Tiers never appear on the play field. No badge, band or rank indicator renders inside the grid, in the HUD band, or on a Chugger. Status lives in the menus; the field is for danger.
4. Typography¶
| Role | Face | Treatment |
|---|---|---|
| Logo & headers | Custom rounded display | Hand-drawn, heavy, slight arc |
| UI & body | Nunito ExtraBold / Baloo 2 | Rounded terminals, generous counters |
| Numerals (timer, score) | Same, tabular figures | Non-tabular figures jitter and look broken when counting down |
- Minimum on-device size: 28px for anything the player must read during a match
- All in-match text carries a 3px Cola Brown outline — the background is chaos and unoutlined text disappears into it
- Never use all-caps for anything longer than three words
- No thin or light weights anywhere in the product
5. Character Design — The Chuggers¶
5.1 What They Are¶
Small, greedy, can-hoarding critters. They are creatures, not containers — this is a hard rule. The can is a distinct game object sitting on the grid, and if the player silhouette is also can-shaped, the two compete for recognition at exactly the moment recognition matters most.
5.2 Proportions¶
- 2.5 heads tall. Big head, small body, stubby limbs.
- Body fits within a single grid tile at 85% width — the 15% margin is what lets players judge whether they'll clear a gap.
- Head is the widest point. The silhouette reads top-heavy and slightly unstable, which sells the comedy of running away in a panic.
- Feet are simple ovals. No visible ankles, no toes, no shoes with detail.
5.3 Silhouette Rules¶
- Distinct from a can at 32px: cans are vertical cylinders, Chuggers are wide-topped blobs
- One strong asymmetric feature per character — an ear, a tuft, a tail — so players can distinguish characters even with the colour drained out
- Nothing thinner than 4px at reference resolution; thin appendages vanish on phone screens and alias badly in motion
- Held can always sits in the same position relative to the body, at 60% scale, so "this player is armed" is legible from any angle
5.4 Expression¶
Faces are simple: two eyes, a mouth, no nose. Emotion comes from eye shape and body posture, not from detailed features.
Four required states, each distinguishable in peripheral vision: - Idle — half-lidded, bored, subtle breathing bob - Running — wide eyes, lean into direction of travel, ears/tufts trail behind - Panic (within blast radius, 0.5s before detonation) — pinprick pupils, mouth wide, whole body vibrating - Popped — comedic, not violent. The Chugger inflates, wobbles, and deflates with a raspberry, spinning off-screen. No gore, no damage state, no lingering corpse on the tile.
5.5 Launch Cast¶
Four characters, one per player colour, each a different animal with a strong distinct silhouette. The raccoon is the mascot and the default — raccoons and cans are a pairing that requires no explanation.
| Character | Animal | Silhouette hook |
|---|---|---|
| Rax | Raccoon | Mask, ringed tail — the mascot |
| Puff | Pufferfish | Round, spiny, inflates when panicked |
| Nim | Squirrel | Enormous vertical tail |
| Bolt | Lizard | Low, wide, long horizontal tail |
Skins reskin the character but never alter the silhouette envelope. A seasonal hat may exceed the head bounds by no more than 20%, and never introduces a shape that could be mistaken for a power-up.
6. The Can¶
The single most important object in the game. It gets its own rules.
Three states, each unmistakable:
- Placed — sits upright, slight wobble, cool-toned, fuse ring at the base showing time remaining as a depleting arc. The full fuse is 2.0 seconds, fixed, on every can in the game
- Armed (final 0.8s) — visibly swelling, seams glowing, pulse accelerating, hissing louder
- Burst — one frame of white-hot flash, then foam sprays four lanes
The fuse ring is the most important UI element in the entire game and is never occluded by a character, prop, or particle. If a Chugger stands on the same tile, the ring renders on top of them.
Every can is identical. There is no shake level, no charge, no variable fuse and no Pressure meter — the can is already shaken when the Chugger throws it, and it bursts 2.0 seconds later, always. Bubble streaks on the can body are decorative and constant; they never encode a value.
This makes the fuse ring the only timing information in the game. With no meter anywhere on screen, a player reads the board entirely by counting rings. That is why the ring is never occluded, and why it gets smooth interpolation rather than the 12fps step used for characters (§12).
The shake lives in the animation, not the input: the place_can cycle is wind-up, shake, throw, recovery hop — one motion, performed on the player's behalf. See Framework & Flow §8.7 and §8.8.
7. Arena Readability System¶
Three tiers, distinguishable by silhouette and value alone. Every arena reskins these tiers; no arena adds a fourth.
Version 1 ships three: Indestructible, Destructible, Hazard. The Reinforced two-hit tier below is retained as design intent but is not buildable in version 1 — the asset architecture slot manifest has no reinforced slots and the map format has no glyph for it. Anything described as Reinforced in §8.3 and §9.3 either ships as an ordinary Destructible or waits for format 2. See Framework & Flow §16.2.
| Tier | Reads as | Value | Silhouette | Behaviour |
|---|---|---|---|---|
| Indestructible | Dark, cold, heavy | Darkest on screen | Full tile, flat top, hard edges | Never breaks |
| Destructible | Light, warm, cheap | Mid-value | 80% tile, irregular, slightly tilted | One blast |
| Reinforced (v2) | Light but glassy/dense | Mid-value + specular | 80% tile, geometric, highlight | Two blasts |
| Hazard | Moving or glowing | Highest contrast | Breaks the grid rhythm | Kills, unowned |
Floor is always the lowest-contrast surface on screen. Nothing decorative ever stands on a tile. If it isn't one of the tiers above, it is painted flat on the floor or lives outside the play field.
8. Arena — The Diner¶
8.1 Mood¶
3am, empty, neon still humming. Chrome and cracked vinyl. Warm interior light against a cold blue night outside the windows. Nostalgic rather than grimy — this place is loved, just tired.
8.2 Palette Shift¶
Base palette, biased warm. Cola Brown and Frenzy Red dominate. Bottle Teal appears as vinyl booth upholstery and neon. Chrome does heavy lifting on the indestructibles.
8.3 Tier Dressing¶
Indestructible - Chrome-edged booth ends with red vinyl seats - The jukebox — corner anchor, glows, cycles colour on a slow loop - Counter segments with fixed stools - Walk-in fridge doors along the back wall
Destructible - Stacked cardboard 12-packs of Frenzy Cola - Paper cup towers - Napkin dispenser stacks - Pie display cases (glass — shatters with a satisfying sparkle)
Reinforced (two-hit) — deferred to v2 - Ice blocks from the machine — crack, then shatter into a slippery puddle that persists for 4 seconds. Ships in v1 as a single-hit Destructible with no puddle, or not at all
Hazards & Props
Version 1 supports one hazard type — a tile that is periodically lethal on a pack-defined timer (map glyph H). The soda fountain qualifies. Teleporters, ricochet and persistent floor states need map format 2; see Framework & Flow §16.2.
- Bendy straw teleporters (v2) — chrome straw dispensers, paired by colour
- Syrup puddles — dark, glossy, slow the player and cling for one second after they leave
- Bottle caps — spin in place, ricochet a blast 90°
- Soda fountain — erupts vertically on a fixed timer, telegraphed by a gurgle and a 1s floor decal
8.4 Lighting¶
Warm pools under pendant lamps, cold blue spill through the windows. Neon rim-light on the left edge of every solid object. Characters always carry a bounce light from below so they never sink into the floor.
9. Arena — The Beach Kiosk¶
9.1 Mood¶
Blazing midday. Bleached wood, faded parasols, sand everywhere. Everything is slightly sun-damaged. The contrast to the Diner is total: that arena is enclosed and warm-dark, this one is open and bright.
9.2 Palette Shift¶
Base palette biased cool and bright. Bottle Teal and Sunbeam dominate. Frenzy Red survives only on the cans, the kiosk signage, and the parasols — which keeps hazard-red rare and therefore loud.
9.3 Tier Dressing¶
Indestructible - The kiosk itself — shuttered hatch, hand-painted Frenzy Cola sign - Concrete groynes and sea wall segments - Chained-up pedal boats - Full steel coolers
Destructible - Stacked crates of empties - Sandcastles (crumble beautifully) - Rolled parasols - Beach ball nets - Polystyrene cooler boxes
Reinforced (two-hit) — deferred to v2 - Ice buckets — crack, then burst into slush. Ships in v1 as a single-hit Destructible
Hazards & Props - Bendy straw teleporters (v2) — reskinned as buried pipe outflows - Wet sand patches — the syrup equivalent, slows movement - Incoming tide — on a long timer, the outer ring of tiles floods and becomes lethal, shrinking the arena and forcing endgame resolution - Seagulls — steal an uncollected power-up if it sits for 8 seconds
9.4 Lighting¶
Hard overhead sun, short sharp shadows, high ambient bounce off the sand. Heat shimmer at the horizon only — never over the play field, where it would compromise readability.
10. VFX¶
10.1 The Foam Blast¶
The signature effect. Budget accordingly — this is what players see thousands of times.
- Frame 1: white flash, full opacity, one frame only
- Frames 2–4: foam surges outward along four lanes, leading edge cream, trailing rim red
- Frames 5–8: foam collapses, bubbles pop individually, residue fades
- Total duration 0.4s; damage window is the first 0.15s and is telegraphed entirely by the fuse ring, never by the VFX
- Foam never extends visually beyond damaged tiles. Not by a single pixel. Every complaint about unfair deaths in this genre traces back to VFX overhang.
10.2 Rules¶
- Particles are shapes, not smoke — bubbles, droplets, cardboard scraps, sugar sparkles
- Nothing persists longer than 2 seconds except designed floor state (syrup, puddles, wet sand)
- Screen shake: maximum 4px, 0.1s, and it scales down with the number of simultaneous blasts so chain reactions don't make the screen unplayable
- No full-screen flashes, no bloom over the play field, no motion blur
11. UI & HUD¶
Thumb zones are sacred. The bottom band belongs to the controls. No information, no hazard, no critical geometry ever appears there.
The screen is portrait, 11 columns × 13 rows, and the vertical budget is owned by Framework & Flow §5.8.1. In summary, top to bottom:
- HUD band: four player slots (portrait, colour, shape badge, alive/dead) with the round timer and round pips. Four slots fill this band — it is not reserving space for more
- Play field: fitted to screen width, never stretched. Surplus vertical space is arena backdrop
- Pickup strip: Shaker / Extra Can / Ice Cube counts and active timed-effect chips. It sits directly under the field because it is glanced at constantly and the top of the screen is the hardest place to read mid-panic
- Control zone: joystick origin area left, DROP and KICK right. It never overlaps the play field — no transparent joystick floating over live tiles
- Nothing is ever drawn under the notch or on the home indicator
League surfaces are menu-only. The tier chip sits in the Home upper band beside the player badge (Framework & Flow §5.2), and the standing has its own screen. No rank, badge or band ever appears in the match HUD — §3.4.
No pause button in multiplayer. A menu affordance opens a slim overlay while the simulation continues underneath; pause exists in single player only. Framework & Flow §8.4.
Style: chunky rounded rectangles, thick Cola Brown outlines, flat fills, minimal gradient. UI is a layer above the world and never pretends to be part of it.
Diegetic exception: menus are dressed as a diner countertop or a kiosk hatch. This is the one place the world and the interface are allowed to touch.
12. Animation¶
- 12fps stepped for character cycles — reads as deliberate and hand-crafted, and saves budget
- Smooth interpolation for anything gameplay-critical: can fuse, blast, power-up spawn
- Anticipation on every action, overshoot on every stop
- Idle animation is mandatory. A still Chugger looks like a broken game.
- Placing a can: a fast wind-up with the shake in it, a throw, and a recovery hop. The whole cycle must complete well inside the 2.0s fuse and must not obscure the tile the can lands on. It is animation, not input — the player taps once.
13. Audio Direction¶
Included here because it carries the same information as the art and must not contradict it.
- Fuse: a rising hiss, pitch climbing with the fuse ring. Audible above music at all times.
- Burst: a wet thwack plus a fizz tail. Never an explosion. Never a bang.
- Popped Chugger: a raspberry and a deflating balloon.
- Music: surf rock. Works for both arenas — reverb-heavy and diner-jukebox for the first, bright and open for the second.
- Design for silence. Most mobile sessions are muted. Every audio cue must have a visual equivalent that is sufficient on its own.
14. Seasonal & Skin Framework¶
Both arenas were selected because they reskin cheaply, and the framework must be respected to keep that true.
| Season | Diner | Beach Kiosk |
|---|---|---|
| Winter | Christmas lights, cocoa cans | Ice-swimming, snow on sand |
| Spring | Pastel vinyl, blossom outside | Kites, pastel parasols |
| Summer | Fans, condensation | Base state |
| Autumn | Pumpkin pie, orange neon | Storm, grey sea, packed-up stalls |
Non-negotiable across every skin: hazard colour law (§3.3), the three-tier silhouette system (§7), player identity colours and shape badges (§3.2), and the can's three states (§6). A skin changes surfaces. It never changes what something means.
15. Store Assets¶
- Icon: the can alone, three-quarter view, mid-burst, on flat Frenzy Red. Not the logo, not a character, not a scene. It must read at 48px.
- Screenshot 1: four Chuggers, one blast mid-frame, maximum visible chaos
- Screenshot 2: the Diner, wide, showing arena identity
- Screenshot 3: power-ups with names visible — this is where depth gets communicated
- Screenshot 4: the Beach Kiosk with the tide closing in
- Video: first 3 seconds must contain a drop, a fuse ring draining, and a burst. No logo intro. The shake is part of the drop animation, not a separate beat.
16. Legal Guardrails for Artists¶
Non-negotiable, and cheaper to respect now than to fix at submission.
- Frenzy Cola's can art must never use red-and-white Spencerian script, the dynamic ribbon device, or any silver-on-red wave. That is Coca-Cola trade dress.
- No real brand may appear on any prop, poster, wrapper or signage, however small or blurred. Every label in the world is a fictional brand we own.
- The mint-geyser hazard, if implemented, is a generic roll of mints. It is never called Mentos, never styled as Mentos, and is never referenced in marketing.
- No real-world celebrity likeness, no licensed music, no recognisable album art on the jukebox.
- Diner and beach photo reference is for mood only. Nothing is traced. Keep a reference log with sources and licences.
Open Questions¶
- Do Chuggers rescue each other, or is elimination final? A two-stage elimination — trapped in foam, freed by a teammate — would change team modes substantially and is worth prototyping before the character rig is locked.
- Closed: final arena tile dimensions. The grid is 11×13 portrait, world assets are authored against a 192 × 192 px tile reference (asset architecture §4), and the tile resolves to roughly 98 logical px on the reference phone (framework §5.8.1).
Part 4 — Visual Assets & Design Architecture¶
Version 0.17 — Pre-production Companion to the Design Style Guide, the Application Framework & Flow, the Gameplay Rules & Mechanics and the Economy & Progression. Where the style guide says what things look like, this document says how they are cut up, named, bundled and shipped.
Changes in 0.17: §6.7 Economy added — the slot manifest for the currency icon, reward burst, XP bar and level badge required by Economy & Progression §10, with the third-cube legibility table and the Gold-badge Sunbeam collision. New §8 gate (currency vs cubes and Gold). §9 launch scope gains the economy row and the leagues row it was missing since 0.14 — total ~1,011 → ~1,064. Decision 23 logged.
Changes in 0.16: core/economy/ added to the §2.2 Core tree — currency icon, reward burst, XP bar and level badge required by Economy & Progression §10. §6.6.3's deliberate absence of reward art is confirmed by that document: the rollover pays nothing in v1.
Changes in 0.15: §2 reconciled with the locked pack source contract (Pack Studio §8.2), which it had predated and contradicted. The arena subtree is now pack.json + a flat art/ + maps/, not nine category directories. The tree splits in two: pack sources at Content/ outside the engine project, Core at Assets/Game/Art/Core/ inside it. Root renamed from assets/ — it collided case-insensitively with the engine's own Assets/. §4 now says how @1x/@2x/@3x are expressed on disk, which no document previously stated, and exempts Core. Four decisions logged. Closes Pack Studio §11 Q5.
Changes in 0.14: League assets added — §6.6 specifies six tier badges, the Home chip, promotion and demotion band treatments and the movement stingers required by Framework & Flow §17. All Core, non-variant. New core/leagues/ directory. ui/leagues added to the §6.4 HUD & UI table. Closes the open art gap logged in Framework & Flow §16.2 item 4.
Changes in 0.13: All six open items in §11 closed. Drop weights move into the map header as a required key — no pack default. Map delivery locked to bundled baseline plus server-added maps, additive only. No dedicated 2-player maps in v1, which makes players a spawn capacity rather than an exact match size. First-cube rigging locked.
Changes in 0.12: Procedural fill removed. ? and ~ glyphs, fill_density and fill_weights are cut — every tile is authored explicitly. Grid fixed at 11×13 and validated. New corridor-exit rule (A.7.16). Randomised worked example replaced with a duel map. Load sequence simplified.
Changes in 0.11: Appendix A demoted from a level-1 heading to level 2 so that H1 is reserved for document titles — required for clean compilation into the consolidated specification. No content change.
Changes in 0.10: Shaker cap lowered to 6. pickup_destroyed confirmed and added — blasts destroy uncollected pickups. Seagull cut. drop_weights added to the map header as a per-map override of the pack table. Open questions 3 and 6 closed.
Changes in 0.9: Pickup behaviour (effects, caps, stacking, drop table, timed-effect rules) moves to the new Gameplay Rules & Mechanics document; this document retains silhouette, animation and legibility. hud/lives flagged as vestigial — there is no lives system.
Changes in 0.8: Pre-placement mechanic cut — the Pressure meter is withdrawn, along with its naming constraint on the Shaker icon. Fuse is a fixed 2.0s on every can. Decision 5 superseded.
Changes in 0.7: Player count confirmed at 4 for version 1 — players: 2–4 stands and is not being widened. Grid confirmed at 11×13 portrait. Format 2 reserve extended to E/F for a future 6-player spawn assignment. Reinforced tier confirmed absent from the slot manifest. Open questions 3 and 6 updated.
Changes in 0.6: Map glyphs reworked — dense symbols for solid (#%@&), digits for destructible, . for empty. Void cut from format 1. Comment marker moved to ;.
Changes in 0.5: Arena packs now carry multiple grid maps. Full ASCII map file specification added as Appendix A, with validation rules and a deterministic load sequence.
Changes in 0.4: Single drop table — pickups and cubes both spawn at destroyed blocks. Spawn sequencing and debris rules added. How to Play icon requirements added (§6.5).
Changes in 0.3: Timed effects moved into Surprise Cubes — debuffs no longer appear on tiles. Glue reduced to 3s. Pickup shape law rewritten around three tile states. Cube legibility gates added.
Changes in 0.2: Shaker locked as the range power-up. Ice Cube capped at 3 stacks. Can flavour promoted to a reserved fifth variant axis. Blast skins confirmed cosmetic-only.
1. The Spine: Five Variant Axes¶
Everything in the game belongs to exactly one of five axes. An asset's axis determines where it lives, who can change it, and what it is allowed to affect.
| Axis | Varies by | Ships as | May change | May never change |
|---|---|---|---|---|
| Core | Nothing | Base app | — | — |
| Arena | Arena pack | Downloadable pack | Surfaces, props, backdrop, lighting | Grid dimensions (11×13 portrait), tier silhouettes, hazard colour law |
| Character | Chugger skin | Per-skin bundle | Colours, textures, accessories inside the hat box | Silhouette class, proportions, tile footprint, animation set |
| Blast | Blast skin | Per-skin bundle | Core fill, particles, residue decal | Blast mask, timing, warm rim, arm length |
| Can flavour | Flavour skin | Per-flavour bundle | Label art, body tint | Silhouette, fuse pulse hue, timing, the three can states |
Can flavour is reserved, not built. Only classic fills the slot at launch. The manifest, the label layer separation and the naming convention exist from day one so the axis can be switched on later without touching a core asset.
The rule that makes this work: a variant never introduces a new asset. It fills a fixed slot manifest. If an arena pack ships without solid_block_c, the pack fails validation and does not build. Variants are interchangeable because they are structurally identical.
Power-ups are Core. This is a deliberate decision and it earns three things: a player learns the three pickups once and knows them in every arena forever; the store can never sell an advantage or a confusion; and the pickups can be authored once at high polish instead of once per pack. The cost is that every arena pack must prove its floor is readable behind them (§8).
2. Directory Structure¶
Two trees, split by one rule: anything belonging to a variant axis is engine-agnostic source and lives outside the engine project; Core ships in the binary and lives inside it.
2.1 Pack sources — Content/¶
The authoritative source form for the four variant axes. Never imported by the engine. The source contract is Pack Studio §8.2 and it is locked: loose PNGs, one file per slot, import settings as manifest data, no engine sidecar files, atlasing at build.
Content/
├── arenas/
│ ├── _manifest.schema.json # required slot list — validated at build
│ ├── diner/
│ │ ├── pack.json # slots, import settings, defaults
│ │ ├── art/
│ │ │ ├── @1x/ # loose PNGs, one per slot — see §4
│ │ │ ├── @2x/
│ │ │ └── @3x/
│ │ └── maps/ # .map text files, see Appendix A
│ └── beach_kiosk/ # identical subtree
│
├── characters/
│ ├── _manifest.schema.json
│ ├── _rig/ # shared skeleton + attach points
│ ├── rax/
│ │ ├── base/ # the free launch skin
│ │ │ ├── pack.json
│ │ │ └── art/{@1x,@2x,@3x}/
│ │ └── skins/
│ │ └── soda_jerk/ # same shape as base/
│ ├── puff/
│ ├── nim/
│ └── bolt/
│
├── blast/
│ ├── _manifest.schema.json
│ ├── classic/ # default, free, shipped in base app
│ └── skins/
│ ├── blue_raspberry/
│ ├── lime/
│ └── grape/
│
├── flavours/ # reserved axis — only classic at launch
│ ├── _manifest.schema.json
│ ├── classic/ # Frenzy Cola, shipped in base app
│ └── skins/ # empty at launch
│
└── _source/ # working files, never shipped
├── vector/
├── rigs/
└── palettes/
There are no category subdirectories inside art/. The §5.1 slot names already carry the category — backdrop_far, floor_tile, solid_block, grade_profile, meta_card — so a floor/ directory would encode the same fact twice and give a filename two places to disagree with its slot. One flat directory per resolution, filename derived deterministically from the slot name.
2.2 Core — inside the engine project¶
Assets/Game/Art/Core/
├── can/ # can geometry, states, fuse pulse — label layer excluded
├── powerups/ # Shaker, Ice Cube, Extra Can
├── status/ # player state overlays (frost, kick-ready, speed)
├── blast_mask/ # blast geometry + timing, skin-independent
├── fx_generic/ # dust, sparkle, pickup pop, spawn burst
├── hud/ # in-match interface
├── leagues/ # tier badges, bands, movement stingers — §6.6
├── economy/ # currency, XP, level — *Economy & Progression* §10
├── ui/ # menus, store, lobby, results
├── type/ # fonts, logo, wordmark
└── store_meta/ # app icon, screenshots, feature graphics
Why Core is not in Content/. Core ships in the binary, is not pack-swappable, fills no slot manifest and is never read by Pack Studio. None of the §8.2 source constraints apply to it, so it lives where the engine imports it normally and carries its own import settings.
Why the root is Content/ and not assets/. The engine project's own directory is Assets/, and Windows and macOS filesystems are case-insensitive by default. assets/ and Assets/ cannot coexist in one repository.
Maps live inside the pack directory. The map CI gate and the pack build gate therefore touch the same tree, which is intended rather than incidental — a map is meaningless without the pack whose slots its glyphs name, and separating them would let a map reference a slot its pack no longer has. Closes Pack Studio §11 Q5.
Bundling: Core is in the binary. Each arena, character skin and blast skin is an independently downloadable bundle with its own version stamp, produced from its source directory by the build step (Pack Studio §8.2). A match downloads only the arena in play plus the skins the four participants actually own.
3. Naming Convention¶
{axis}_{set}_{object}_{variant}_{state}_{direction}_{frame}
Lowercase, underscores only, no spaces, no capitals, no hyphens. Frame numbers are always two digits.
| Example | Reads as |
|---|---|
arena_diner_solid_counter_a |
Diner pack, solid block, counter variant A |
arena_beach_dest_cooler_break_03 |
Beach pack, destructible cooler, break frame 3 |
char_rax_base_run_e_04 |
Rax, base skin, run cycle, facing east, frame 4 |
char_puff_sodajerk_kick_s_02 |
Puff, Soda Jerk skin, kick, facing south, frame 2 |
blast_lime_arm_h_mid_05 |
Lime blast skin, horizontal arm, mid segment, frame 5 |
core_pu_shaker_idle_03 |
Power-up Shaker, idle loop, frame 3 |
core_can_armed_pulse_06 |
Can in armed state, pulse loop, frame 6 |
flav_classic_label_armed_06 |
Classic flavour label, armed state, frame 6 |
Direction codes: n s e w. West is mirrored from east at runtime and is never authored.
4. Canvas & Export Standards¶
- Grid tile reference: 192 × 192 px. All world assets are authored against this.
- Author in vector. Export raster at @1x / @2x / @3x; the tile is 64 / 128 / 192 device px.
- On disk the three exports are sibling directories —
art/@1x/,art/@2x/,art/@3x/— carrying the same filename in each. The scale is never a filename suffix. This keeps Pack Studio §8.2's one-file-per-slot rule true at all three resolutions and keeps the §3 naming convention free of a field it does not define. - Core is exempt and authors one set at @3x. Core lives inside the engine project (§2.2), where per-platform texture import overrides derive the lower tiers from a single source. Three hand-maintained copies of the same asset would be a maintenance cost with no benefit. Packs cannot use that mechanism, because their source form has to stay engine-agnostic.
- Atlas size 2048², one atlas per bundle, power-of-two, trimmed, 4px padding. Core gets two:
core_worldandcore_ui. - Pivots: world objects pivot at bottom-centre of their tile footprint. Characters pivot at foot centre. Blast segments pivot at tile centre.
- Character sprites occupy 85% of tile width, per §5.2 of the style guide. The remaining margin is gameplay-critical and no skin may spend it.
- Everything carries a 6px Cola Brown outline at reference scale, baked in, not shader-applied.
- Overhang budget: a prop may exceed its tile by up to 40% vertically upward only. Never sideways, never downward.
- PNG-32 for source, platform compression at build. No JPEG anywhere in the world layer.
5. Slot Manifests¶
5.1 Arena Pack — required slots¶
Every arena pack ships exactly this. Missing slot = build failure.
| Slot | Count | Notes |
|---|---|---|
backdrop_far |
1 | Static, no parallax |
backdrop_near |
1 | Parallax layer, max 8px travel |
floor_tile |
3 | Randomised across the grid to break repetition |
floor_edge |
1 | 9-slice, frames the play field |
floor_decal |
4 | Painted-on dressing, never occludes |
solid_block |
3 | Indestructible tier — dark, cold, heavy |
solid_cap |
4 | Edge and corner caps for block runs |
destructible |
4 families | Light, warm, cardboard-y tier |
destructible_cracked |
4 | One damaged state per family |
destructible_break |
4 × 6 frames | Destruction animation per family |
debris_sheet |
4 | Particle sheet per family — cardboard, glass, ice, foam |
ambient_static |
6 | Backdrop or floor only. Never on a playable tile. |
ambient_animated |
2 | Jukebox glow, gull, ceiling fan. Loops under 3s. |
grade_profile |
1 | Lighting/LUT. Constrained by §8 contrast gate. |
maps |
3 min bundled | .map text files, Appendix A. This is the bundled baseline; the server adds more, never replaces (§11.1). Three is a floor, not a target — with procedural fill removed, every round of every match on a given map is the identical authored board, so map count is replayability. Five or six per pack is a better aim |
meta_card |
1 | Store thumbnail, 512² |
meta_loading |
1 | Full-bleed loading art |
| Total per pack | ~65 assets + 3 maps | Diner and Beach Kiosk at launch |
Maps are data, not art, and they are the cheapest content in the entire project. A new map costs one person an hour in a text editor and reuses every asset in the pack. If maps are delivered server-side rather than baked into the bundle, the rotation can change weekly without a store submission — which is the single highest-leverage decision in this document for retention per unit of production cost.
The three-tier read — solid / destructible / hazard — is the pack's contract with the player. A pack may dress those tiers however it likes and may not blur the line between them.
There is deliberately no Reinforced slot. The style guide §7 describes a two-hit tier; it is not built in version 1 and has no manifest slot, no glyph and no damage-state animation. Anything a pack wants to dress as reinforced ships as an ordinary destructible. See Framework & Flow §16.2.
5.2 Character Skin — required slots¶
Per skin, including each character's base skin.
| Slot | Frames | Directions | Sprites |
|---|---|---|---|
idle |
8 | 3 | 24 |
run |
8 | 3 | 24 |
place_can |
6 | 3 | 18 |
kick |
4 | 3 | 12 |
hit_pop |
8 | 1 | 8 |
victory |
12 | 1 | 12 |
defeat |
8 | 1 | 8 |
portrait |
1 | — | 1 |
emote_face |
4 | — | 4 |
store_card |
1 | — | 1 |
| Total per skin | ~112 sprites |
Two contracts skins must honour:
- The hat box. A skin may add geometry only inside a defined region above the head, up to 30% of body height. Everything else is recolour and retexture. This is what keeps a purchased skin from becoming a harder target to read.
- The kick animation is mandatory. Kick is unlocked by the Ice Cube, so every skin ever sold must ship it — including skins authored before anyone remembers why it's there. Manifest validation enforces this; do not rely on memory.
5.3 Blast Skin — required slots¶
| Slot | Frames | Notes |
|---|---|---|
burst_core |
8 | Origin tile |
arm_h_mid |
8 | Horizontal mid-segment, tiles |
arm_v_mid |
8 | Vertical mid-segment, tiles |
arm_cap |
8 × 4 | Tip, one per direction |
residue_decal |
4 | Fading puddle left behind |
droplet_sheet |
1 | Particle atlas |
store_card |
1 | 512² |
| Total per skin | ~54 assets |
Blast skins are the one place the architecture pushes back on the design. Style guide §3.3 says the blast is Fizz Cream core with a Frenzy Red rim, and that a skin never changes what something means. Selling recoloured blasts collides with that, and worse, collides with player identity colours — a purchased blue blast on a grid containing the blue player is a readability failure the buyer paid for.
The resolution, and it should be treated as a hard constraint on the store:
- The mask is core.
core/blast_mask/owns the shape, extent and timing. Skins supply fill only. The lethal area is byte-identical across every skin. - The rim stays warm. Hue locked to the 10°–45° band. This is the pixel that says "this will kill you," and it is not for sale.
- The core fill is free. Interior colour, particles, residue decal, droplet shape — all sellable, all expressive, none of it load-bearing.
- Collision fallback. If two players in a match own blast skins within 20° hue of each other, the lower-seed player renders
classic. Silent, server-resolved, no message.
That gives the store a real product and keeps the colour law intact.
Locked: blast skins are cosmetic only. No blast skin changes range, timing, damage or travel. Store copy must not imply otherwise — no "explosive," no "supercharged," no comparative adjectives on a blast SKU. Flavour and mood words only. This is a store-copy review rule, not just an art rule, and it should live in the localisation brief so it survives translation.
5.4 Can Flavour — required slots¶
Reserved for post-launch. Only classic is authored at launch, but the slots exist now so the axis costs nothing to switch on later.
| Slot | Frames | Notes |
|---|---|---|
label_idle |
4 | Label decal only, transparent outside the label area |
label_armed |
12 | Registers to the core pulse frames — does not replace them |
label_slide |
6 | |
body_tint |
1 | Single-channel mask, tints the can body beneath the label |
store_card |
1 | 512² |
| Total per flavour | ~24 assets |
Three constraints on the axis:
- The label is a decal, not a repaint. Core owns the can silhouette, the fuse pulse and all timing. A flavour supplies artwork that rides on top, registered to the core frames.
- The fuse pulse hue is not for sale. Red → Sunbeam is the single most important tell in the game. A flavour may tint the body; it may never touch the pulse.
- Merchandise flavours with blast skins, but keep them technically independent. A grape can bursting into lime foam is a bad look, so sell them as a matched SKU. Do not couple them in code — a player who owns both should still be able to mix them if they want, and coupling the systems means every future flavour drags a blast skin's production cost with it.
6. Core Assets — Non-Variant¶
6.1 The Can¶
| Asset | Frames | Notes |
|---|---|---|
can_idle |
4 | Unplaced, held state reference |
can_armed_pulse |
12 | Red → Sunbeam, accelerating with the fuse. Fixed 2.0s on every can — the pulse curve is authored once and never parameterised |
can_slide |
6 | Kicked, travelling |
can_bounce |
4 | Impact against a block |
can_settle |
4 | Slide resolving to rest |
Flavour axis reserved — see §5.4. Every can state above is authored with the label on its own layer, and flavours/classic/ fills the slot at launch. The five states, the silhouette and the pulse timing belong to core permanently; a flavour only ever supplies decal artwork registered to these frames.
6.2 Pickups¶
Six effects, delivered two ways. All non-variant, all core, authored once, identical in every arena and every match.
Behaviour lives in Gameplay Rules & Mechanics §8. Effects, caps, stacking rules, drop weights and timed-effect interactions are specified there and that document governs them. What follows here is silhouette, motion, frame count and legibility — the production side. Effect columns are retained for context only; where the two disagree, Gameplay Rules wins.
The delivery split is the important part. Buffs sit on the field as themselves and are identifiable before you commit. Debuffs only ever arrive inside a Surprise Cube — an opaque ? container whose contents are unknown until collected. So the player never fails to see a debuff coming; they see a gamble coming, and choose whether to take it.
That is a different fairness contract from the previous draft, and a sound one. A visible debuff is fair because you can path around the tile. A Surprise Cube is fair because the risk is disclosed even though the outcome isn't — the same contract as a Mario Kart item box. What it must never become is a debuff that arrives unannounced from a container that looked safe. The ? is doing that work, and it is the single most important glyph in the pickup set.
Shared spec:
- 70% of tile width — deliberately smaller than a can, so a pickup never reads as a threat
- 4px Fizz Cream outline so they survive any arena floor
- Slow bob, 1.2s loop, plus a per-item signature motion
- Shared spawn_burst (6 frames) from fx_generic
Class A — Permanent upgrades¶
Persist until death. Stack to a hard cap. Displayed as counters in the HUD. Drop visibly from destructibles.
| Power-up | Effect | Cap | Silhouette | Signature motion | Frames |
|---|---|---|---|---|---|
| Shaker | +1 soda blast range | 6 | Wide hexagonal canister, domed cap, handle ridge | Rattles side to side, 3-frame jitter every 2s | 12 |
| Ice Cube | Kick at stack 1; stacks 2–3 increase can slide speed | 3 | Rounded cube, 3/4 view, single white highlight wedge | Slow rotate + occasional sparkle | 16 |
| Extra Can | +1 simultaneous can | 5 | Two-can sleeve — a mini six-pack, not a single can | Gentle tilt, cans clink on the beat | 12 |
Class B — Timed effects¶
Do not stack. Displayed as a depleting band, not a counter. Only ever obtained from a Surprise Cube — these three never appear on a tile as themselves.
| Effect | Result | Duration | Type | Reveal icon | Frames |
|---|---|---|---|---|---|
| Coffee Bean | Player move speed up | 5s | Buff | Small, dark, hard-edged bean with a centre crease | 12 |
| Honeycomb Wax | Player move speed down | 5s | Debuff | Pale amber hex-cell slab, soft edges, sagging | 14 |
| Glue | Cannot place cans | 3s | Debuff | Spilled white blob with a tipped-over bottle half-sunk in it | 14 |
Because these never sit on a tile, they need no bob loop and no field silhouette — only a reveal icon, a status overlay and a HUD band icon. That saves roughly 20 assets against the previous draft and removes the Coffee Bean / Honeycomb Wax tile collision entirely, since the two are never on the grid at the same scale.
The Surprise Cube¶
| Asset | Frames | Notes |
|---|---|---|
pu_cube_idle |
16 | Slow rotate, ? glyph faces the camera on every frame |
pu_cube_open |
8 | Shell splits, contents not yet legible |
pu_cube_reveal_good |
10 | Sunbeam burst, rising, sharp |
pu_cube_reveal_bad |
10 | Drab burst, sagging, viscous |
reveal_icon |
6 | One per effect, shown at the burst centre and above the player |
The rules the cube has to follow:
- The cube is one silhouette, always. It never telegraphs its contents by shape, colour, size or motion. Any variation is a tell, and a tell that only regular players notice is worse than no tell at all.
- The
?must survive 32px and greyscale. It is the entire disclosure. Heavy weight, Cola Brown, high-contrast plate behind it, never a thin outline. - The cube is visually distinct from the Ice Cube. Both are cubes. The Ice Cube is translucent, cool, single-highlight, smooth. The Surprise Cube is opaque, mid-tone, panel-lined, with the
?plate on every visible face. Test these two together at 32px before either is final — this is now the collision that matters most, and it replaces the Bean/Wax problem from the previous draft. - The reveal reads before the effect lands. Open, burst, and icon all play in under 250ms, and the good/bad burst variants differ in the same three channels the field pickups use: rising vs sagging, saturated vs drab, sharp vs viscous. A player must know what happened without reading the icon.
- The reveal is public. It plays at world scale above the collecting player, not just in their own HUD. Everyone in the match sees who just got glued.
Contents pool. All six effects at equal weight, per the flat-probability decision — so a cube is 1/3 debuff, 2/3 buff, and worth opening. Including the Class A permanents in the pool is what makes that true; a cube containing only the three timed effects would be 2/3 debuff and no rational player would ever touch one.
Spawning¶
One source, one position. Every pickup and every cube spawns from a destroyed block, at the tile the block occupied. Nothing seeds at round start, nothing spawns on an empty tile, and the two entry types share a single drop table.
| Drop table entry | Notes |
|---|---|
| Nothing | The majority case — tune to taste |
| Shaker | Visible, identifiable before you commit |
| Ice Cube | Visible |
| Extra Can | Visible |
| Surprise Cube | Opaque, six possible effects at flat weight |
This makes the arena the pacing mechanism. Early rounds are dense with blocks and poor in pickups; the mid-game opens up and floods the board; the endgame has nothing left to break. That's a good curve and it comes free — but it means the drop rate has to be tuned against arena block count, not set globally. A dense Diner and an open Beach Kiosk with the same drop percentage will play at very different power levels.
Three spawn rules the shared position forces:
- Debris first, pickup second.
destructible_breakruns its 6 frames, thenspawn_burstfires. A pickup that materialises inside its own debris cloud is invisible for exactly the moment the player is looking at that tile. Sequence them; do not overlap. - Debris must not occlude a settled pickup. Draw order puts debris particles at layer 10, above pickups at layer 4 — correct while the block is breaking, wrong a half-second later. Debris lifetime is capped at the length of
destructible_breakplus 200ms, and no debris particle may come to rest on a tile. - Chain reactions spawn in a live blast. A four-block chain can drop four pickups inside foam that's still on screen, at the exact moment the player is reading the lethal layer. The
spawn_bursttherefore waits for the blast on that tile to clear before it plays. A pickup appearing under active foam is a pickup nobody sees.
Locked — blasts destroy uncollected pickups (Gameplay Rules §6.5). This adds one required asset:
| Asset | Frames | Notes |
|---|---|---|
fx_generic/pickup_destroyed |
8 | Foam swallowing the item — sagging, drab, downward. Must not resemble pickup_pop, which is rising, saturated and Sunbeam-cored. A player who sees the collect animation and gets no stat change reports it as a bug |
The two animations are the clearest possible test of the rising-versus-sagging channel already established for buffs and debuffs: destruction is the same motion vocabulary as a debuff reveal.
The pickup shape law¶
Three states, distinguishable in greyscale at 32px, no exceptions:
- Ringed and rising = take it. Clean circular Fizz Cream halo, Sunbeam core, bobs upward. The three Class A pickups.
- Panelled with a
?= gamble. Opaque cube, mid-tone, no halo, steady rotation. One asset, one meaning. - Nothing else on a tile is collectable. Live cans are always single, always vertical, never ringed and never panelled.
Two silhouette warnings, both worth solving in art rather than in a tutorial:
- Extra Can must not be a can. The most dangerous object in the game is a can on a tile. A pickup shaped like one is the wrong shape at the worst moment. The two-can sleeve solves it.
- Shaker must not be a can either. A canister and a can are the same family. Force them apart on the width axis — the can is tall and narrow, the Shaker is squat and wide. At 32px that survives; a texture difference does not.
Shaker is locked, and with the pre-placement mechanic cut there is no longer a meter for it to collide with. The naming problem that produced the Pressure rename has been removed rather than solved: there is no charge, no shake input and no meter anywhere in the HUD. Shaker is now unambiguous — it is a pickup, and nothing else in the vocabulary competes with it.
What this removes from the asset list: a HUD meter and its fill states, an abstract-versus-object shape constraint on the Shaker icon, and a tutorial page. What it does not remove: the can_armed_pulse frames, which now run on a fixed clock rather than a variable one.
Cap behaviour. At cap, a Class A pickup is still collectable — an uncollectable pickup squatting on a tile is worse than a wasted one. It plays fx_generic/pickup_pop_capped (8 frames, cream-white instead of Sunbeam, no stat readout) so the player learns the ceiling without a message. A cube that rolls a capped Class A effect plays the same pop after the reveal, so a wasted roll still reads as a roll rather than a bug.
Timed-effect rules.
- Refresh, never extend. A second Coffee Bean resets the clock to 5s; it never accumulates to 10. This is what stops a debuff chain from locking a player out of the match.
- One movement modifier at a time, last one wins. Coffee Bean clears Honeycomb Wax and vice versa. That gives the bean a rescue role and makes cubes worth opening even while slowed.
- Glue is orthogonal and coexists with either movement state. Glued-and-fast is a real and useful combination; glued-and-slow is the worst place in the game to be.
- Glue is 3s, not 5. Losing placement for five seconds while boxed in is close to unavoidable death, and now that it arrives from a blind roll the player can't have positioned around it. Three seconds still denies an escape and still swings a duel.
6.3 Status Overlays¶
These render above the character skin and below the blast layer, and are non-variant precisely because a purchased skin must never be able to hide a gameplay state.
| Overlay | Trigger | Asset |
|---|---|---|
status_kick_ready |
Ice Cube stack 1 | Frost glint at feet, 8 frames |
status_frost |
Ice Cube stack 2 and 3 | Two intensity steps — visual states map 1:1 to the cap, no invisible stacks |
status_speedlines |
Can sliding at stack 2+ | Trailing streak on the can, 6 frames, 2 intensities |
status_armed |
Holding a can | Held-can attachment, from core/can/ |
status_speed |
Coffee Bean active | Warm streaks off the player, jittery bounce, 8 frames |
status_slow |
Honeycomb Wax active | Amber drips off the body, sagging posture, 8 frames |
status_glued |
Glue active, 3s | White strings from the hands plus a crossed-out can symbol above the head, 8 frames |
status_reveal |
Any cube collected | Effect icon rises above the head, holds 1s, fades. Public — all players see it. 10 frames |
Two overlay rules the timed effects force:
- A negative state needs a positive symbol. "You cannot place cans" is an absence, and absences are invisible. The crossed-out can above the head is what makes Glue legible — the string VFX alone reads as decoration. Same principle applies to any future debuff.
- Two speeds, two visual languages. Ice Cube speeds up the can; Coffee Bean speeds up the player.
status_speedlinesattaches to the can and stays cool;status_speedattaches to the character and stays warm. They must never share a motif, or the tutorial has to explain a difference the art should be making obvious. status_revealis where the game is taught. With debuffs hidden in cubes, the reveal above the head is the only place a player ever sees what Honeycomb Wax or Glue actually is. It carries the whole onboarding load for three of the six effects, so it gets the highest polish budget incore/status/and it plays at full world scale, never HUD scale.
6.4 HUD & UI¶
| Group | Assets | Notes |
|---|---|---|
hud/avatar_frame |
4 | One per player slot |
hud/shape_badge |
4 | Circle, square, triangle, star — colourblind identity |
hud/lives |
3 states | |
hud/timer |
1 + tabular numerals | |
hud/powerup_pip |
3 | Class A counters, HUD-scale redraws not downscales. Each shows current and cap — 6/8, 2/3, 4/5 |
hud/timed_band |
1 + fill | Depleting band, 5s or 3s. Separate HUD region from the Class A counters — permanent and temporary must never share a slot |
hud/timed_icon |
3 | Coffee Bean, Honeycomb Wax, Glue at band scale |
hud/opponent_state |
3 | Debuff indicators on the three opponent avatars — a glued opponent is a pressing opportunity and should be visible |
hud/pause |
1 | |
hud/tile_telegraph |
4 frames | Pre-blast tile warning |
hud/countdown |
3 + "GO" | |
hud/banner |
2 | Victory, defeat |
ui/menu_chrome |
~20 | 9-slice panels, buttons, toggles |
ui/store |
~25 | Cards, currency, rarity frames, purchase flow |
ui/lobby |
~15 | Slots, ready states, character select |
ui/results |
~12 | Scoreboard, rewards, progression |
ui/leagues |
~14 | Leagues screen chrome: standing row, sticky self row, band dividers, countdown plate, empty and stale states. Badges themselves live in core/leagues/ — §6.6 |
ui/how_to_play |
~18 | Panel chrome, page dots, 7 effect icons at UI scale (6 effects + cube), 3 can-state icons, movement/placement diagrams |
type/ |
3 faces + logo lockups | Nunito ExtraBold, Baloo 2, custom display |
fx_generic/ |
~11 | Dust, sparkle, spawn burst, pickup pop, pickup destroyed |
6.5 How to Play — Icon Requirements¶
The How to Play screen documents all six effects and the cube. Three constraints, because a screen that contradicts the game is worse than no screen:
- Icons are redraws, not downscales. The field asset is authored at 192px with a 6px outline and read at 32px in motion. A UI icon sits still at ~64px on a plain panel. Downscaling produces mush; redraw at UI scale from the same source vector.
- The screen must match the field silhouette exactly. A player learns the shape here and looks for it on the grid. If the How to Play Shaker is a friendlier three-quarter view than the in-game one, the screen has taught the wrong thing.
- Debuff icons are the only place Honeycomb Wax and Glue appear at rest. Everywhere else they're a 250ms reveal burst. These two carry more explanatory weight than any other asset in
ui/, and they have to communicate slow and can't place through shape alone — the screen gets localised, and labels truncate.
Worth being honest about the limits of the screen. Most players skip How to Play, and the ones who read it do so once, before they have context for what "cannot place cans for 3 seconds" means in practice. It's necessary — it satisfies the store, it serves the minority who look, and it's the one place you're allowed to use words. But it does not replace status_reveal (§6.3) as the primary teaching surface. Budget accordingly: the screen gets clarity, the reveal gets polish.
6.6 Leagues¶
Required by Framework & Flow §17. All Core, non-variant — a tier badge is identical in every arena, under every skin, for every flavour. Nothing on this list is sellable, seasonal or pack-supplied, and no variant axis may override any of it.
The reason is the same reason power-ups are Core. A badge that meant Gold in the Diner and something else at the Beach Kiosk would make the one piece of status the player carries between matches unreadable. Rank is a property of the account, not of the arena it happens to be standing in.
6.6.1 Tier badges — required slots¶
| Slot | Count | Canvas | Notes |
|---|---|---|---|
league_badge |
6 | 256² | Tin, Bronze, Silver, Gold, Diamond, Elite. Full-detail, used on the Leagues header and Profile |
league_badge_chip |
6 | 64² | Redraws, not downscales, per the §6.5 rule. Home badge chip and standing rows |
league_badge_locked |
1 | 256² | Greyed treatment for the Profile history strip. One asset, tinted per tier — not six |
league_frame |
1 | 9-slice | Plate the badge sits in. Shared across all six |
| Subtotal | 14 assets |
The six must be distinguishable by silhouette alone. Six metals with the same shape and different fills is the obvious cheap approach and it fails the greyscale gate in §8 immediately — Silver and Chrome are the same value, and Tin and Silver are the same idea. Each tier gets a distinct outline: point count, edge treatment, or a rising element that grows with rank. Test all six side by side, desaturated, at 64px, before any is finalised. This is a release blocker, not a polish note.
Elite is allowed one animated frame set. league_badge_elite_shimmer, 12 frames, is the single exception on this list and exists because the top of a ladder should look like the top of a ladder. It never appears at chip scale, where the motion would be noise.
6.6.2 Standing bands and movement¶
| Slot | Count | Notes |
|---|---|---|
league_band_promote |
1 | 9-slice fill behind the promotion rows. Cool, rising treatment |
league_band_demote |
1 | 9-slice fill behind the demotion rows. Drab, sagging — never the hazard red |
league_band_divider |
2 | The cut lines themselves — the most important two pixels on the screen. One promote, one demote |
league_arrow |
3 | Up, down, hold. Shown against a player's row and in the Match Results beat |
league_stinger_promote |
10 frames | Rollover result animation |
league_stinger_demote |
8 frames | Deliberately shorter and quieter than promotion |
league_stinger_hold |
6 frames | |
| Subtotal | ~10 slots, 26 assets |
Colour law is Style Guide §3.4 and it is not negotiable from this document. The one constraint worth restating in production terms: the demotion band may not use any hue in the 10°–45° warm hazard band. Everywhere else in the game, warm means this will kill you. A leaderboard is read at rest, with nothing exploding, and teaching the player that warm sometimes just means disappointing is a cost paid back on the grid at the worst possible moment.
Movement direction reads without colour. The three league_arrow assets carry direction in shape — up, down, flat — because the bands they sit against are the only other cue and roughly one player in twelve will not separate them by hue. Same rule as the shape badges in §6.4, same reason.
6.6.3 What is deliberately absent¶
- No reward art. No chests, no claim buttons, no currency bursts. The rollover pays nothing in version 1 — decided in Economy & Progression §3.2 — and the Leagues screen is laid out to take a reward strip later; this manifest does not pre-author one. Currency and level art exists, but it is menu chrome, not league reward art: see
core/economy/(§2.2, Economy & Progression §10) - No per-tier frames, backgrounds or nameplates. One shared
league_frame, one shared locked treatment. Six of everything is how a 14-asset list becomes a 60-asset list for no legibility gain - No club identity art. Clubs are numbered shards with a one-week lifetime, not guilds. They get no crest, no name and no colour
Total for the axis: ~40 assets, all Core, all authored once. That is roughly two-thirds of a single arena pack for a system that appears on three screens and drives the retention loop.
6.7 Economy¶
Required by Economy & Progression §10. All Core, non-variant — the same rule as power-ups and league badges, for the same reason: currency, XP and level are account state, not arena dressing. Nothing on this list is sellable, seasonal or pack-supplied, and no variant axis may override any of it.
And nothing on this list ever renders during a match. Currency and level are menu concepts; the field is for danger (Style Guide §3.4, §11). These assets live on Home, Store, Match Results and Profile — the same screens the league chrome lives on, and no others.
6.7.1 Required slots¶
| Slot | Count | Canvas | Notes |
|---|---|---|---|
sugar_cube |
1 | 256² | The currency icon, hero scale — Store shelf headers, IAP bundle cards |
sugar_cube_chip |
1 | 64² | Redraw, not a downscale, per the §6.5 rule. Home counter, price tags, Match Results reward line |
cube_burst |
8 frames | 256² | Reward celebration on Match Results and level-up. World-scale feel at UI scale |
xp_bar |
2 | 9-slice + fill | Match Results and Profile |
level_badge |
1 | 128² | Frame the level numeral sits in, Home chip and Profile. The numerals are the type/ system, not new assets — tabular figures per Style Guide §4 |
| Total | ~13 assets |
6.7.2 The third cube problem¶
The Sugar Cube is the game's third cube, and the only one that never shares a screen with the other two — so this is a store-UI legibility problem, not a field readability emergency. It is still a real one: a player who cannot tell the currency icon from the Surprise Cube icon in the Store has been taught that cubes are interchangeable.
| Cube | Reads as | Treatment |
|---|---|---|
| Ice Cube | Translucent, cool, smooth, single white highlight | Field pickup — §6.2 |
| Surprise Cube | Opaque, mid-tone, panel-lined, ? plate on every face |
Field pickup — §6.2 |
| Sugar Cube | Translucent-white crystal cluster, Sunbeam core | Menu currency — here |
Translucent-white crystal, Sunbeam core, no ? plate, no frost highlight. Verify all three side by side at 32px in greyscale before locking — the §8 gate carries this check.
Sunbeam is by law, not by taste. The palette assigns Sunbeam to power-ups, rewards, currency (Style Guide §3.1), so the currency icon is Sunbeam-cored permanently. The collision that law creates is with the Gold league badge, which is deliberately darker and less saturated than Sunbeam (Style Guide §3.4, rule 1) precisely because the two sit a thumb's width apart on Home. The badge moves; the icon does not. Check them adjacent, at 32px, in colour, before either is final.
6.7.3 The burst vocabulary¶
cube_burst is the economy's only celebration, and it speaks the same motion language as pickup_pop and pu_cube_reveal_good: rising, saturated, sharp. It must never borrow the sagging, drab, viscous vocabulary of pickup_destroyed and the debuff reveals — a reward that reads as a penalty is worse than no reward. Endpoints are authored; the 8 frames derive.
6.7.4 What is deliberately absent¶
- No league reward art. Chests, claim buttons, payout cards — the rollover pays nothing in v1 (Economy & Progression §3.2), and §6.6.3 stands exactly as written
- No per-bundle pile art. The five IAP bundles are priced cards in
ui/storechrome carrying the onesugar_cubeicon. Five denominations of near-identical cube piles is how a 13-asset list becomes a 30-asset list for no legibility gain - No loot-box or gacha art of any kind. There are no randomized paid items (Economy & Progression §1.3), so there is nothing to illustrate
- No level numeral set, no level-up stinger frames beyond
cube_burst. Numerals come fromtype/; the level-up moment is the burst plus the badge it already has
7. Draw Order¶
Fixed, and no variant may insert a layer.
0 backdrop_far arena
1 backdrop_near arena
2 floor + floor_decal arena
3 blast_residue blast skin
4 power-ups core
5 destructible / solid arena
6 cans core (+ flavour label decal, same layer)
7 characters character skin
8 status overlays core
9 blast VFX blast skin
10 debris particles arena
11 HUD core
Power-ups sit below tiles so a pickup can never occlude a block edge, and above floor decals so no arena dressing can swallow one. Status overlays sit above characters so no skin can hide them. Blast sits above everything in the world so the lethal layer is never ambiguous.
The one place this order is a liability: debris at layer 10 sits above pickups at layer 4, and pickups now spawn exactly where debris is thickest. The layering is right during the break and wrong immediately after, so it's resolved in time rather than in order — capped debris lifetime and a sequenced spawn_burst, per §6.2. Do not fix this by moving debris below pickups; debris needs to be above the blast to sell the destruction.
8. Compatibility Gates¶
Automated, run on every bundle before it can ship.
| Gate | Test | Threshold |
|---|---|---|
| Manifest completeness | Every required slot present | Pass/fail |
| Power-up contrast | 3 Class A pickups + Surprise Cube against every floor tile and decal in the pack | ΔE ≥ 30 |
| Cube glyph legibility | ? plate at 32px, greyscale, over every floor tile in the pack |
Human review — release blocker |
| Cube vs Ice Cube | Both at 32px, greyscale and colour, side by side and on adjacent tiles | Human review — release blocker |
| Currency vs cubes and Gold | economy/sugar_cube vs both game cubes, 32px greyscale; vs Gold badge, 32px colour, adjacent |
Human review — §6.7.2 |
| Reveal polarity | reveal_good vs reveal_bad, greyscale, first 6 frames only |
Human review — must be callable before the icon is legible |
| Tier separation | Solid vs destructible, silhouette-only, at 32px | Human review + luminance delta ≥ 25 |
| Blast rim hue | Rim pixels within 10°–45° | Pass/fail |
| Blast mask parity | Skin alpha silhouette matches core mask | Pixel-exact |
| Flavour pulse integrity | Label decal alpha never overlaps the fuse pulse region | Pixel-exact |
| Flavour silhouette | Can outline identical to core with label applied | Pixel-exact |
| Character footprint | Skin within 85% tile width, hat box respected | Pass/fail |
| Colourblind | Deuteranopia, protanopia, tritanopia simulation on the four player colours plus all blast skins | Human review |
| Atlas budget | Bundle ≤ 2048² per atlas | Pass/fail |
| Map validity | Every .map in the pack passes Appendix A §A.6 |
Pass/fail |
The power-up contrast gate is the price of the non-variant decision. It means an arena pack cannot use Sunbeam-adjacent hues on its floor — worth stating to artists on day one rather than discovering it at review.
9. Launch Scope¶
| Bundle | Unit count | Assets each | Total |
|---|---|---|---|
| Core — can, pickups, cube, status, blast mask, fx | 1 | ~205 | 205 |
| Core — HUD & UI | 1 | ~150 | 150 |
| Core — leagues | 1 | ~40 | 40 |
| Core — economy | 1 | ~13 | 13 |
| Arena packs | 2 | ~65 | 130 |
| Character base skins | 4 | ~112 | 448 |
| Blast — classic | 1 | ~54 | 54 |
| Flavour — classic | 1 | ~24 | 24 |
| Launch total | ~1,064 |
Post-launch unit costs, useful for roadmap planning:
| New content | Asset cost |
|---|---|
| One arena pack | ~65 |
| One character skin | ~112 |
| One blast skin | ~54 |
| One can flavour | ~24 |
| One flavour + matched blast SKU | ~78 |
Character skins are by far the most expensive thing to sell and flavours the cheapest — a useful shape for a store, provided §5.3 and §5.4's constraints hold. The matched flavour-and-blast SKU is the interesting middle: roughly two-thirds the cost of a character skin, visible to all four players in a match rather than only to the owner's own avatar, and the only cosmetic that reads at a glance from across the grid.
10. Decision Log¶
| # | Decision | Status |
|---|---|---|
| 1 | Shaker is the range power-up. Long Straw ditched. | Locked |
| 2 | Ice Cube caps at 3, Shaker at 6, Extra Can at 5. | Locked — Shaker lowered from 8, Gameplay Rules §8.2 |
| 3 | Blast skins are cosmetic only. | Locked — store copy rule in §5.3 |
| 4 | Can flavour reserved as a fifth axis, classic only at launch. |
Locked — §5.4 |
| 5 | ~~Pre-placement meter renamed Pressure~~ | Superseded — mechanic cut, no meter exists. Framework & Flow §8.7 |
| 6 | Timed effects: Coffee Bean (speed, 5s), Honeycomb Wax (slow, 5s), Glue (no placement, 3s). | Locked — §6.2 Class B |
| 7 | Timed effects are delivered only via Surprise Cubes, never visibly on a tile. Flat probability across all effects. | Locked — §6.2 |
| 8 | Class A pickups and Surprise Cubes share one drop table, spawning at the tile of a destroyed block. Nothing seeds at round start. | Locked — §6.2 Spawning |
| 9 | Effects are documented in a How to Play screen. | Locked — §6.5 |
| 10 | Arenas carry multiple grid maps, authored as plain ASCII text files scoped to the pack. | Locked — Appendix A |
| 11 | Every can is pre-shaken with a fixed 2.0s fuse. No charge input, no meter. | Locked — Framework & Flow §8.7 |
| 12 | Pickup behaviour is owned by Gameplay Rules & Mechanics; this document owns appearance. | Locked — §6.2 |
| 14 | The map file defines every tile. No procedural fill, no engine-placed lattice, no forced corner spawns. Grid fixed at 11×13. | Locked — Gameplay Rules §3 |
| 15 | Corridor exit rule replaces the fairness guarantee the lattice provided. | Locked — §A.7.16 |
| 16 | drop_weights is required in every map. No pack default, no global config. |
Locked — §A.4 |
| 17 | Maps: bundled baseline, server adds, additive only. IDs permanent and namespaced. | Locked — §11.1 |
| 18 | No dedicated 2-player maps in v1. players is spawn capacity, not match size. |
Locked — §11.2 |
| 13 | No lives system. One life per round — hud/lives is vestigial, cut or repurpose as round pips. |
Locked — Gameplay Rules §7 |
| 19 | Pack sources live outside the engine project, at Content/. Core lives inside it, at Assets/Game/Art/Core/. |
Locked — §2 |
| 20 | No category subdirectories inside art/. The slot name carries the category. |
Locked — §2.1 |
| 21 | The three raster exports are sibling directories art/@1x|@2x|@3x with the same filename in each — never a filename suffix. Core is exempt and authors one @3x set. |
Locked — §4 |
| 22 | Maps live inside the pack directory. | Locked — §2.2, Pack Studio §11 Q5 |
| 23 | Economy art is Core, non-variant, menu-only. Sugar cube icon, reward burst, XP bar, level badge (§6.7). Never on the play field | Locked — §6.7, Economy & Progression §10 |
11. Closed¶
All six items are decided. Nothing in this document is open.
| # | Question | Decision |
|---|---|---|
| 1 | Cube contents pool | All six effects at flat weight. Gameplay Rules §8.4 — see the note below on the two tables |
| 2 | Drop rate per arena or global | Declared in the map header, required. No pack default, no global config — §A.4 |
| 3 | Do blasts destroy uncollected pickups | Yes. pickup_destroyed added to fx_generic — §6.2 |
| 4 | First-cube rigging | Yes. A new account's first cube always rolls a buff. Server rule, no assets — §6.2 |
| 5 | Maps bundled or server-delivered | Both: bundled baseline, server-added, additive only — §11.1 |
| 6 | Does pack one author 2-player maps | No dedicated duel maps in v1 — §11.2 |
11.0 Two probability tables, not one¶
Worth stating plainly, because they are easy to conflate and the bug is silent:
| Table | Question it answers | Where | Who owns it |
|---|---|---|---|
| Drop table | When a block is destroyed, what spawns on that tile? | Map header, drop_weights |
Level designer, per map |
| Cube pool | When a Surprise Cube is opened, which of the six effects does it roll? | Fixed in code | Nobody — it is a constant |
They share no values and never will. The drop table has five entries including nothing and Surprise Cube; the cube pool has six entries, all effects, flat, no nothing row. A cube can never contain a cube. If an implementation rolls cube contents from the drop weights, a map that weights Surprise Cube highly produces cubes containing cubes, and the bug will present as "power-ups feel wrong on the Beach Kiosk" rather than as an exception.
11.1 Map delivery — bundled baseline, server adds¶
The base app ships a complete, playable baseline set of maps for every bundled pack. The server may add maps. It may never replace or remove one.
This is the right shape and it is worth understanding why the additive constraint matters more than the delivery mechanism:
- Offline play always works, and works identically to online play. A player on a plane has the full baseline, not a degraded subset
- A server outage cannot remove content. The worst case is that no new maps appear
- No map ever changes under a player's feet. A map ID resolves to exactly one file, forever. If a map needs a fix, it ships as a new ID and the old one is retired from rotation rather than edited
- Rollback is free. Pulling a bad map from rotation is a list change, not a client update
Three requirements this places on the pipeline:
- Map IDs are globally unique and permanent. Namespace them by pack:
diner/crossroads. A server map must never collide with a bundled one, and the validator checks the whole namespace, not just the pack directory - Server-added maps are validated on upload, not at build. Every rule in §A.7 has to run server-side too. A bundled map that fails validation breaks a build; a server map that fails validation breaks a live match, which is considerably worse
- Clients fetch missing maps at Lobby. A
.mapfile is under 4 KB, so this is a negligible download — but the lobby must handle "one player has this map and another does not" rather than assuming a shared set. Fetch on match config, before the ready check
11.2 No dedicated 2-player maps in version 1¶
Duel maps are cut from launch scope. This has a consequence that is not obvious and must be handled before any 2-player match runs.
Every v1 map declares players: 4 and carries four spawns. A 2-player match on that map uses two of them — so players is the map's spawn capacity, not the size of the match played on it. Validation rule 8 changes accordingly: spawn count equals players, and a match may have fewer players than the map's capacity, never more.
Which two spawns get used is a real decision. Reading order would put both players on the top edge, adjacent, which turns a duel into a knife fight at tick zero. The rule is in Gameplay Rules §3.3: choose the subset with maximum pairwise separation.
The duel tag and the worked duel example in §A.6 stay in the format. They cost nothing, and the day someone writes three duel maps in an afternoon, everything needed is already there.
Appendix A — Map File Specification¶
Format version 1.
A map is a plain text file describing one grid layout. It contains no art references. Every glyph names a slot in the arena manifest, and the pack the map lives in decides what that slot looks like — which is what lets the same layout play as a Diner or a Beach Kiosk without editing a byte.
A.1 The Glyph Principle¶
Dense symbols are solid. Digits are breakable. Punctuation is empty.
And every glyph is literal. There is no glyph that resolves probabilistically — the file is the board. ? and ~ existed in an earlier draft to drive random fill; both are cut and reserved, and the format is simpler for it.
That single rule is the whole format, and it means a designer can read a map's structure without consulting this document. # and % look like walls at a glance; 1 and 2 don't. The mapping is doing visual work in the text editor, which is the point of using text at all.
Two consequences worth stating up front:
#is solid, not a comment. Comments use;. A wall-shaped character should mean wall; giving the densest glyph in ASCII to metadata wastes it..is empty and only empty. It's the most common character in any map file, so it gets the lightest, quietest glyph. The earlier draft's separate void marker is gone — see §A.9.
A.2 File Conventions¶
| Property | Rule |
|---|---|
| Encoding | ASCII only. No UTF-8, no BOM, no box-drawing characters |
| Line endings | LF (\n). CRLF accepted on read and normalised; never written |
| Extension | .map |
| Location | arenas/{pack}/maps/{name}.map |
| Naming | Lowercase, underscores: diner_crossroads.map |
| Comments | Lines beginning ;. Permitted in the header only, never inside the grid |
| Whitespace | No tabs anywhere. No trailing spaces — a stray space is a parse error, not a warning |
| Final line | Must end with a newline |
| Max size | 4 KB. A map larger than this is a bug |
Why raw text and not JSON: the grid has to be readable and editable as a grid. A designer needs to see the arena in the file, count tiles by eye, and check symmetry by folding the page in half. Any format that wraps rows in quotes and commas destroys that, and the header is small enough not to need structure.
A.3 File Structure¶
; comment lines start with a semicolon
key: value
key: value
---
<grid rows>
Everything before the --- separator is the header. Everything after is the grid, one row per line, top row first.
A.4 Header Keys¶
| Key | Required | Type | Notes |
|---|---|---|---|
format |
yes | int | Currently 1. Parser rejects unknown versions |
name |
yes | string | Display name, localisation key |
pack |
yes | string | Arena pack this map belongs to. Must match the containing directory |
size |
yes | 11x13 |
Must be exactly 11x13 in format 1. Redundant on purpose — catches truncated files. Variable dimensions break the portrait layout budget's 80px minimum tile; see Gameplay Rules §3.2 |
players |
yes | 2–4 | Spawn capacity, not match size. Must equal the spawn marker count. A match may seat fewer players than this; never more. All v1 maps declare 4. Six-player is a version 2 change arriving with A–F spawn assignment |
drop_weights |
yes | 5 ints | The drop table for this map, in order: nothing, Shaker, Extra Can, Ice Cube, Surprise Cube. Required — there is no pack default and no global fallback. A map's block density and its drop weights are the same balance decision and belong in the same file. Gameplay Rules §8.5 |
symmetry |
no | enum | none | mirror_h | mirror_v | mirror_both | rot180. Validated if present |
tags |
no | csv | duel, chaotic, tight, open — for rotation and matchmaking filters |
There are no procedural keys. Every tile is authored.
One cost to accept with drop_weights required per map: a global rebalance means editing every map file rather than one manifest. That is a real maintenance tax and it grows with the map count. It is the correct trade anyway — a map's block count and its drop weights determine the same thing, the rate at which power arrives, and splitting them across two files is how a pack ends up feeling wrong for reasons nobody can locate. Mitigate with a bulk-edit script, not with a fallback. The only randomness in a match is the drop table, seeded from the match seed — see A.8.
A.5 Grid Glyphs¶
Solid — indestructible¶
| Glyph | Meaning | Manifest slot |
|---|---|---|
# |
Solid, engine picks the variant | solid_block[*] |
% |
Solid, pinned to variant A | solid_block[0] |
@ |
Solid, pinned to variant B | solid_block[1] |
& |
Solid, pinned to variant C | solid_block[2] |
Use # by default and pin only when it matters. The engine already assigns variants and solid_cap edges by neighbour rules, and it does a better job of avoiding visible repetition than a human placing them by hand. Pinning exists for landmarks — the jukebox that must sit at a specific spot, the groyne that anchors the composition. A map where every solid is pinned is a map someone did unnecessary work on, and it will look more repetitive, not less.
Destructible — breakable, drops from the table in §6.2¶
| Glyph | Meaning | Manifest slot |
|---|---|---|
1 |
Destructible, family A | destructible[0] |
2 |
Destructible, family B | destructible[1] |
3 |
Destructible, family C | destructible[2] |
4 |
Destructible, family D | destructible[3] |
Empty and directives¶
| Glyph | Meaning |
|---|---|
. |
Empty floor. Walkable, blast passes through |
S |
Player spawn. Resolves to empty floor. Assigned in reading order, top-left first |
H |
Arena hazard tile. Resolves to ambient_animated with collision. Pack-specific — the Beach Kiosk tide, the Diner grill |
Reserved and unused: 5–9, 0, $, X, T, G, R, ?, ~, A–F and all other characters are reserved for format version 2. A parser encountering one must reject the file rather than skip the glyph — a silently ignored character is a map that plays differently than it looks, and that bug is very hard to find later.
A.6 Worked Examples¶
Hand-authored¶
arenas/diner/maps/diner_crossroads.map
; Diner — Crossroads
; Launch map 1 of 3. Mirror-both. Dense corners, open centre.
; The two % tiles flanking the centre are the pinned jukeboxes.
format: 1
name: diner_crossroads
pack: diner
size: 11x13
players: 4
drop_weights: 62,12,10,6,10
symmetry: mirror_both
tags: open, chaotic
---
###########
#S..1.1..S#
#.#1#2#1#.#
#.21.3.12.#
#2#3#1#3#2#
#.1243421.#
#1#2%.%2#1#
#.1243421.#
#2#3#1#3#2#
#.21.3.12.#
#.#1#2#1#.#
#S..1.1..S#
###########
Note how much faster the structure reads than the previous all-digit version — the lattice of # is visible as a pattern, and the digits stand out as the things that will disappear during a round.
The same file dropped into arenas/beach_kiosk/maps/ with pack: beach_kiosk plays identically and looks nothing alike: solid becomes concrete groyne instead of chrome counter, destructibles become coolers and crates instead of booths and pie cases.
A duel map¶
Illustrative only — no duel maps ship in version 1 (§11.2). Retained because the format supports it and the day someone writes three of these in an afternoon, nothing needs to change.
Same pack, two spawns, deliberately tighter. Note the drop weights differ from the 4-player map: fewer blocks means each one has to carry more.
; Diner — Booth Standoff
; Duel map. Rot180. Spawns diagonal, centre is the only crossing.
format: 1
name: diner_booth_standoff
pack: diner
size: 11x13
players: 2
drop_weights: 58,12,10,8,12
symmetry: rot180
tags: duel, tight
---
###########
#S..1.1...#
#.#1#2#1#.#
#..21.12..#
#2#3#.#3#2#
#.1234321.#
#1#2%.%2#1#
#.1234321.#
#2#3#.#3#2#
#..21.12..#
#.#1#2#1#.#
#...1.1..S#
###########
Note the . gaps at rows 4 and 8 in the pillar columns: those are the perpendicular exits that satisfy A.7.16. Without them the vertical corridors on this map would run nine tiles unbroken, which is a hard validation failure and — more to the point — an unfair death waiting to happen.
A.7 Validation Rules¶
Run at build time on every map in every pack, and server-side on upload for every server-added map (§11.1). All are hard failures unless marked advisory.
Structural
- Header parses;
formatis a known version - Every row is the same length
- Grid dimensions equal
size, andsizeis exactly11x13 - Only glyphs from §A.5 appear
4a.
drop_weightsis present and parses as five non-negative integers, not all zero packmatches the containing directory, andpack/nameis unique across the entire map namespace, bundled and server-side together (§11.1)- Every pinned solid glyph (
%,@,&) maps to a variant index that exists in the pack manifest. A map pinning variant C in a pack that ships two solid variants must fail loudly, not fall back silently
Boundary
- Every tile in row 0, row H−1, column 0 and column W−1 is a solid glyph. The border is always sealed — a blast that leaves the grid is undefined behaviour, and every map must make that impossible by construction
Spawns
- Spawn count equals
players.playersis spawn capacity — a match may seat fewer, never more (§11.2) - Every spawn has at least two orthogonally adjacent empty tiles, and each of those has at least one empty neighbour of its own. A player who can be trapped by the first can they place has been killed by the level designer
- Minimum spawn separation: 6 tiles Manhattan
- Spawns may sit anywhere. Corners are conventional, not required — fairness is checked by rules 9, 10 and 12, not by position
Playability
- Connectivity with all destructibles removed: every empty tile is reachable from every spawn. An unreachable pocket is a place a pickup can spawn where nobody can go
- Connectivity with all destructibles present: each spawn has a route out requiring no more than 2 blocks destroyed. Otherwise the opening fifteen seconds are pure digging
- Destructible count ≥ 20% of interior tiles. Below that there is nothing to break, and with one drop table (§6.2) that means nothing to collect
- If
symmetryis set, the grid satisfies it exactly, spawns and pinned variants included - Corridor exit rule — every walkable tile has a perpendicular exit within 3 tiles along its corridor. Hard failure above 6. See Gameplay Rules §3.4
Rule 16 is the one that replaces the old pillar lattice. When solids were engine-placed on odd/odd coordinates, a side exit was never more than one tile away and nobody had to think about it. With authored solids, a nine-tile corridor with no exits is expressible, looks fine in a text editor, and is an inescapable death lane against a maxed blast range. Three tiles is derived from the escape equation: a slowed player covers 3.6 tiles in a 2.0s fuse.
Advisory — warnings, not failures
- Corridor exits between 4 and 6 tiles apart. Legitimate for a deliberate chokepoint; flag so it is a decision rather than an accident
- A 4-player map should be symmetric. Asymmetry is a legitimate choice for party modes and a serious problem for anything ranked
- Pinned solids under 20% of all solids. Above that, flag for review — see §A.5
- Solid blocks under 35% of interior tiles. Denser than that and the field is corridors rather than a grid
A.8 Load Sequence¶
parse header
↓
validate (§A.7) — reject the map, never repair it
↓
seed RNG from match seed + map name # drop table + cosmetic variants only
↓
resolve S → floor, register spawn points in reading order
↓
resolve H → hazard, pack-specific
↓
resolve # → solid variant by neighbour rules; %, @, & keep their pinned variant
↓
assign solid_cap edges
↓
assign floor_tile variants (random, seeded)
↓
hand off to renderer
The RNG seed now covers less than it used to, but still matters. With ? fill removed, the board layout is fully deterministic from the file — every client builds an identical grid with no shared randomness at all, which removes an entire class of desync. What remains seeded is the drop table and the cosmetic variant picks (# solid variant, floor tile variant). Seed both from the match seed and the map name, never wall-clock time and never a local device source: a pickup appearing on one client and not another is the same bug, just later in the match.
A.9 A Note on Voids¶
The previous draft had X for void — a tile with no floor at all, allowing non-rectangular arenas. It's cut from version 1, and . now covers everything that isn't a block.
The reasoning: the border must be sealed with solid blocks anyway (§A.7.7), so a void could never touch the edge of the play field where an irregular outline would actually be visible. That left it doing very little for a real validation cost — interior voids read as holes in the floor and need their own contiguity rules. If a genuinely non-rectangular arena turns out to be worth building, X is reserved and the feature can arrive in format 2 with the border rule rewritten to suit it.
A.10 Format Version 2 — Reserved¶
Do not build these now; do keep the glyphs free.
5–9— additional destructible families, if a pack ever needs more than fourX— void, per §A.9T— teleport pairG— guaranteed drop, for tutorial and scripted mapsA–F— explicit per-player spawn assignment, replacing reading order, for asymmetric and team modes. Six letters, not four, so a version 2 six-player mode does not need a third format revisionR— reinforced two-hit destructible, if the style guide §7 tier is ever built. It is not in the version 1 slot manifest and has no glyph today$— moving hazard or conveyor tile- A
layers:header key for maps needing a second grid pass
Part 5 — Economy & Progression¶
Version 0.3 — Pre-production
Changes in 0.3: two stale cross-references corrected. The IAP validation question is Framework & Flow §15 question 5 — question 9 is its number in the consolidated front matter, and the two lists are numbered differently. The age-rating question it cites is now closed at 13+ (decision 83), which the §1.3 note reflects. No economy rule changed. Changes in 0.2: §10 rewritten — the slot manifest moves to Asset Architecture §6.7, which owns asset slots; this section keeps the requirements and legibility constraints. No economy rule changed. Changes in 0.1: Initial version. Fills the gap the consolidated front matter called the Known Gap — defines the three account numbers (XP, league points, Sugar Cubes), prices, yields, the level curve, purchase validation and the match-end reward flow. Closes Framework & Flow §15 questions 7 and 11.
One of five companion documents. Gameplay Rules says what happens on the grid. Framework & Flow says what screens exist and who talks to the server. The Style Guide and Asset Architecture say what it looks like and how it is shipped. This document says what a player has, earns and spends across their lifetime on the game.
0. Scope and ownership¶
Three numbers live on an account. Everything in this document is one of them, or serves one of them.
| Number | Lifetime | Purpose | Owned by |
|---|---|---|---|
| XP | Forever. Never resets, never decreases | Lifetime player level. A "time served" signal that pays Sugar Cubes on level-up | This document |
| League points | One week. Reset at rollover | Rank inside the weekly club. Placement, promotion, demotion | Framework & Flow §17 — not this document |
| Sugar Cubes | Until spent | The store. Arena packs, character skins, blast skins, can flavours | This document |
The three never convert into each other. XP cannot be bought. League points cannot be bought and cannot be spent. Sugar Cubes cannot buy XP, league points, promotion, or anything that touches the grid. This is the pay-to-win firewall, stated once and applied everywhere below.
| Subject | Owner |
|---|---|
| Currency, XP, prices, yields, caps, level curve, rollover payout policy | This document |
| League placement — points, clubs, cuts, rollover job | Framework & Flow §17 |
| Round pips inside a match | Gameplay Rules §11 |
| Currency and reward art — icons, bursts, store cards | Asset Architecture §6.7 |
| Currency and reward colour law | Style Guide §3.1, §3.4 |
1. Sugar Cubes — the currency¶
One currency, earned and purchased. The in-world fiction: Chuggers hoard sugar. Sugar Cubes sit next to the Frenzy Cola brand the way bottle caps sit next to real soda — the thing you collect, the thing you trade.
There is no second premium currency in version 1. A purchased-only currency exists to create price opacity and to split the store into "earnable" and "not earnable," and both of those read as hostile in a casual game rated to attract children. Every item in the store is purchasable with earned play, always. Purchased cubes are the same cubes.
1.1 What they buy¶
Every store shelf (Framework & Flow §5.12) is priced in Sugar Cubes. All prices below are starting values, held in economy_config on the server (§7), never in the client.
| Shelf | Contents | Price (starting) |
|---|---|---|
| Arena packs | One theme, its maps, hosting rights (Framework & Flow decision 61: playable unowned, purchasable to host) | 1,500 |
| Character skins | Per-Chugger, kick animation enforced | 800 |
| Blast skins | Cosmetic-only foam fill | 500 |
| Can flavours | Reserved axis, post-launch | 300 |
The price shape follows the production cost shape (Asset Architecture §9): character skins are the most expensive thing made and the second most expensive thing sold; arena packs cost the most because they are the only shelf that is content, not cosmetic — a pack is maps forever, and hosting rights.
What Sugar Cubes can never buy: league points, XP, promotion, demotion immunity, round pips, pickup effects, anything rendered on the play field as an advantage. There is no SKU now or later that touches the simulation. This is the same law as blast skins being cosmetic-only (Asset Architecture §5.3), extended to the whole economy.
1.2 Earning¶
Deliberately sparse per drop, casual-generous over a week. Per-match yields are single digits and low double digits; prices are in the hundreds. A cube should feel like a thing you picked up, not a number inflating. All yields are server config.
| Source | Sugar Cubes |
|---|---|
| Online match completed (any mode) | 6 |
| Round won | +2 each |
| Match won | +4 |
| First win of the day (any mode, once per UTC day) | +10 |
| Level-up (§2.3) | 10 / 50 / 150 by milestone |
| Weekly league rollover | 0 — see §3.2 |
A best-of-3 sweep pays 14; a 0–2 loss pays 6. A player running five matches a day at a coin-flip win rate lands near 60–65 cubes a day with the first-win bonus — roughly 450 a week, before level-ups. Against the prices above: a blast skin in a good week, a character skin in under two, an arena pack in about three. That is the intended starting pressure: IAP buys impatience, never access.
Mode rules:
| Mode | XP | Sugar Cubes | League points |
|---|---|---|---|
| Quick Match | Yes | Yes | Yes (Framework & Flow §17.9) |
| Match Browser lobby | Yes | Yes | No |
| Private match by code | Yes | Yes | No |
| Single player vs AI | Yes, capped | Yes, capped | No |
- Single player is capped: the first 5 single-player matches per UTC day pay full XP and cubes; beyond that, zero. This closes the last open half of Framework & Flow §15 question 7 with the compromise that document itself names. Uncapped invites farming against Easy AI; zero kills the mode. Five is enough for the mode to feel rewarding and few enough that the farm is not worth running
- All online modes pay. Friends in a private lobby playing real matches to completion are not a farm; they are the game working as intended. The anti-collusion rule stays exactly where §17.9 put it — on the ladder, which is the only competitive surface. Cosmetics-only economies do not need the same wall
Anti-farm rules — mirrors of §17.9, applied to every reward in this document:
- Forfeit or long disconnect pays zero for the whole match, retroactively — XP, cubes, and the round-won bonuses already earned in it
- A match in which the player issued no input pays zero. The match handler already runs this check for league points; the same verdict covers economy rewards
- Yields are per match, not per round, except the round-won bonus. Quick Match still exposes no round-count option (Framework & Flow §17.3), so the flat participation yield cannot be gamed by match length
1.3 Purchasing¶
Sugar Cubes are sold in fixed bundles through the platform stores, validated server-side (IAP validation path remains Framework & Flow §15 question 5, front matter Q9 — this document consumes whichever answer lands there, and the implementation plan's M0 owns the closing).
| Bundle | Price anchor | Bonus |
|---|---|---|
| 100 cubes | €0.99 | — |
| 550 cubes | €4.99 | +10% |
| 1,200 cubes | €9.99 | +20% |
| 2,600 cubes | €19.99 | +30% |
| 7,500 cubes | €49.99 | +50% |
Compliance, because this is the document where money appears:
- The store shows the local-currency equivalent next to any Sugar Cube price when the purchase can be made with bought cubes. EU consumer law is heading this way regardless; doing it on day one is cheaper than retrofitting it
- No randomized paid items, ever. No loot boxes, no gacha, no paid Surprise Cubes. The store is flat: you see the item, you see the price, you buy the item. This is also half of why the age-rating question could close at 13+ (Framework & Flow §15 question 6, decision 83) — a game that will attract under-13s should have nothing a regulator can call gambling, whatever its rating says
- Restore purchases already lives in Settings (Framework & Flow §5.14). Unspent purchased cubes survive reinstall; unlinked accounts are still prompted to link after first purchase (§6.2), which is the flow that protects the money
- Purchase grants are idempotent on the store transaction ID. A retried validation must never credit twice (§9)
2. XP and player level¶
XP is the lifetime number. It only goes up, it never resets, and it is the one number on the account that says how long you have been here. Framework & Flow §5.2 already renders it — the Home badge carries "equipped Chugger, name, level" — without defining it. This is the definition.
2.1 Earning XP¶
| Source | XP |
|---|---|
| Match completed (any mode) | 10 |
| Round won | +5 each |
| Match won | +10 |
A best-of-3 sweep pays 30 XP; a 0–2 loss pays 10. Single-player awards the same rates inside the daily cap of §1.2. The anti-farm rules of §1.2 apply unchanged.
XP is not competitive, so it is not mode-restricted. League points are Quick-Match-only because the ladder is a competition and private collusion would corrupt it. XP ranks nothing — there is no XP leaderboard in version 1 — so there is nothing to corrupt, and a private match with friends deserves the same lifetime credit as a matchmade one.
2.2 The level curve¶
Cumulative XP to reach level n = 35 · n². One formula, no table.
| Level | Cumulative XP | Approx. matches | Feels like |
|---|---|---|---|
| 2 | 140 | ~6 | First evening |
| 5 | 875 | ~35 | First week |
| 10 | 3,500 | ~150 | First month of regular play |
| 25 | 21,875 | ~900 | A committed season |
| 50 | 87,500 | ~3,500 | Two years at 5/day |
The quadratic flattening is the point: early levels arrive fast enough to teach that levels pay Sugar Cubes, later levels space out until the number becomes a long-term possession. There is no level cap in version 1. A prestige or frame system is a post-launch decision and is deliberately not pre-built (§8).
2.3 What a level pays¶
Level-ups are the one place the economy celebrates. The payout is credited by the server the moment the level threshold is crossed, inside the same match-end write that granted the XP.
| Event | Sugar Cubes |
|---|---|
| Every level-up | 10 |
| Every 5th level (5, 10, 15, …) | 50 |
| Every 25th level (25, 50, 75, …) | 150 |
Milestones do not stack — a level pays the single highest row it qualifies for. Level 25 pays 150, not 10 + 50 + 150.
That is all a level does in version 1, and it is enough: the level is visible on Home and Profile, and it feeds the currency loop without ever gating it. No store item is level-gated — a new player who wants to buy a skin on day one is revenue, not a problem.
2.4 Where XP appears¶
- Home — the player badge chip, level number (Framework & Flow §5.2)
- Match Results — the XP bar beat: progress fill, level-up callout if one happened, cube payout alongside (§6)
- Profile — level, total matches, lifetime stats (Framework & Flow §5.13)
XP never appears inside a match, on the grid, or in the match HUD — the same rule the leagues follow (Style Guide §3.4). Status is a menu concept.
3. League points — weekly, reset, unchanged¶
League points are owned by Framework & Flow §17 and this document does not redefine them. One point per round won, plus one for completing a matchmade game; clubs of 50; rollover Sunday 22:00 UTC; scores reset to zero. Their purpose is singular: rank weekly in the leagues.
What this document adds is the answer §17 deliberately left open.
3.1 Points are not currency¶
League points are spent on nothing, buy nothing and convert to nothing. They exist to be compared, inside one club, for one week, and then they are gone. A player asking "what are my 14 points worth?" is answered by the standing itself — rank 22 of 50 — never by a payout table.
3.2 Rollover pays nothing in version 1 — decided¶
The weekly rollover publishes a result and pays no Sugar Cubes, no XP, and no items. This closes Framework & Flow §15 question 11 and the payout half of what was the front matter's Known Gap — as a decision, which is what the gap was asking for.
The reasoning is deliberate: Sugar Cubes stay sparse, and a weekly payout to every active account is the single largest faucet the economy would ever have. A faucet that size, set wrong at soft launch, cannot be quietly taken back — players experience a removed reward as a loss far more loudly than a never-given one. Leagues therefore ship as pure status, exactly as §17's scope boundary already assumed.
What is kept ready:
- The Leagues screen retains its reserved reward-strip layout under the header (Framework & Flow §5.17) — adding payouts later is a content change, not a redesign
- The rollover job's publish step (Framework & Flow §17.5, step 7) already emits each player's outgoing result; a payout table would consume that event, keyed on
week_idfor idempotency - If payouts are ever added, they pay by tier × movement — promoted above hold above demoted, higher tier above lower tier — so that deliberately demoting into an easier club is never the optimal play. Rank-based prizes are rejected in advance: they make the last hour before rollover a sniping contest
3.3 The one interaction: nothing¶
Worth stating plainly because it looks like an omission: there is no "league points also grant Sugar Cubes per point" rule, no participation conversion, no end-of-week salvage. Points rank. Cubes buy. XP accumulates. Three numbers, three jobs, zero conversions — the firewall of §0 applies to the ladder too.
4. The numbers, end to end¶
One worked week, so the shape is auditable in one place. Player: five matches a day, coin-flip results, one level-up mid-week.
| Source | Per week |
|---|---|
| Match completions (35 × 6) | 210 |
| Rounds won (~45 × 2) | 90 |
| Matches won (~17 × 4) | 68 |
| First-win bonus (7 × 10) | 70 |
| Level-up (one, non-milestone) | 10 |
| Total | ~450 cubes |
Against prices: blast skin ≈ 1 week, character skin ≈ 2 weeks, arena pack ≈ 3.3 weeks. Sparse per drop, generous over a month, and every lever in the table is a server config key (§7). If live data says the store feels unreachable, the earn column moves before the price column does — raising prices on existing players is a one-star review; raising yields is invisible.
5. Match-end flow¶
Rewards are computed and credited by the match handler at MATCH_END, in the same server write that credits league points (Framework & Flow §17.10). The client never claims, calculates, or writes a reward.
MATCH_END
├─ guard: forfeit? no input? → zero, stop
├─ league points → incr on club leaderboard (§17, Quick Match only)
├─ XP → add yields, evaluate level threshold(s)
│ └─ crossed? → credit level-up Sugar Cubes
├─ Sugar Cubes → credit match yields
├─ first win of the day? → credit bonus, set flag (UTC day key)
└─ single player? → enforce daily cap counter before any of the above
Every credit is idempotent on match_id. The same match result written twice — reconnect, retry, two devices on one account — credits once. The first-win flag is a date key, the single-player cap is a date-keyed counter, and both are server storage.
MATCH_END (opcode 9, Framework & Flow §7.3) already promises "final standings, rewards." This document defines the rewards payload:
"rewards": {
"cubes_earned": 14,
"cubes_total": 1243,
"xp_earned": 30,
"xp_total": 4260,
"level": 11,
"level_progress": 0.03,
"level_ups": [ { "level": 11, "cubes": 10 } ],
"first_win_bonus": true,
"league": { "points": 3, "club_total": 14, "rank_delta": 4 }
}
The league block is present only in Quick Match (Framework & Flow §17.12); the rest is present in every mode that pays.
5.1 Single player is the one untrusted path¶
Single-player matches are simulated on the client (Framework & Flow §8.5), so their reward claims are the only client-originated reward data in the game. Three guards, in order of how much work they do:
- The daily cap of 5 — the absolute ceiling on what abuse can yield, ~50 cubes a day
- Plausibility validation server-side — match duration, round count, and timestamps are checked against the rules of the game before credit; a 40-second best-of-5 is rejected
- Cosmetic-only stakes — the worst case of a successful cheat is cosmetics the cheater already had a free path to. No ladder is touched, no other player is harmed
Offline play queues reward claims locally and syncs on reconnection, inside the same cap. A plane flight's worth of matches pays its five and no more.
6. Client responsibilities¶
The client displays. It never computes and never writes. Currency, XP and level are server-write only (Framework & Flow §11, decision 59).
- Home — one currency counter beside the league chip, tapping through to Store. §5.2 says "counters" plural; version 1 ships exactly one, and the plural is a leftover of a dual-currency draft that this document closes
- Match Results — the reward beat: cubes earned, XP bar fill with level-up callout, league beat alongside in Quick Match. It animates with the standings and never gates REMATCH — the same law as the league beat (Framework & Flow §17.12), for the same reason
- Store — prices rendered from server config, never from a client table; Sugar Cube prices carry the local-currency equivalent (§1.3); the store copy rule stands — no cosmetic may be described with words that imply advantage
- Leagues — no reward UI. The reserved strip stays reserved (§3.2)
- Offline — cached balances render greyed with the same staleness treatment as the league standing; nothing economy-related blocks Home or Play, ever
7. Server implementation¶
Mirrors §17.10's approach: Nakama primitives, no bespoke stores, everything tunable without a client build.
| Concern | Mechanism |
|---|---|
| Sugar Cube balance | Nakama wallet, server-write only. The wallet ledger gives transaction history and idempotent writes natively |
| XP and level | Storage collection player_progression, key = user ID, server-write only. {xp_total, level}; level is derived from xp_total by the curve, stored to avoid recomputation on read |
| Level-up payouts | Evaluated inside the match-end write; credited to the wallet in the same transaction |
| First-win flag / single-player cap | Date-keyed counters in player_progression, UTC day |
| Purchase grants | Nakama IAP validation hooks (or the separate service, per Framework & Flow §15 question 5); wallet credit idempotent on store transaction ID |
| Config | economy_config storage record: xp_yield, cube_yield, level_curve_coeff, milestone_levels, sp_daily_cap, first_win_bonus, store_prices, iap_bundles. All tunable without a client build |
| Price changes | Prices are read by the client from config at Store entry. A price change is a config write, never an app update |
The league_config precedent stands: tuning the economy must never require a client build (Framework & Flow §17.10). If a number in this document is worth writing down, it is worth putting in economy_config.
8. Deliberate omissions¶
Listed so the absences read as decisions.
| Omitted | Reason |
|---|---|
| Second premium currency | Price opacity and a two-class store, in a game for children. Every item earnable, always — §1 |
| Weekly rollover payouts | The largest faucet the economy would ever have; irreversible if mistuned. Leagues ship as pure status — §3.2 |
| Rank-based weekly prizes | Makes the last hour before rollover a sniping contest — §3.2 |
| Level-gated store items | A day-one buyer is revenue, not a problem — §2.3 |
| Loot boxes / gacha / paid Surprise Cubes | Gambling mechanics in a child-attracting game; also store-review and regulatory risk — §1.3 |
| Daily quests / login calendar | A real retention lever, and version 2 material. Version 1's retention system is the league week; two chore systems at launch compete with each other |
| Battle pass | Needs a content cadence the launch asset plan does not have. Revisit after the second arena pack |
| Ads, rewarded video | None at launch. If ever added, opt-in rewarded only, and the age-rating answer decides first |
| Energy / lives / timers | A hard gate on playing is the opposite of the game's session pitch |
| Trading / gifting between players | A fraud and laundering surface with no gameplay benefit at this scale |
| Prestige / level frames | Post-launch. The curve has no cap to prestige against yet — §2.2 |
9. Failure modes to design against¶
- A reward is credited twice. Every credit is idempotent on
match_id; every purchase on the store transaction ID; every level-up payout is a function of the level threshold crossing, computed inside the XP write. Replays and retries credit once economy_configis malformed or missing. The server refuses to credit against bad config and falls back to the last known-good record, logging loudly. The client renders cached prices with a staleness marker. Nobody's balance ever depends on a parse succeeding- Client and server balances disagree. The server is the only truth. Balances refresh on
POST_MATCH, on Store entry, and on app foreground; a stale client display is cosmetic and self-heals - Single-player claim flooding. The daily cap and plausibility check bound it to ~50 cubes a day of cosmetic-only value — §5.1
- A purchase validates but the credit fails. The ungranted transaction sits in the wallet ledger as pending; restore purchases re-runs validation. No support flow may ever require the player to produce a receipt screenshot — the ledger is the record
- Price change mid-session. The client read a price at Store entry; the server validates the current price at purchase. A mismatch fails the purchase with a refresh, not a silent overcharge
10. Asset asks¶
Small, and all Core non-variant — currency is account state, not arena dressing. The slot manifest is Asset Architecture §6.7, which owns slots; this section is what the economy needs and why.
- A currency icon — the Sugar Cube. The game's third cube, and the only one that lives in menus. Translucent-white crystal, Sunbeam core per the palette's rewards/currency role; legible at 32px in greyscale and distinct from both the Surprise Cube and the Ice Cube, and from the Gold league badge it sits beside on Home. The pair checks are §6.7.2's, enforced by the §8 gate
- A reward burst. The celebration on Match Results and level-up — rising, saturated, the same motion language as
pickup_pop, never the sagging debuff vocabulary. The economy's only celebration, so it carries the whole "something good happened" channel - An XP bar and a level badge. Match Results, Profile, and the Home chip. The numerals are the type system, not new assets — tabular figures per Style Guide §4
Nothing from this group ever renders during a match. Currency, XP and level are menu concepts; the field is for danger (Style Guide §3.4, §11).
The Leagues reward strip remains un-authored per Asset Architecture §6.6.3 — §3.2 of this document confirms that as correct for version 1.
11. Open questions¶
| # | Question | Recommendation |
|---|---|---|
| 1 | All yields, prices, curve coefficient — every number in §§1–4 | Playtest and soft-launch data settle these. The structure is the proposal; the numbers are starting values in economy_config |
| 2 | First-purchase bonus (double cubes on first bundle)? | Standard practice, cheap, and converts the hardest purchase. Recommend yes at marketing's call — it is a config flag, not a system |
| 3 | Founder / soft-launch thank-you grant? | Decide with the soft-launch plan. If granted, a flat cube grant, never an exclusive item — exclusivity at soft launch punishes everyone who finds the game at real launch |
| 4 | Does the daily first-win bonus expire unused or bank? | Expire. A banking bonus is a chore and a hoard; a daily bonus is a reason to play today |
12. What this document resolves¶
| Was | Now |
|---|---|
| Front matter Known Gap: no economy specification; Store, Profile and the Match Results reward step blocked | This document. All three are unblocked |
| Framework & Flow §15 Q7: does single player feed progression? | Yes, capped — 5 single-player matches per UTC day pay full XP and Sugar Cubes, zero beyond (§1.2) |
| Framework & Flow §15 Q11: rollover rewards | Pays nothing in version 1 — leagues ship as pure status (§3.2) |
| Framework & Flow §5.10: "Rewards: currency, XP, unlock progress" — undefined | Defined — §5 |
| Framework & Flow §5.2: "currency counters" plural | One currency, one counter — §6 |
| Asset Architecture §6.6.3: no reward art, payouts undefined | Confirmed correct for v1 — and core/economy/ adds the menu-side currency and level art (§10) |