Fizz Frenzy — Full Specification

Consolidated edition · 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.

Single source of truth Compiled Five parts, bound whole

Fizz Frenzy — Full Specification

Version 1.0 — Pre-production

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 organised as five parts, delimited so that the set can be split into standalone documents at any time.


Component Documents

Part Document Source file
Part 1 Gameplay Rules & Mechanics fizz-frenzy-gameplay-rules.md
Part 2 Application Framework & Flow fizz-frenzy-framework-and-flow.md
Part 3 Design Style Guide fizz-frenzy-style-guide.md
Part 4 Visual Assets & Design Architecture fizz-frenzy-asset-architecture.md
Part 5 Economy & Progression 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.


How This Document Splits

Each part is a level-1 heading. Nothing else in the document uses level 1. To divide the specification again:

  1. 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
  2. Discard this front matter, which is everything above the first delimiter line
  3. Restore each part's original heading by replacing its "Part N" title line with the "FIZZ FRENZY" title shown in the table above
  4. Save under the source filename in the table above

Within each part, headings begin at level 2 and nest from there. Section numbers are local to each part, 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 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, 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, 8 + Appendix A
UI designer Part 3 §§4, 11 Part 2 §5, §17.12, Part 5 §6
Producer This front matter Part 4 §9 + Appendix B, 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 parts, in one table. Each is expanded in the part cited. Numbers are stable identifiers referenced from other project documents; they are grouped by topic, not ordered.

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 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 §10.2, P2 §10
13 Map capacity is a ceiling. Duels run on 4-spawn maps or on a map declaring capacity 2; a map is never offered to more players than it declares P4 §10.3, P1 §3.3
14 Version 1 supports one hazard type — a periodically lethal tile (H) P1 §12.1
84 Rendering is deterministic, not just layout. Slot variants are a pure function of tile position, never of the match seed. The RNG seeds the drop table and nothing else P4 §A.5, §A.8
85 An optional second grid block carries floor decals — same 11×13 shape, glyphs a–d, purely cosmetic, absent block means none. One tile carries two facts, so it takes two aligned grids P4 §A.3, §A.5

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 3 ticks / 0.2s; 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, and may leave to Match Results. Ghost mode is post-launch P1 §7.1
29 Simultaneous death is a draw — no point awarded, round replayed. Round-wins matches only; single player scores by placement P1 §7, §11.1, §11.3
30 Sudden death from T−53s, one indestructible block every SuddenDeathTicksPerBlock ticks (8, 0.53s), spiralling inward across five interior rings P1 §12
31 2-player duels use the identical arena, grid and ruleset as 4-player P1 §11.2
32 Beach Kiosk seagulls are ambient only — they never interact with pickups 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 §10.1
39 First cube of a new account is rigged to roll a buff. Server rule, no assets P1 §8.4
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 the ordinary 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
86 A pack art filename is its slot name, with no axis, pack or skin prefix — the directory path carries those. pack.json therefore holds no slot→file table P4 §3.1

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 P2 §9, §14
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 §14, 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. What remains open is tuning, scope and commercial policy.

Tuning — only playtesting settles these

# Question Part
1 Corridor exit distance — 3 tiles hard, 6 advisory? Derived, but verify against a real authored map P1 §3.4
2 Drop weights against the Shaker cap of 6 under four-player contention. 62,12,10,6,10 is the accepted default, but the cap-6 case has not been exercised with four real players. Owned by implementation-plan M8 P1 §8.5
3 Economy numbers — yields, prices, curve coefficient. All starting values in server config; soft-launch data settles them P5 §11

Scope

# Question Part
4 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
5 Is 22:00 UTC the right instant for the intended launch region? The fixed-offset decision stands regardless of the number P2 §17.5
6 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
7 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
# 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 P2 §15
10 GDPR consent age. 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 §14

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−53s
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 the damage window, 3 ticks. 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

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

  1. Four Chuggers spawn at the positions the map defines, on an 11×13 grid dense with destructible blocks
  2. Each starts with one can, blast range 1, and no upgrades
  3. Dropping a can destroys blocks, opens paths, and reveals pickups
  4. Pickups increase range, can count, and unlock the kick
  5. The last Chugger alive wins the round
  6. 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: 9.5 visible columns (the outer wall ring is cropped to a quarter tile) at ~119 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 x 0–10 left to right, rows index y 0–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 an escape from its own first can: an empty orthogonal neighbour plus one more empty tile beyond it, around the corner or straight on. At the starting blast range of 1 that tile is outside the can's cross. 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. A map is offered only to matches at or below its declared capacity. Most maps declare 4 and carry four spawns, so a 2- or 3-player match runs on one of them and the engine must choose which spawns to use.

A map may declare 2. diner_booth_standoff declares players: 2 and the duel tag: it is selectable for a duel and never for a four-player match.

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

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 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 enforces a side-exit guarantee directly:

Every walkable tile must have a perpendicular exit within 3 tiles along its corridor.

Three tiles is derived from base speed. Reaching the exit tile is not safety — the player is still in the lane — so the escape is three tiles along plus one through the opening:

Exit at Travel At 3.0 tiles/s Under Wax, 1.8 tiles/s
2 tiles 3 1.00s 1.67s
3 tiles 4 1.33s — the rule, 0.67s of margin 2.22s — dead
4 tiles 5 1.67s dead
5 tiles 6 2.00s — an exact dead heat dead
6 tiles 7 2.33s — dead at base speed dead

Honeycomb Wax plus maximum blast range is meant to be lethal without a near corner. Under Wax, a three-tile exit is 2.22s against a 2.0s fuse, so there is no escape along the lane at all. Stacking a movement debuff against a maxed opponent should be close to a death sentence, and the player's answer is to already be near a corner rather than to outrun the arm. §10's tight case is survivable given a side exit within two tiles — which this rule does not guarantee, and is not required to.

  • 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

A warning for whoever uses that advisory band. The table above is at base speed with no debuff at all. A 6-tile corridor is already lethal to a player at full speed who drops a can in it — 2.33s against a 2.0s fuse — and a 5-tile one is an exact dead heat. The advisory band is not "tense"; it is "the author has chosen to make this stretch fatal". A legitimate choice, and one to make with the number in front of you.

This is the most important validation rule in the format, and the easiest one to forget: 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.

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.42 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
  • There is no junction exception. Assist fires wherever there is a lane the player could enter

Without this, players die to geometry and blame the game. With it, they die to their own decisions.

Why there is no junction exception. Suppressing assist wherever both perpendicular neighbours are walkable would ask whether the player could step sideways, not whether the choice is ambiguous — and on an open board the first is nearly always true, so assist would survive only beside the border wall. Ambiguity is handled where it actually arises: the nearest qualifying lane wins, and an exact tie breaks toward the lower coordinate.

Why 0.42. A player covers speed ÷ rate tiles a tick — 0.2 at §4.4's 3.0 tiles/s and 15 Hz — so from a lane centre the offsets they can come to rest on cycle 0.0, 0.2, 0.4, 0.4, 0.2, 0.0. The threshold must cover 0.4, and it must not equal it: at exactly 0.40 the comparison sits on a knife edge — 1.5 + 0.2×7 is 2.9000000000000004, whose offset exceeds 0.4 — and float error accumulates differently in the TypeScript server port than in the C# client, so the two would strand players on different tiles. The extra 0.02 is two pixels, below perception, and it is what stops the rule depending on floating-point representation. Never set this equal to a reachable offset.

Why not 0.5. It assists from anywhere in a tile and reads as the game steering.

One limit is accepted rather than fixed. §8.3's timed effects change the step mid-match: a Coffee Bean's ×1.4 steps 0.28 tiles and can come to rest 0.44 from a lane centre, Honeycomb Wax's ×0.6 steps 0.12 and can rest at 0.48. Both are past 0.42, so a player under either effect can still be stopped a few pixels short. Only 0.5 removes it, and 0.5 costs the whole mechanic's restraint. The two effects are transient, one of them is a debuff whose whole job is to make movement worse, and buying their edge cases would make every ordinary step feel steered.

This number and §4.4's base speed cannot be tuned independently. Changing either moves the offsets the other has to cover, and 0.42 has only two pixels of margin. It remains an input to the escape equation (§10) and the corridor exit distance (§3.4).

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

At 3.0, crossing the board is neither tedious nor fast enough to make the 2.0s fuse hard to judge. Every constant derived from it — the escape equation (§10), the two derived speeds above, the corridor exit distance (§3.4) — depends on this number.

▲ 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 (§10) 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.

A can is solid to every other player from the instant of placement — except to one already standing on the tile. §4.3 makes Chuggers pass through each other, so two players sharing a tile when one of them drops a can is ordinary play rather than a corner case. The exemption is therefore per player, not per owner: every player whose box overlaps the tile at the instant of placement is exempt, and each clears independently, exactly as the owner does.

Forbidding the drop instead is rejected — it would let a player deny a can by standing still, which is precisely the body-blocking grief tactic §4.3 gave up solid players to avoid, reintroduced through the can.

Without the exemption the non-owner is teleported. Collision tests only the leading edge of a move, so walking out of a newly solid tile resolves cleanly; a step whose leading edge lands back in it takes the blocked branch and clamps to the face it entered from — a backwards snap of up to 0.8 tiles, in one tick, with no wall in front of the player.

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 whether or not you hold an Ice Cube. The cube decides whether the can moves; it never decides whether the player is blocked.

A kicked can occupies its tile until it has cleared, so the kicker is blocked on the tick they kick exactly as a player with no cube is. There is no tick on which a Chugger and a can share a tile by walking, and a rule that says otherwise invites an implementation that lets the kicker follow the can into the tile it is still leaving. The kick and its blocked-behind case are §9.


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
Arena hazard (H) Blast fills it, arm continues — as floor

Why the blast fills a hazard. H is passable floor (§12.1, Asset Architecture §A.5): a tile a player can stand on and die on is a tile the foam must be able to reach, or a hazard becomes a blast shelter, which is the opposite of what it is for. Collision never varies with the hazard's phase, so propagation is static — the arm does not care which part of the cycle the tile is in.

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 Ticks @15 Hz Seconds
Damage window 3 0.2s
Blast VFX 6 0.4s

The tick count is the source and the seconds are derived. At a fixed server rate a duration stated in seconds is not implementable unless it happens to divide into whole ticks.

A player is in a blast tile when their tile is — the tile their centre occupies, not any tile their collision box overlaps. This is not a detail: the box is 0.7 tiles on a 1.0 tile, so a box test would kill anyone standing within a third of a tile of the foam's edge and make the lethal region visibly wider than the cross that is drawn. That is exactly the "the game lied to me" failure this section exists to prevent, and it would silently widen the region §10's escape equation — and therefore the 3.0 base speed — was verified against. It is also the same test §5.1 uses to decide which tile a can is placed on, so the two agree.

Everything lethal happens in the first 0.2 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.

Why 3 ticks. Two ticks would also work — nothing fails at 2 — but at 3 the lethal moment has time to register as one, and the blast reads better. It is a feel preference, not a defect fix: do not reconstruct a failure at 2 that never happened. At 3 ticks, half of the 0.4s foam is lethal.

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 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 resemble pickup_pop — a player who sees the collect animation and gets no stat change will report it as a bug
  • The endgame does not silt 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 fewer players remain alive than min(2, seated) — so a four-player round ends at one survivor, and a solo match ends only when that player dies. What this rule means is that a winner has been decided, and that needs an opponent to have lost — a rule like "one or zero players remain" would be true at tick zero of a single-player match
  • Simultaneous death is a draw. No player is awarded the round

There is no lives system. Asset Architecture §6.4's hud/lives is vestigial — cut it, or repurpose it 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 marker leaves the field with their Chugger (the defeat animation plays, then both are gone), the same as everyone else sees it. There is no HUD roster to update (Framework & Flow §5.8)
  • Leave. A spectating player may drop to Match Results at any time. It is built together with Match Results (implementation-plan M9) rather than against a placeholder, and it cannot apply in hotseat, where the one local player leaving ends the match for everyone sharing the device
  • 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 is 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. Not counted in the HUD since the lean HUD (Framework & Flow §5.8.1): the effect shows in play. 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. Range 9 would span an 11-wide grid from any position, making the top 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. The simulation tracks 0–6; the HUD shows no counter.

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 the ordinary pickup_pop. There is no capped variant in version 1.

Why no capped animation. Solo, a player caps blast range and Ice Cubes readily, so the state is not rare enough to be a discovery; across four players contending for ~48 blocks it is not common enough to teach anything either. A distinct animation for a moment that is either unremarkable or unreachable is art nobody reads. The simulation still distinguishes the two outcomes — PickupCollectedCapped is a separate event — so a duller sound costs nothing later if the ceiling turns out to want announcing.

The HUD counts none of the three. Range and kick show in play, and a player reads them off the board. The one exception is derived, not a counter: DROP carries the number of cans the player can place right now (Extra Can stacks + 1, minus cans already on the field), because that is the one thing the board cannot show. Framework & Flow §5.8.1 owns the widget.

8.3 Class B — timed effects

Do not stack. Shown as a depleting fill, not a counter — what the player needs is time remaining, and a timed effect has no quantity to count. 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

The depleting fill and Framework & Flow §5.8.1's "effect chip with a drain ring" are one widget, described from two sides. This section says what the indicator must convey; that one says where it sits and what it is made of, and P2 owns HUD structure per the ownership table. Glue draws its own fill alongside a movement effect rather than replacing it, because the two coexist.

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 ordinary 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

Implementation note. "A new account's first-ever cube" is a fact about an account, not a match. The simulation implements the rule behind a flag the match is created with; account persistence (implementation-plan M9) is what sets that flag.

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
Nothing 62
Shaker 12
Extra Can 10
Ice Cube 6
Surprise Cube 10

62,12,10,6,10 is the accepted default. Solo on a 48-block map, blast range and Ice Cubes cap and five cans are reached, so power arrives fast enough alone. Under four-player contention the caps are expected to be mostly unreachable — a property of a map's block count, not a fault to weight around.

One case is still unverified: the table against the Shaker cap of 6 under real four-player contention, which only networked play can produce. That check belongs to implementation-plan M8 (front matter open question 2).

Three things to hold:

  1. 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 the accepted default to copy into a new map, not a placeholder the engine supplies
  2. 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
  3. 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.

The trigger is automatic. There is no KICK button and no kick input. The trigger is a simulation rule, which the ownership table gives to this document, so it cannot be half a control scheme. The cost is real and accepted: an Ice Cube permanently removes the ability to back up against a can. A cornered player holding one shoves their own can away and keeps running, with no way to decline. On device this is not felt as the game fighting the player.

Ice Cube stack Slide speed
1 6.0 tiles/s
2 8.0 tiles/s
3 10.0 tiles/s

The blocked-behind rule decides whether a slide starts at all:

Walking into a can while holding ≥ 1 Ice Cube Result
The tile beyond the can, in the direction of travel, is free The can slides. The player is still blocked this tick — the can occupies that tile until it clears
That tile holds a solid, a destructible, another can, a player, or the border No kick happens. The can stays where it is and blocks the player exactly as it blocks a player with no Ice Cube

A can wedged against a wall is a wall, for everybody. Without the second row the kick either has to fail silently after being announced, or has to push a can into an occupied tile, and both are worse than declining to start.

This never becomes fiddly, because the slowest kick is 6.0 tiles/s against a 3.0 tiles/s walk: a kicked can always outruns the player who kicked it. The player pushes once and follows. There is no repeated nudging.

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 (3 ticks, 0.2s), not for the VFX duration (0.4s / 6 ticks). 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 — and does — feel like nearly dying.

This case assumes a side exit within two tiles, and §3.4 does not guarantee one. At the three tiles §3.4 permits, the escape is four tiles and 2.22s — beyond the fuse. That is accepted: Wax plus maximum range is meant to be lethal unless the player is already near a corner. The equation above describes the case where a corner is available; it is not a promise that one always is.

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−53s (i.e. at 67s 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 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. At the cap, the highest scorer wins; only a tie at the top is a draw. A flat draw would discard a player who led 2-1-1-0 through seven rounds of a best-of-5. The cap bounds the match — without it, players who keep drawing could play forever. Only a drawn round fails to move anyone's total, so only repeated draws can reach the cap at all: a best-of-3 cannot see four decided rounds, because someone reached two first

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.

A duel usually runs on a standard 4-spawn map with two of its spawns occupied, selected by the maximum-separation rule in §3.3. It may also run on a map that declares a capacity of 2, such as diner_booth_standoff, which is then never offered to a four-player match.

Three things to hold:

  • A four-player board is never offered to more players than it declares, and never fewer than it can seat. Capacity is a ceiling, not an equality: a 4-spawn map takes 2, 3 or 4 players, and a 2-spawn map takes only 2
  • 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 more duel maps later
  • This makes §3.3 load-bearing rather than a detail. With most duels on 4-spawn maps, spawn selection is what makes a 2-player match open sensibly. It is not optional polish

11.3 Single-player scoring

A match with exactly one human (single player against bots) scores by placement instead of round wins. Multiplayer, whether local or matchmade, keeps one point per round won (§11, §11.1), and league points stay built on that unit (Framework & Flow §17.3). This scoring is single player's own and never feeds the ladder.

Place in the round Points
1st 4
2nd 2
3rd 1
4th 0
  • Places come from pop order. First is the player left standing, second the player popped last before that, and so on
  • Popped on the same tick: the players share the places they cover and split those places' points, rounded down to a whole point. Two players covering 2nd and 3rd get 1 each
  • Timeout: the survivors split the 4, rounded down, and everyone popped scores 0
  • Wipe-out: when the last players standing are all popped on the same tick, they count as the survivors and split the 4. Everyone popped earlier scores 0
  • Fewer players: the same table. A 1-vs-1 loser scores 2

Every round counts, draws included. Nothing is replayed, §11.1's hard cap does not apply, and the match is always exactly the configured 1, 3 or 5 rounds: second and third place keep every player in it to the end. The highest total wins; a shared top total is a drawn match. There is no tie-break.


12. Sudden Death

Triggered at T−53s. The arena closes from the outside in, one block at a time, spiralling clockwise from the outer ring inward.

Five interior rings. The map border is already indestructible (Asset Architecture §A.7), so the ring closes the 9×11 interior — five concentric rings:

Ring Extent Tiles
1 x 1–9, y 1–11 36
2 x 2–8, y 2–10 28
3 x 3–7, y 3–9 20
4 x 4–6, y 4–8 12
5 x 5, y 5–7 3
99

Each ring fills clockwise from its top-left, one tile at a time, so players can read the direction and run ahead of it.

One block every SuddenDeathTicksPerBlock ticks — 8, i.e. 0.53s. A constant per-block beat, rather than a whole ring at once, is what lets players read the direction and run ahead of it: a 36-tile ring landing on a single tick is a guillotine with nothing to outrun. It also makes the ring accelerate on its own as the rings get shorter, which is the tension the mechanic exists to create. Spreading each ring across a fixed time instead would make it decelerate as it tightens, and would need a rounding rule the C# simulation and the TypeScript server port must agree on exactly. SuddenDeathTicksPerBlock is a tuning value in SimTuning, not a constant. Faster intervals read on device as a guillotine rather than pressure.

The trigger is paired with the interval. SuddenDeathAtTick is T−53s so that the ring finishes before the round timer does. The first block lands on the start tick itself; the last of the 99 — 98 intervals later — lands at tick 1789, against the 1800-tick round timer. That margin is thin: 11 ticks, 0.73s. A dedicated test guards the arithmetic directly, at every tick rate the dev toggle offers, and any retune of either number must keep the ring inside the timer.

  • A closing block fills the tile with an indestructible block
  • Anything on a filled tile is destroyed: players die, pickups vanish, destructible blocks are replaced. A closing block draws nothing from the generator — the same rule §8.5 states for a blast over empty floor. There is one RNG stream by design; the drop roll belongs only inside the destructible branch of §13 step 8, and a ring block rolling too would shift every later pickup in the round exactly as an errant blast-tile roll would
  • A can caught by a closing block detonates on the next tick, not this one. §13 step 10 runs after fuses, chain resolution, the blast tile set and damage have already resolved for this tick; same-tick detonation would need either a step inserted before step 5 or a second blast pass inside step 10, and a second pass breaks "all deaths this tick are simultaneous," the property that makes a mutual kill a genuine draw. The block instead marks the can for detonation, and next tick's step 6 chains it exactly as it chains a fuse expiry — which is also what makes a crushed can that touches off two more read as one event
  • A survivor whose collision box merely overlaps a new block is clamped flush out of it. The 0.7-wide box reaches a quarter-tile into the next tile, so without this a player standing just outside a closing tile is left a quarter-tile inside a wall until they next try to move

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, placed with map glyph H. It kills anything standing on it during its active phase and detonates cans. It is passable floor at every point in that cycle — see §A.5, and §6.2's propagation row.

The rhythm is a version 1 constant, not pack data. No map header key or pack.json field carries it. Period, warning and active durations live in the simulation's tuning table beside the other feel numbers, which is also where a cheat-menu slider can reach them. Promotion to authored data is a later change if a pack ever needs a second rhythm, and it changes no rule when it happens.

The rhythm: a 4.0s cycle, 0.8s of telegraph, 0.53s lethal. Brisk (2.0s) and slow (8.0s) cycles both read worse on device. The numbers stay in the tuning table rather than becoming constants, because that is what a later pack promotion needs.

The warning phase carries no rule. It exists so the tile telegraphs, and only the view reads it. A player who walks onto an already-active tile dies — "standing on it" includes arriving.

That covers the Diner's soda fountain. Teleporters, ricochet and persistent floor states need map format 2 and are not in v1.

The Beach Kiosk tide is not a hazard tile. The tide is the closing ring (§12) — sudden death, reskinned — because it closes a whole arena on a schedule, and H is one tile with a cycle. The soda fountain is the case only H serves.

12.2 The seagull — ambient only

Seagulls never interact with pickups. Blasts already handle uncollected pickups (§6.5), and a seagull that stole them would be a second solution to a solved problem that also 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.

  1. Apply movement inputs; resolve collision and corner assist
  2. Resolve kicks initiated this tick
  3. Advance sliding cans; resolve slide collisions; mark any can now occupying a live-blast tile for detonation (§9.1)
  4. Resolve can placements
  5. Decrement fuses; mark expired cans for detonation — and mark any can standing on a hazard tile whose phase is active (§12.1)
  6. Resolve chain detonations to a fixed point — repeat until no new can is marked
  7. Compute the full blast tile set for all detonating cans simultaneously
  8. Destroy blocks; destroy pickups and cubes in blast tiles (§6.5)
  9. Apply damage to every player in a blast tile, and to every player whose centre tile is an active hazard — all deaths this tick are simultaneous
  10. Advance sudden-death ring if due
  11. Resolve pickup collection
  12. Tick timed effects; expire finished ones
  13. Queue deferred spawn bursts for tiles whose blast has cleared
  14. Expire live-blast tiles whose damage window has elapsed
  15. 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 the damage window — 3 ticks. 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.

The hazard gets two clauses and no step of its own. Its phase is a pure function of the tick and the §12.1 constants — nothing is stored and nothing is advanced — so there is nothing for a step to do. Step 5 marks a can on an active tile so that step 6 chains it exactly as it chains a fuse expiry, and a fountain setting off a three-can chain reads as one event. Step 9's clause is the load-bearing half: putting the hazard kill in the same simultaneous pass as blast damage is what makes a player taken by the fountain and a player taken by a blast on the same tick a genuine draw rather than an ordering artefact. Neither clause reorders anything, and inserting a step would invalidate every constant tuned on device, which is what this list exists to prevent.

Step 10 is stateless in the same way. It sits fixed after step 9's damage pass, and that position is deliberate: closing blocks land one at a time, 0.53s apart (§12), so two players taken by the ring die on different ticks rather than in the same simultaneous pass, and the round keeps a clean winner instead of every ring kill becoming a coincidental draw with the tick's blast damage. What has been placed by a given tick is a pure function of the tick and the §12 interval, exactly as the hazard's phase is — nothing is stored, so there is nothing to compare in state equality, serialise for the conformance canon below, or re-implement diverging in the TypeScript port.

The round timer is a clause on step 15, not a sixteenth step. If it reaches 120s with the round still live, the round ends in a draw — no player is credited. This should be unreachable: sudden death fully closes the field at tick 1789 by default — 119.3s, 0.73s before the 1800-tick timer would fire (§12). That margin is thin enough that this unreachable branch is worth taking seriously rather than filing away. It is defined anyway, because an unreachable branch that turns out to be reachable — a retuned ring interval, a hazard that stalls the last kill, a bug — is how a match hangs, and the alternative to a defined rule is an undefined one.

A tick index is resolved before the counter that names it advances. The tick counter increments only after all fifteen steps have run, so a rule inside a step reads the index it is resolving — call it N — while anything reading the counter between ticks sees N+1. At the start of resolving tick N, exactly N/15 seconds have elapsed at 15 Hz — which is why the round timer above fires at exactly 120.0s and why sudden death places its first block at exactly 67.0s, both comparing the resolving index against a tick count rather than the elapsed one. The two rules read the counter the same way on purpose; if they ever disagreed about which side of the increment they meant, one of them would be wrong.

M8's conformance suite replays this order against fixed input sequences. ffvector runs the vectors under tests/conformance/vectors/ and compares a canonical serialisation of the state, tick by tick and then once more at the final tick, so a divergence between this implementation and the TypeScript port is caught at the tick it starts rather than reported as "the two states differ somewhere." The canon separates its grid rows with commas: without that, a live-blast row of [1,12,0…] and one of [11,2,0…] both render as 112000000000, and two distinct states would share a digest — a false match, the worst outcome the suite can produce, since it reports agreement between implementations that disagree.


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:

  1. Ghost mode — eliminated players lob cans from the sidelines
  2. Remote detonator — limited-use pickup, confirmed for v2
  3. Punch / throw
  4. 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. With the kick automatic (§9), DROP is the only button there, so the detonator would be the first thing to make the lower-right cluster a layout question rather than one icon.


15. Open Questions

# Question Blocks Recommendation
1 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 open question in this document is a tuning question that only playtesting answers. There are no unresolved rules.


Part 2 — Application Framework & Flow

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
Player markers 4 — colour + shape under each Chugger's feet; there is no HUD roster (Framework & Flow §5.8)
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
Player markers Two more marker shapes, matching shape badges 5 and 6. There is no HUD roster to redesign Small
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, a slim top row (timer, round pips or single-player points, menu) above the field and the 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 goes to the control zone. The tile size on a tall phone comes from cropping the outer wall ring, not from the vertical budget. 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

The full set. Each entry is there because something in the design implies it exists.

# Screen Why it exists
1 Boot / Splash Nakama session restore, asset manifest check, pack integrity
2 Home Root hub
3 Single Player Setup Arena, map, AI count, difficulty
4 Multiplayer Hub Quick Match vs Browse vs Private
5 Account Device auth is silent; this screen is upgrade & link, not gate
6 Match Browser Lobby list, filter, join
7 Match Lobby Pre-match room: roster, ready state, arena vote, chat
8 Loading / Handoff Pack download, map parse, spawn assignment
9 Game Engine (Match) The actual game
10 Pause Single player only — see §8.4
11 Round Results Between-round scoreboard: round pips, or single-player points (Gameplay Rules §11.3)
12 Match Results Winner, rewards, rematch, exit
13 Character Select Four Chuggers, skins per Asset Architecture §5.2
14 Store Arena packs, character skins, blast skins, flavours
15 Profile / Collection Owned cosmetics, stats, equipped loadout
16 Settings Audio, haptics, control scheme, language, account
17 How to Play Pickup vocabulary — Asset Architecture §6.5 depends on this existing
18 About Version, legal, links
19 Credits Child of About
20 Chat Not a screen — an overlay component. See §9
21 Network Not a screen — a service. See §7
22 Player AI Not a screen — a system. See §8.5
23 Leagues / Leaderboard Weekly club standing, tier, cut lines — §17

There is no 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:

  1. 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.
  2. 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.
  3. Loading is never a destination. It has no back affordance and is entered only from a screen that has already committed the player.
  4. 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.
  5. 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_version above 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 counter (Sugar Cubes) → 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:

  1. Quick Match — matchmaker ticket, no configuration. This is the default and should be visually dominant
  2. Browse Matches → Match Browser
  3. 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_players slots (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:

The lean HUD (adopted 2026-09-25). Every pixel of height goes to the field. There is no player roster and no pickup strip: players are recognised on the board, and power-ups show their effect in play.

HUD, top row (40 CSS px): - Round timer, tabular figures per style guide §4 - Round pips (best-of-N); in single player the point totals instead: yours on the left, the leading opponent's on the right (Gameplay Rules §11.3) - Menu button at the right edge (pause in single player, the match menu online)

On the field, instead of the roster: - Player marker under every Chugger: the player's shape badge (circle, square, triangle, star) in their player colour, lying flat on the floor at the feet. It is the who-is-who cue and the colourblind identity (style guide §3.2); the corners and points that show around the feet carry the shape - Opponent debuffs show as the status overlay on the Chugger itself (Asset Architecture §6.3), not on an avatar - A defeated player's Chugger and marker leave the field; there is no dead slot to grey out

5.8.1 Portrait layout budget

Reference device: 6.1" phone, 390×844 CSS px (1170×2532 at 3×), 19.5:9. The layout is computed, not a table of percentages: art/runtime/match-layout.js (matchLayout) is the implementation, and the values below are its constants, in CSS px.

Band Height Contents
Safe area top device Notch / dynamic island. Never draw here
Top row 40 Timer, round pips (single player: point totals), menu button
Headroom 0.3 tile Top-row Chuggers' heads and ears draw over the frame into this gap
Playfield 11.5 tiles The 11×13 grid with the outer wall ring cropped to a quarter tile: 9.5 × 11.5 tiles visible
Control zone the rest, ≥ 156 Joystick origin area left, DROP right, the timed-effect chip between them
Safe area bottom device Home indicator. Never place a button here

Playfield sizing. tile = floor(min((width − 12) / 9.5, (usable height − 40 − 156) / 11.8)). Tall phones are width-bound: on the reference phone the tile is 39 CSS px (117 device px), 22% larger than the v0.5 budget's 32, and the spare height lands in the control zone. Squarer screens (16:10 foldables, tablets in portrait) are height-bound, and gain the most (36 → 52 CSS px on a 540×864 screen). Never stretch tiles.

The frame. Only a quarter of the outer wall ring shows, inside a 3 px Cola Brown keyline with a 4 px cel drop edge and rounded corners (clipFrame in art/runtime/arena.js). Floor, residue, pickups, markers and blocks are clipped to it; what stands up (cans, Chuggers, status, blast VFX, debris) is not, so a top-row head overlaps the frame instead of being cut off. There is no floor_edge art and no arena backdrop behind the match: the background is the Home screen's plain slate gradient, so nothing competes with the board.

Four rules this budget exists to enforce:

  1. 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
  2. The HUD takes no height from the field. Anything new that wants to be on screen during play goes in the top row, in the control zone or on the field in world space. It never adds a band
  3. 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
  4. The outer wall stays visible as a quarter-tile strip. Players need to see where the board ends; cropping it away completely gains only about 8% more tile size

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. It is the only button in the cluster — the kick is automatic. Gameplay Rules §9 owns the trigger - Cans ready as a badge on DROP: the number the player can place right now. It is the only count in the HUD (Gameplay Rules §8.2); there are no Shaker / Extra Can / Ice Cube counters - Active timed effect chips (Coffee Bean / Honeycomb Wax / Glue) with drain rings, centred above the joystick and DROP. A chip is a depleting fill, not a counter — Gameplay Rules §8.3 says why, and this section owns the widget

No pause in multiplayer. A menu button opens a slim overlay: forfeit, settings, mute — the simulation continues underneath.

5.9 Round Results

Between rounds. Fast — 5 seconds, auto-advance, skippable when everyone taps.

  • Round winner callout
  • Score pips updated; in single player, each player's points this round (+4, +2, +1, +0) and their running total, and a drawn round reads "Draw: points shared" (Gameplay Rules §11.3)
  • 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.

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:

  1. It never blocks anything. A failed league_get shows an inline retry and nothing else breaks. Home loads, Play works
  2. 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

Multiplayer is never gated behind registration. 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:

  1. 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
  2. Cleartext on 7350 will not pass App Store review. iOS App Transport Security blocks non-TLS connections, and ws:// fails the same check; Android has blocked cleartext by default since API 28. A TLS-terminating reverse proxy must sit in front of Nakama, with a real certificate on a real hostname, before a submission candidate is built. Retrofitting this after the client is written means touching every connection path.

There are two hostnames — prod.fizz-frenzy.com and dev.fizz-frenzy.com. TLS terminates once, in a reverse proxy in front of Nakama, which routes each hostname to its own Nakama VM; Nakama itself runs unmodified and holds no certificate. Both names carry a Let's Encrypt certificate, both environments connect on 443, and 7349, 7350 and 7351 are closed to the internet.

Routing by hostname rather than by address is what makes the dev/prod split a proxy-side change: a second environment is a second name on the same public address, not a second client build. Both names resolve to the same address, and the client cannot tell.

The hostname matters beyond the certificate: the host ships inside the client in a Resources ScriptableObject (§11), so a bare IP would pin the server to one address for the life of every installed build. A name is the indirection that lets the server move without an app update.

  1. The admin console on 7351 is not a public surface. It is administrative, weakly authenticated by default, and must never be reachable from the internet or proxied to a hostname. Reach it over an SSH tunnel. The only ports published to the internet are 80 and 443 — the proxy reaches Nakama across the private bridge, and 7349, 7350 and 7351 stay inside it

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} — opcode 4, §7.3. 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}
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.

Opcode 4 carries no kick bit. The kick is a consequence of walking into a can while holding an Ice Cube, which the server already knows from dir; a bit for it would be a second place the trigger could be decided, and a way for a client to claim a kick the simulation would not produce.

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 S glyphs 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

One consequence for the loader: the board is fully deterministic from the file, in layout and in appearance, and needs no shared randomness to build. The match seed covers only the drop table. 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-53s. Closing walls, or the Beach Kiosk's tide from the style guide — either way, the field closes inward one block at a time (Gameplay Rules §12). 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. Single player plays every round of the 1, 3 or 5 and scores by placement (Gameplay Rules §11.3)
  • Round timer 120s, then sudden death at 67s
  • Round ends when one Chugger remains, or all remaining die simultaneously (draw — no point awarded in round-wins matches)
  • 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:

  1. 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
  2. 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. It is the only button in the cluster.

There is no KICK button. The kick fires automatically on walking into a can while holding an Ice Cube, and Gameplay Rules §9 owns the trigger. The cost lands on the player, not on the layout: an Ice Cube permanently removes the ability to back up against a can, because there is no way to decline a kick.

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 T and everyone knows it bursts at T + 30 ticks. 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_can animation 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 3 ticks / 0.2s The only lethal period. Ticks are canonical
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. A fixed set of 8–12 canned phrases and emotes, and no free text anywhere in the app. It is what makes the 13+ age rating (§14) 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 .map is 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 §10.2
  • Multiplayer pack rule: every player in a match needs the arena's assets, so the pack downloads for all players at Lobby. Packs are 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
  • The Nakama dev instance is separate from the production one — two VMs behind the shared reverse proxy, reached as dev.fizz-frenzy.com and prod.fizz-frenzy.com. Match handler changes take a server restart, and this is what keeps that restart off the instance a playtest is running against
  • Build config: dev / staging / prod, differing in server host, log level and cheat menu availability. Staging is currently folded into dev; a third environment is a third site block on the proxy and a third VM, not a client change. The cheat menu (spawn pickup, set range, kill player, skip round) pays for itself in the first week of tuning

12.1 Frame rate — 60, set explicitly

The application sets Application.targetFrameRate = 60 at startup, and clears QualitySettings.vSyncCount so that it governs. Both live in AppRoot, which runs once in the Bootstrap scene.

It must be assigned rather than inherited. Unity's mobile default is 30, and a build that never sets the field simply runs at it. A frame-time figure gathered against an unset target measures the vsync cap, not the game, so it cannot answer the question §12.2 asks.

Why 60 and not 30. The simulation is tick-locked at 15 Hz regardless (§8.8), so the render rate buys no simulation fidelity at all — what it buys is input-to-photon latency, which at 60 is roughly half what it is at 30. §4.2 calls corner assist the single most important feel mechanic in the genre, and it is exactly the mechanic that reads as unresponsive when the display lags the thumb. A match is 4–7 minutes, which makes the battery argument for 30 weak.

No platform branch. The same target applies to the Windows dev build, which would otherwise free-run at the monitor's refresh rate while the phone ran at 60 — the dev-build-diverges-from-device split this section exists to prevent.

12.2 Performance measurement

The platform floor is AndroidMinSdkVersion 34 (Android 14), set in Project Settings — nothing older than API 34 is a target for this build. The hardware floor is not yet defined. §5.8.1 rule 3 decides screen geometry — tile size never below 80 logical px — but says nothing about a GPU, and no minimum-spec device is named.

Until one is, treat any frame-time number as provisional and record the device model alongside it. The dev overlay does this automatically.

Naming and measuring the device belongs to implementation-plan M5, when art and VFX land — the first point at which a frame budget is a real constraint. Two obligations:

  • Name the device, and acquire one. Naming has to happen first; the geometric floor is not a device.
  • Measure with art on screen. Greybox measurements on a flagship confirm only that the frame rate is set and being met. They say nothing about headroom and must not be cited as evidence that the game performs.

The risk being carried is that the named device turns out to be too slow for the art direction, which would cost a rendering rework.

A percentile is not a drop detector. The overlay reports p50, p90 and p99 over a 600-frame window, which discards the worst six frames — and a dropped frame, worth ~33 ms against a 16.7 ms budget, is exactly what lands there. On hardware that comfortably holds the target this is academic, because the distribution is flat. On minimum-spec hardware it is the entire question, and a clean-looking p99 can sit above a hitch a player would see. Any frame-time instrument used to qualify a device must report a maximum, not percentiles alone.


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. Age Rating & Compliance

The store listing targets 13+. 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 what 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 the rating is re-examined first
  • No COPPA verifiable-consent path and no Google Play Designed for Families track are built
  • No gambling mechanics — no loot boxes, no gacha, no paid Surprise Cubes, ever (Economy & Progression §1.3). That holds whatever the rating is

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.


15. Open Questions

  1. 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.
  2. Region routing. One server in Germany means playable EU latency, poor US, bad APAC. Fine for soft launch, needs a plan before wide release.
  3. IAP validation. 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. A separate service is a second host and a second certificate, so this is owned by implementation-plan M0 alongside the TLS hostnames.
  4. 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.

16. Cross-Document Consistency

Five parts, one project. This section says who owns what, so that when two parts disagree there is a rule rather than an argument, and records the dependencies that are invisible from inside a single part.

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 Gameplay Rules, which supersedes §8 wherever they overlap.

16.2 Dependencies and scope limits

1. The Reinforced tier is not built in version 1. Style Guide §7 describes a two-hit tier — ice blocks in the Diner, ice buckets at the Beach Kiosk — but it has no slot in the arena manifest (Asset Architecture §5.1) and no glyph in the map format (Asset Architecture §A.5). Anything dressed as reinforced ships as an ordinary destructible; R is reserved for format 2.

2. Version 1 hazards are whatever H can express — a tile that is periodically lethal on a fixed rhythm (Gameplay Rules §12.1). That covers the Diner's soda fountain. The Beach Kiosk tide is the sudden-death ring, not a hazard tile. Teleporters (T, reserved), ricochet and persistent floor states such as syrup puddles need map format 2.

3. Two netcode consequences of the simulation rules:

  • Blasts destroy uncollected pickups — a pickup_destroyed case on the EVENT opcode, distinct from pickup_taken
  • The live-blast tile set crosses tick boundaries (Gameplay Rules §9.1, §13) — it is the only simulation state that does, and it must be included in STATE_FULL or a reconnecting client can walk a can through a blast the server knows about and it does not

4. Round points are 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. Tuning the round structure tunes the ladder — changing rounds-per-match, or the draw rule in Gameplay Rules §11.1, changes the rate at which the entire player base climbs.


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 except Tin. 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 in league_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_END fires. 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.

  1. Freeze at 22:00:00. Close point writes to the outgoing week
  2. Snapshot every club at every tier, resolved to final rank order (§17.6)
  3. Resolve movements for all clubs — promote / hold / demote — against the snapshot only
  4. Collect each tier's incoming population: holds, plus promotions from below, plus demotions from above
  5. Deal each tier into clubs of 50 (§17.4)
  6. Write new placements, zero the scores, open the new week
  7. 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:

  1. League points, descending
  2. Earliest timestamp at which the player reached that point total — first to arrive holds the higher rank
  3. 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_late in 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. Single player therefore never feeds the league; what it pays in XP and currency is Economy & Progression §1.2.

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 INPUT message, 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_cut is 15; the same client renders a Gold club with promote_cut 10 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_at against 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

  1. The rollover job does not run. Players see a countdown at zero and a frozen standing. The client must render closes_at in the past as "Results are being calculated", not as a negative number or a stuck timer. Alert on the job, not on the symptom
  2. 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
  3. 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
  4. Two devices, one account, simultaneous matches. The incr operator 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

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.

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 every UI element referencing them, and on the field as the player marker under the Chugger's feet (Framework & Flow §5.8). 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:

  1. Gold is not Sunbeam. Sunbeam #FFC42E means 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
  2. 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
  3. 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
  4. 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, and a small species-appropriate muzzle and 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 Colouring Silhouette hook
Rax Raccoon Frenzy Red fur, Cola Brown mask, Fizz Cream belly Mask, broad ringed tail — the mascot
Puff Bear Sunbeam yellow fur, Fizz Cream muzzle Small round ears, broad head, cuddly round body — the roundest, stockiest silhouette
Nim Squirrel Warm chestnut #9A552E fur, cream belly Enormous bushy upright curling tail, clearly separated from the torso
Bolt Bunny Bottle Teal fur, Slate and Chrome steel-grey accents Long upright ears — the tallest silhouette; tiny round tail, powerful rounded hind feet

Species anatomy is literal: each Chugger gets its species' muzzle, nose, ears, paws and tail. Puff carries no pufferfish traits (no spikes, fins or inflate-when-panicked), and Bolt no lizard traits (no scales, dorsal plates, claws, low reptile posture or 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:

  1. 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
  2. Armed (final 0.8s) — visibly swelling, seams glowing, pulse accelerating, hissing louder
  3. 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 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 85% tile, one boxy parcel shape per arena; families differ only by a printed pictogram 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 - Kraft cardboard parcels sealed with Frenzy Red tape, one shape for all four families. The family shows as a pictogram printed on the front: cola bottle, burger, fries, pie. (Changed 2026-09-25: the v0.5 mix of 12-packs, cup towers, napkin dispensers and pie cases was noisy at game size and players did not read it as blocks. A uniform box reads as the grid; the pictogram keeps the Diner's flavour.)

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 fixed rhythm (map glyph H). The soda fountain qualifies. Teleporters, ricochet and persistent floor states such as syrup puddles 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 — vents a low cloud of scalding steam (about one tile high) on a fixed timer, telegraphed by rising wisps, a gurgle and the grate's warm glow. Steam, never foam: it must not resemble the can blast (changed 2026-09-25: the v0.5 foam column, 2.5 tiles tall, was mistaken for a can going off). The warm hazard hue lives in the glow and the underside of the lowest puffs; the cloud itself is pale

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 - Sun-bleached cardboard parcels sealed with Bottle Teal tape, a rope carry handle on top; one shape for all four families, pictograms ice cream, shell, sunglasses, beach ball. (Changed 2026-09-25, as for the Diner §8.3: the v0.5 crates, sandcastles, parasols and cooler boxes read as scenery, not blocks.)

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 — the sudden-death ring, reskinned: the arena floods inward, shrinking it and forcing endgame resolution (Gameplay Rules §12) - Seagulls — ambient dressing only, off the play field. They never touch a tile or a pickup (Gameplay Rules §12.2) - Cola outflow pipe — the Beach's H tile: a buried pipe mouth that vents the same low steam cloud as the Diner's fountain (§8.3)

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
  • The foam fills its tiles. Arms are about 82% of the lane wide, the core fills the origin tile (and carries the lanes under it, so the joins never show a cut edge), the arm ends are rounded caps, and the foam is warmed toward Sunbeam from the rim inward so a thick lane still reads warm at 32px (changed 2026-09-25: the v0.5 arms covered 43% of the lane and read thin)
  • Frames 5–8: foam collapses, bubbles pop individually, residue fades
  • Total duration 0.4s; damage window is the first 0.2s 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:

  • Top row: round timer, round pips (point totals in single player) and the menu button. No player roster: players are recognised by their Chugger and the colour-and-shape marker under its feet
  • Play field: the outer wall cropped to a quarter tile, inside a Cola Brown keyline, never stretched. Behind it the plain Home slate, no arena backdrop
  • No pickup strip. Power-ups show their effect in play; the only count is cans ready, as a badge on DROP
  • Control zone: joystick origin area left, DROP right, the timed-effect chip between them. It takes all the spare height of tall phones — the kick is automatic, triggered by walking into a can, so DROP is the only button in the cluster. 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.

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.

Part 4 — Visual Assets & Design Architecture

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.


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.

There is no per-axis manifest schema file. The required slot lists are §5.1–§5.4, and their single coded copy is SlotTable in FizzFrenzy.Maps — a schema file per axis would be a second statement of the same list, free to fall silently behind the first, which is the drift Pack Studio T1 exists to prevent.

Content/
├── arenas/
│   ├── 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/
│   ├── _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/
│   ├── classic/                   # default, free, shipped in base app
│   └── skins/
│       ├── blue_raspberry/
│       ├── lime/
│       └── grape/
│
├── flavours/                      # reserved axis — only classic at launch
│   ├── 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 — the exact rule is §3.1, and it is what lets a tool resolve a glyph to a file without a lookup table.

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.

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

Two conventions, and the file's location decides which applies. Inside a pack's art/ directory a file is addressed by slot; everywhere else it is addressed by description.

3.1 Pack art — the filename is the slot

For every file in an art/@1x/, art/@2x/ or art/@3x/ directory under Content/:

{slot}[_{variant}][_{direction}][_{frame}].png
Field Rule
{slot} A slot name from the §5 manifests, verbatim
{variant} Two digits. Present only where the slot's count is greater than 1
{direction} n, s or e. Present only where the slot is directional
{frame} Two digits. Present only where the slot is animated

Lowercase, underscores only. No axis, set, pack or skin prefix — the directory path already carries all four.

File in Content/arenas/diner/art/@1x/ Slot
backdrop_far.png backdrop_far, count 1 — no index
floor_tile_01.png floor_tile[1] of 3
solid_block_00.png solid_block[0] of 3 — what the % glyph pins to
solid_cap_03.png solid_cap[3] of 4
destructible_break_02_04.png destructible_break, family C, frame 4
File in Content/characters/rax/base/art/@1x/ Slot
portrait.png portrait, count 1
run_e_04.png run, facing east, frame 4
hit_pop_07.png hit_pop, frame 7 — not directional

Why there is no prefix. §2.1 forbids category subdirectories inside art/ because a directory "would encode the same fact twice and give a filename two places to disagree with its slot". A pack prefix inside that pack's own directory is the identical duplication one level up: Content/arenas/diner/art/@1x/arena_diner_solid_counter_a.png names the Diner twice and describes the slot in a word — counter — that no tool can derive from solid_block[0].

The consequence that makes this load-bearing: because the name is derivable, every tool resolves a glyph to a file by construction, with no lookup table. pack.json therefore carries import settings and defaults (Pack Studio T7) and no slot→file map. A pack cannot have a file whose name disagrees with its slot, because the name is the slot.

3.2 Core, and working files — the descriptive convention

Core lives inside the engine project (§2.2), fills no slot manifest, and its tree is organised by function rather than by variant axis. It needs the descriptive form — and so do _source/ working files and the pre-final output of the Asset Generation Brief, which is explicitly renamed to real slot names by the export step.

{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
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
core_hud_fuse_ring_08 HUD fuse ring, frame 8
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 — in both conventions.


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.1 slot-derived name 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_world and core_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. Not drawn behind the match since the lean HUD; kept for the tablet/landscape variant and menus
backdrop_near 1 Parallax layer, max 8px travel. Same status as backdrop_far
floor_tile 3 Randomised across the grid to break repetition
floor_edge 1 9-slice, frames the play field in menus and previews; not drawn in the match since the lean HUD (Framework & Flow §5.8.1). The insets live in SlotTable, not in pack.json — they are per-slot geometry like pivot, and a pack able to choose its own insets for floor_edge would break the format's claim
floor_decal 4 Painted-on dressing, never occludes. Placed by the map's decal grid (§A.3), glyphs a–d
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 1–4 Particle sheet per family. A family without its own sheet uses [0]; the parcel packs ship only [0] (cardboard, tape)
ambient_static 6 Backdrop or floor only. Never on a playable tile, so no map glyph places it
ambient_animated 2 × 8 frames Index 0 is the hazard tile — the H glyph, walkable floor that periodically vents steam (§A.5; Design Style Guide §8.3). Index 1 is backdrop scenery — jukebox glow, gull, ceiling fan — pack-placed, never on a playable tile. Eight frames, matching every other animated slot in this document — idle, run, burst_core, hit_pop and cube_burst are all 8 — looping in 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 (§10.2). Three is a floor, not a target — with no procedural fill, 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 78 files per scale + 3 maps Diner and Beach Kiosk at launch. 78 is files, not slots — ambient_animated alone is 16

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 × 3 Tip, one per authored direction — n, s, e. West is mirrored from east at runtime and is never authored (§3.2), so four are rendered and three are drawn. Total: 8+8+8+24+4+1+1 = 54
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:

  1. 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.
  2. 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.
  3. The core fill is free. Interior colour, particles, residue decal, droplet shape — all sellable, all expressive, none of it load-bearing.
  4. 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:

  1. 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.
  2. 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.
  3. 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.

It is a sound fairness contract. 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. Not counted in the HUD (Gameplay Rules §8.2). Drop visibly from destructibles.

Power-up Effect Cap Silhouette Signature motion Frames
Shaker +1 soda blast range 6 Steel bar shaker, tilted ~25° mid-shake: tapered cup, cone top, red cap, red band, fizz spray 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. It also rules out any Coffee Bean / Honeycomb Wax tile collision, 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 the collision that matters most in the pickup set.
  • 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_break runs its 6 frames, then spawn_burst fires. 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_break plus 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_burst therefore 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 silhouette: the can stands upright, the Shaker is a tapered bar shaker tilted about 25° with a cone top and cap. At 32px that survives; a texture difference does not. (Changed 2026-09-25: the v0.5 wide canister kept the can apart but players did not read it as a shaker; a bartender's shaker names the pickup at a glance.)

The name Shaker is unambiguous. There is no charge, no shake input and no meter anywhere in the HUD, so nothing else in the vocabulary competes with it. The can_armed_pulse frames run on a fixed clock.

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 the ordinary fx_generic/pickup_pop; there is no capped variant. A cube that rolls a capped Class A effect plays that same ordinary pop after the reveal. Gameplay Rules §8.2 carries the reasoning.

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. 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_speedlines attaches to the can and stays cool; status_speed attaches 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_reveal is 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 in core/status/ and it plays at full world scale, never HUD scale.

6.4 HUD & UI

Group Assets Notes
hud/avatar_frame — Cut with the lean HUD (no roster). Menus use the CSS .avatar
hud/shape_badge 4 Circle, square, triangle, star — colourblind identity
hud/lives 3 states Vestigial — there is no lives system (Gameplay Rules §7). Cut, or repurpose as round pips
hud/timer 1 + tabular numerals
hud/powerup_pip — Cut with the lean HUD (no Class A counters). The pickup icons remain for How to Play
hud/timed_band 1 + fill Depleting ring, 5s or 3s, in the control zone
hud/timed_icon 3 Coffee Bean, Honeycomb Wax, Glue at band scale
hud/opponent_state — Cut with the lean HUD. A glued opponent shows the status overlay on their Chugger, which is where the eye already is
hud/player_marker 4 Colour + shape under each Chugger's feet; drawn in code (worldkit.marker), no art
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 (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 1 frame + 1 fill 9-slice + fill Match Results and Profile. Two files: a 9-slice frame and a fill — one bar in two pieces. Insets live in SlotTable, as for floor_edge
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/store chrome carrying the one sugar_cube icon. 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 from type/; 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. Maps and Probability Tables

10.1 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.

10.2 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:

  1. 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
  2. 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
  3. Clients fetch missing maps at Lobby. A .map file 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

10.3 Duel maps — capacity is a ceiling

Most v1 maps declare players: 4 and carry 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: spawn count equals players, and a match may have fewer players than the map's capacity, never more.

A map is therefore offered only to matches at or below its capacity. A 4-spawn map takes 2, 3 or 4 players; diner_booth_standoff, at capacity 2, takes only 2 and is never selectable for a four-player match. MapLoader enforces this as a load-time assertion.

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 are the format's duel support.


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 ~ are reserved.

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. There is no separate void marker — see §A.9.
  • Rendering is deterministic, not merely layout. Two clients, a preview tool and a screenshot taken a year apart all draw the identical arena from the identical file. Which variant of a slot each tile uses is a pure function of the tile's position (§A.5), never of a match seed. This is the same principle as literal glyphs applied one level down, and it is what makes an offline preview trustworthy enough to tune art against.

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
---
<terrain rows>          ; 13 rows of 11, required
---
<decal rows>            ; 13 rows of 11, optional — omit entirely for none

Everything before the first --- is the header. What follows is the terrain grid, one row per line, top row first — this is the board, and it is required.

An optional second --- introduces the decal grid, the same 11×13 shape, addressing the same tiles. It carries purely cosmetic floor dressing (§A.5) and affects no rule of play: nothing in it collides, blocks a blast, or changes a validation outcome other than its own.

Two blocks, because one tile carries two facts. A floor tile has a terrain identity and may carry a decal, and a single character cannot express both without a combinatorial glyph set. Two aligned grids keep one character per tile in each, which preserves the property §A.2 exists to defend: you can read the arena, and now its dressing, by eye, and check either for symmetry by folding the page.

A file with no decal block is valid and complete, and means no decals. That is the common case; do not write an all-. block to say nothing.

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. Most 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 Free-form labels for rotation and matchmaking filters. duel, chaotic, tight, open, service, hazard are examples, not a closed vocabulary — see below

There are no procedural keys. Every tile is authored.

The tags list is illustrative and the validator enforces no vocabulary, because a matchmaking filter is exactly the thing that wants a new label without a format revision, and a closed vocabulary would make adding one a format bump. The cost is that a typo is a silently useless tag rather than a validation error; that is worth less than the revision it would otherwise cost, and a pack lint can catch it without touching the format.

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, variant derived from position solid_block[hash(x,y) % 3]
% 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 derivation below distributes variants without visible repetition, and it does a better job of it 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.

Variant derivation — the exact function

Wherever a glyph names a slot with more than one variant and does not pin one, the index is a pure function of the tile's position. No seed, no match state, no device entropy.

variant(x, y, count):
    h = (x * 73856093) XOR (y * 19349663)     ; 32-bit, wrapping
    h = h XOR (h >>> 13)
    h = h * 1274126177                        ; 32-bit, wrapping
    h = h XOR (h >>> 16)
    return (h AND 0x7FFFFFFF) MOD count

Applied to # → solid_block (3), and to every walkable tile → floor_tile (3). solid_cap is not derived this way — cap selection is a function of which neighbours are solid, which is already deterministic.

These constants are frozen. They are not a tuning parameter and not an implementation detail: changing one silently re-dresses every map in the game, and the first symptom is a screenshot that no longer matches a shipped build. A change here is a format change.

Why a hash rather than authoring. Determinism and authorship are separable, and only determinism is load-bearing. Floor is roughly a hundred of the hundred and forty-three tiles and is the quietest thing on screen; making a designer choose a variant for each would add noise to the file for a decision nobody would make deliberately. Solids get both — derived by default, pinnable by exception.

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. Renders floor_tile[hash(x,y) % 3]
S Player spawn. Resolves to empty floor. Assigned in reading order, top-left first
H Arena hazard tile. Resolves to ambient_animated[0]. Walkable floor, periodically lethal — Gameplay Rules §12.1. Pack-specific: the Diner's soda fountain

H is always passable. A tile with collision could be neither stood on nor hold a can, which would make the hazard unreachable — a solid block with an animation playing on it. Passable and periodically lethal keeps collision, §A.7 connectivity and blast propagation (Gameplay Rules §6.2) all static: nothing about the board varies with the phase, and only damage does. The alternative — walkable when dormant, solid when active — is rejected on cost, because it makes walkability time-varying and every consumer of it, from §A.7 to corner assist to the player AI, would need a phase-aware query for one glyph.

On the second ambient_animated. The slot has a count of two (Part 4 §5.1) and only one of them is a hazard. ambient_animated[1] is backdrop dressing, placed by the pack and never by the map — the jukebox glow, the gull, the ceiling fan. It has no glyph because it never occupies a playfield tile, which is the same reason ambient_static has none. Index 0 is the hazard; index 1 is scenery. Both are still deterministic, because neither is chosen.

Decal glyphs — second grid block only

These are legal only in the optional decal block (§A.3) and are a parse error in the terrain grid.

Glyph Meaning Manifest slot
. No decal on this tile
a Floor decal A floor_decal[0]
b Floor decal B floor_decal[1]
c Floor decal C floor_decal[2]
d Floor decal D floor_decal[3]

Decals are painted onto the floor and never occlude, never collide, and never affect a rule of play (Part 4 §5.1). A decal may therefore only sit over a tile that is walkable in the terrain grid — see §A.7.21. Unlike floor and solid variants, decals are authored rather than derived, because a decal is a composition decision about a specific spot and a hash cannot make one.

Reserved and unused: 5–9, 0, $, X, T, G, R, ?, ~, A–F and all other characters are reserved for format version 2, in both blocks. 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.
; Decal block: spilled-soda ring at the crossing, tile scuffs on the approaches.
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#
###########
---
...........
..c.....c..
...........
....b.b....
...........
...........
.....a.....
...........
...........
....b.b....
...........
..c.....c..
...........

Note how fast the structure reads — the lattice of # is visible as a pattern, and the digits stand out as the things that will disappear during a round.

Read the decal block against the terrain block by eye. Every mark sits on a tile that is . in the grid above it, and the layer folds along both axes exactly as symmetry: mirror_both promises. Both facts are checkable without running anything, which is the entire argument for a second aligned grid over a coordinate list in the header.

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 sun-bleached parcels with teal tape and beach pictograms instead of red-taped Diner parcels.

A duel map

Capacity 2 (§10.3). 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. It carries no decal block at all, which is legal, complete and the common case — an absent block means no decals, and an all-. block written to say the same thing is noise.

; 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 (§10.2). All are hard failures unless marked advisory.

The two passability predicates, and which rule uses which

Every rule below that asks "can something be here?" asks it in one of exactly two ways. Which one each rule uses is load-bearing, because H is passable floor (§A.5) and the two predicates disagree about it:

Predicate True for Used by
walkable . and S only rules 9, 16a
open — "not solid" everything except #, %, @, &: floor, spawns, destructibles and hazards rules 12, 13, 16

Rules 12, 13 and 16 evaluate the board with destructibles removed, so they go through the not-solid test, which admits H for free.

Rule 9 keeps the stricter test deliberately. A spawn's escape tiles must be tiles a player can wait on, and a periodically lethal tile is not a guaranteed escape. Letting H count would let an author ring a spawn with fountains and pass validation. Rule 16a keeps it for a different reason: a decal is drawn on the floor, and H already draws ambient_animated[0] on that tile.

Structural

  1. Header parses; format is a known version
  2. Every row is the same length
  3. Grid dimensions equal size, and size is exactly 11x13
  4. Only glyphs from §A.5 appear, and only those legal for the block they appear in — terrain glyphs in the terrain grid, decal glyphs in the decal grid, neither in the other 4a. drop_weights is present and parses as five non-negative integers, not all zero 4b. The decal block, if present, is exactly 13 rows of 11 columns — the same shape as the terrain grid it addresses 4c. At most one decal block. A third --- is a parse error
  5. pack matches the containing directory, and pack/name is unique across the entire map namespace, bundled and server-side together (§10.2)
  6. 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

  1. 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

  1. Spawn count equals players. players is spawn capacity — a match may seat fewer, never more (§10.3)
  2. Every spawn has an escape from its own first can at the starting blast range of 1 (Gameplay Rules §2): an orthogonally adjacent empty tile, and beyond it one more empty tile that is either around the corner (a step perpendicular to the first, diagonal to the spawn) or straight on (two tiles out in the same direction). Either tile is outside the can's range-1 cross, two tiles from the spawn: 0.67s at 3.0 tiles/s against the 2.0s fuse. Put differently, an empty tile two steps from the spawn, reached through an empty neighbour. One such escape is enough; a player who can be trapped by the first can they place has been killed by the level designer. Walkable, not open — a hazard is not an escape. (Changed 2026-09-26: rule 9 required two escaping neighbours. One escape already survives the first can, and the stricter rule rejected playable spawns. If the starting blast range ever changes, the straight escape grows with it.)
  3. Minimum spawn separation: 6 tiles Manhattan
  4. Spawns may sit anywhere. Corners are conventional, not required — fairness is checked by rules 9, 10 and 12, not by position

Playability

  1. 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. Open
  2. 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. Open, at a cost of 1 per destructible crossed and 0 for everything else, hazards included. "Out" is reaching any tile at Manhattan distance 4 or more from the spawn. The alternative reading — cost to reach another spawn — costs 5 on all three shipped maps and would fail this appendix's own §A.6 worked example at that threshold, so distance from the spawn itself is the rule
  3. 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
  4. If symmetry is set, the grid satisfies it exactly, spawns and pinned variants included
  5. Corridor exit rule — every tile has a perpendicular exit within 3 tiles along its corridor. Hard failure above 6. See Gameplay Rules §3.4. Open, because this rule is evaluated with all destructibles removed exactly as rule 12 is — treating destructibles as walls makes §A.6's own worked examples fail, since both ring their floor with destructible families

Decal layer — only if the block is present

16a. Every non-. decal sits over a tile that is walkable in the terrain grid — . or S. A decal over a solid, destructible or hazard tile is a hard failure, because Part 4 §5.1 says a decal never occludes and there is nothing under it to not-occlude. Walkable, not open, and a hazard is excluded here even though it is now floor: H already draws ambient_animated[0] on its tile, so there is something under the decal after all

Rule 16 is the one that matters most. 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.

The three is derived from base speed. Reaching the exit tile is not safety, so the escape is three tiles along plus one through the opening — four tiles, 1.33s at 3.0 tiles/s, leaving 0.67s of margin. Under Honeycomb Wax the same escape takes 2.22s, past the fuse. Gameplay Rules §3.4 carries the full table and the decision that Wax plus maximum blast range is meant to be lethal without a near corner, and an author using the 4–6 advisory band should read it — a 6-tile corridor is fatal at base speed with no debuff at all.

Advisory — warnings, not failures

  1. Corridor exits between 4 and 6 tiles apart. Legitimate for a deliberate chokepoint; flag so it is a decision rather than an accident
  2. A 4-player map should be symmetric. Asymmetry is a legitimate choice for party modes and a serious problem for anything ranked
  3. Pinned solids under 20% of all solids. Above that, flag for review — see §A.5
  4. Solid blocks under 35% of interior tiles. Denser than that and the field is corridors rather than a grid 20a. symmetry is set and the decal block does not satisfy it, while the terrain grid does. Deliberate asymmetric dressing on a symmetric arena is a legitimate art choice — a scuff in one corner and not its mirror — so this is flagged rather than failed. Rule 15 governs the terrain grid and is unaffected

A.8 Load Sequence

parse header
  ↓
parse terrain grid, then the decal grid if a second --- follows
  ↓
validate (§A.7) — reject the map, never repair it
  ↓
seed RNG from match seed + map name    # the drop table, and nothing else
  ↓
resolve S → floor, register spawn points in reading order
  ↓
resolve H → ambient_animated[0], walkable floor
  ↓
resolve # → solid_block[variant(x, y, 3)];  %, @, & keep their pinned variant
  ↓
assign solid_cap edges by neighbour rules
  ↓
assign floor_tile[variant(x, y, 3)] to every walkable tile
  ↓
overlay decals from the decal grid, if present
  ↓
hand off to renderer

The RNG seed covers the drop table and nothing else. The board is deterministic from the file in both layout and appearance. Variant picks are a pure function of position (§A.5) rather than of the match seed, so every client, every replay, every preview tool and every screenshot draws the identical arena. Seed the drop table 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.

Why appearance determinism is worth a rule. Seeded cosmetics would mean a map had no canonical look, which makes an offline preview an approximation and art tuning a guess — you would grade a palette against one arrangement and ship another. It would also make a bug report's screenshot unreproducible.

A.9 A Note on Voids

Format 1 has no void — no tile without floor, and so no non-rectangular arena. . 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 four
  • X — void, per §A.9
  • T — teleport pair
  • G — guaranteed drop, for tutorial and scripted maps
  • A–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 revision
  • R — 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

Appendix B — Asset Encyclopedia

Every shippable art asset in version 1, in two flat tables. This is a reading aid, not a manifest — §5 binds the packs, §6 binds Core, and they win any disagreement with this appendix.

Directories. Core/… below means Assets/Game/Art/Core/…, exported once at @3x (§4). All other paths hang off Content/, and every art/ directory is three sibling resolutions — @1x/, @2x/, @3x/ — carrying the same filename in each (§4). Launch fills: {pack} = diner, beach_kiosk · {chugger} = rax, puff, nim, bolt · {skin} and {flavour} = the free defaults (base, classic).

Frames. Shipped frames per animation. — means static, a particle sheet, or a count this document does not fix. West mirrors east at runtime and is never authored (§3.2); directional counts below are authored directions.

B.1 Core

Slot Asset Directory Frames
can_idle Can, idle — held and placed reference Core/can/ 4
can_armed_pulse Can, armed pulse — Frenzy Red → Sunbeam, fixed 2.0s curve Core/can/ 12
can_slide Can, sliding (kicked) Core/can/ 6
can_bounce Can, impact bounce Core/can/ 4
can_settle Can, slide settling to rest Core/can/ 4
shaker Shaker — Class A pickup, +1 blast range, cap 6 Core/powerups/ 12
ice_cube Ice Cube — Class A pickup, unlocks kick, cap 3 Core/powerups/ 16
extra_can Extra Can — Class A pickup, +1 simultaneous can, cap 5 Core/powerups/ 12
pu_cube_idle Surprise Cube, idle — ? faces the camera every frame Core/powerups/ 16
pu_cube_open Surprise Cube, open — shell splits, contents not yet legible Core/powerups/ 8
pu_cube_reveal_good Surprise Cube, buff reveal — rising, saturated, sharp Core/powerups/ 10
pu_cube_reveal_bad Surprise Cube, debuff reveal — sagging, drab, viscous Core/powerups/ 10
reveal_icon Effect reveal icons — one per effect, burst centre and above the player Core/powerups/ 6 icons
coffee_bean Coffee Bean — Class B reveal icon and HUD band icon Core/powerups/ 12
honeycomb_wax Honeycomb Wax — Class B reveal icon and HUD band icon Core/powerups/ 14
glue Glue — Class B reveal icon and HUD band icon Core/powerups/ 14
status_kick_ready Kick-ready overlay — frost glint at feet Core/status/ 8
status_frost Frost overlay — Ice Cube stacks 2–3 Core/status/ 2 steps
status_speedlines Can speedlines — sliding at stack 2+, cool Core/status/ 6 ×2 intensities
status_armed Held-can attachment — from Core/can/ Core/status/ —
status_speed Coffee Bean overlay — warm streaks off the player Core/status/ 8
status_slow Honeycomb Wax overlay — amber drips, sagging Core/status/ 8
status_glued Glue overlay — white strings + crossed-out can symbol Core/status/ 8
status_reveal Cube reveal — effect icon above the head, public Core/status/ 10
burst_core Blast mask, origin tile — geometry + timing, skin-independent Core/blast_mask/ 8
arm_h_mid Blast mask, horizontal arm segment Core/blast_mask/ 8
arm_v_mid Blast mask, vertical arm segment Core/blast_mask/ 8
arm_cap Blast mask, arm tip ×4 directions Core/blast_mask/ 8 ×3 dir
residue_decal Blast mask, fading puddle Core/blast_mask/ 4
droplet_sheet Blast mask, particle shapes Core/blast_mask/ —
spawn_burst Pickup/cube arrival burst — shared, plays after debris Core/fx_generic/ 6
pickup_pop Collect burst — rising, saturated, Sunbeam-cored Core/fx_generic/ —
pickup_destroyed Blast destroying a pickup — sagging, drab, downward Core/fx_generic/ 8
dust Dust particles Core/fx_generic/ —
sparkle Sparkle particles Core/fx_generic/ —
avatar_frame HUD avatar frames ×4 — one per player slot Core/hud/ —
shape_badge Colourblind identity badges ×4 — circle, square, triangle, star Core/hud/ —
timer Match timer — numerals from Core/type/ Core/hud/ —
powerup_pip Class A counters ×3 — current and cap (6/5/3) Core/hud/ —
timed_band Timed-effect depleting band — frame + fill Core/hud/ —
timed_icon Timed-effect band icons ×3 Core/hud/ —
opponent_state Opponent debuff indicators ×3 Core/hud/ —
pause Pause control — single player only Core/hud/ —
tile_telegraph Pre-blast tile warning Core/hud/ 4
countdown Round countdown — 3, 2, 1, "GO" Core/hud/ 4
banner Victory / defeat banners ×2 Core/hud/ —
fuse_ring Fuse ring — depleting arc, renders above everything, smooth-interpolated Core/hud/ —
menu_chrome Menu chrome — 9-slice panels, buttons, toggles (~20) Core/ui/ —
store Store chrome — cards, currency, rarity frames, purchase flow (~25) Core/ui/ —
lobby Lobby chrome — slots, ready states, character select (~15) Core/ui/ —
results Results chrome — scoreboard, rewards, progression (~12) Core/ui/ —
leagues_ui Leagues screen chrome — standing row, sticky self row, band dividers, countdown plate, empty and stale states (~14) Core/ui/ —
how_to_play How to Play — panel chrome, page dots, 7 effect icons, 3 can-state icons, diagrams (~18) Core/ui/ —
league_badge Tier badges ×6 — Tin, Bronze, Silver, Gold, Diamond, Elite, 256² Core/leagues/ —
league_badge_chip Tier badge chips ×6, 64² — redraws, not downscales Core/leagues/ —
league_badge_locked Locked badge ×1 — greyed, tinted per tier Core/leagues/ —
league_badge_elite_shimmer Elite shimmer — the one animated badge, never at chip scale Core/leagues/ 12
league_frame Badge plate ×1 — 9-slice, shared across all six tiers Core/leagues/ —
league_band_promote Promotion band — 9-slice, cool and rising Core/leagues/ —
league_band_demote Demotion band — 9-slice, drab and sagging, never the warm hazard band Core/leagues/ —
league_band_divider Cut-line dividers ×2 — one promote, one demote Core/leagues/ —
league_arrow Movement arrows ×3 — up, down, hold, direction in shape Core/leagues/ —
league_stinger_promote Promotion stinger Core/leagues/ 10
league_stinger_demote Demotion stinger — deliberately shorter and quieter Core/leagues/ 8
league_stinger_hold Hold stinger Core/leagues/ 6
sugar_cube Sugar Cube, hero — currency icon, 256² Core/economy/ —
sugar_cube_chip Sugar Cube chip, 64² — redraw, not a downscale Core/economy/ —
cube_burst Reward burst — Match Results and level-up Core/economy/ 8
xp_bar XP bar — 9-slice frame + fill Core/economy/ —
level_badge Level badge frame, 128² — numerals from Core/type/ Core/economy/ —
nunito_extrabold UI and body face — licensed binary, not authored Core/type/ —
baloo_2 UI and body face — licensed binary, not authored Core/type/ —
display_custom Custom display face — headers and logo, authored Core/type/ —
logo_lockups Fizz Frenzy wordmark lockups Core/type/ —
app_icon App icon — can mid-burst on Frenzy Red, reads at 48px Core/store_meta/ —
screenshots Store screenshots — captures of shipped art Core/store_meta/ —
feature_graphic Store feature graphic — composition of shipped art Core/store_meta/ —

B.2 Packs

Slot Asset Directory Frames
Arena pack — ~65 + maps. Launch: diner, beach_kiosk
backdrop_far Far backdrop — static, no parallax arenas/{pack}/art/ —
backdrop_near Near backdrop — parallax, max 8px travel arenas/{pack}/art/ —
floor_tile Floor tiles ×3 — lowest-contrast surface on screen arenas/{pack}/art/ —
floor_edge Field frame — 9-slice arenas/{pack}/art/ —
floor_decal Floor decals ×4 — painted flat, placed by the map's decal grid (glyphs a–d), never occludes arenas/{pack}/art/ —
solid_block Solid blocks ×3 — dark, cold, heavy; full tile, flat top arenas/{pack}/art/ —
solid_cap Solid edge and corner caps ×4 arenas/{pack}/art/ —
destructible Destructible blocks ×4 families — light, warm, cheap; 85% tile; the launch packs build them from one parcel and four pictograms arenas/{pack}/art/ —
destructible_cracked Damaged states ×4 — one per family arenas/{pack}/art/ —
destructible_break Break animations ×4 families arenas/{pack}/art/ 6 ×4 fam
debris_sheet Debris particle sheets ×1–4; a family without its own uses [0] arenas/{pack}/art/ —
ambient_static Static dressing ×6 — backdrop or floor only, never a playable tile arenas/{pack}/art/ —
ambient_animated Animated dressing ×2 — [0] H hazard tile, walkable floor; [1] off-field scenery arenas/{pack}/art/ 8 frames each
grade_profile Lighting/LUT profile — config, not an image arenas/{pack}/ —
meta_card Store thumbnail, 512² arenas/{pack}/art/ —
meta_loading Loading art — full-bleed arenas/{pack}/art/ —
Character skin — ~112. Launch: rax/base, puff/base, nim/base, bolt/base
idle Idle loop characters/{chugger}/{skin}/art/ 8 ×3 dir
run Run cycle — leaning into travel characters/{chugger}/{skin}/art/ 8 ×3 dir
place_can Place-can — wind-up, shake, throw, recovery hop characters/{chugger}/{skin}/art/ 6 ×3 dir
kick Kick — mandatory on every skin ever authored characters/{chugger}/{skin}/art/ 4 ×3 dir
hit_pop Pop — inflate, wobble, deflate with a raspberry; no gore characters/{chugger}/{skin}/art/ 8
victory Victory pose characters/{chugger}/{skin}/art/ 12
defeat Defeat pose characters/{chugger}/{skin}/art/ 8
portrait HUD portrait — head and shoulders characters/{chugger}/{skin}/art/ —
emote_face Emote faces ×4 — face only characters/{chugger}/{skin}/art/ —
store_card Store card, 512² — full body, hero framing characters/{chugger}/{skin}/art/ —
Blast skin — ~54. Launch: classic, shipped in the base app
burst_core Origin tile fill — inside the Core mask blast/{skin}/art/ 8
arm_h_mid Horizontal arm fill blast/{skin}/art/ 8
arm_v_mid Vertical arm fill blast/{skin}/art/ 8
arm_cap Arm tip fill — 4 directions at runtime, west mirrors east blast/{skin}/art/ 8 ×3 dir
residue_decal Residue puddle fill blast/{skin}/art/ 4
droplet_sheet Particle atlas — bubbles, droplets, sugar sparkles blast/{skin}/art/ —
store_card Store card, 512² blast/{skin}/art/ —
Can flavour — ~24. Launch: classic, shipped in the base app
label_idle Label decal, idle — transparent outside the label area flavours/{flavour}/art/ 4
label_armed Label decal, armed — registered to the Core pulse, never replacing it flavours/{flavour}/art/ 12
label_slide Label decal, slide flavours/{flavour}/art/ 6
body_tint Body tint — single-channel mask beneath the label flavours/{flavour}/art/ —
store_card Store card, 512² flavours/{flavour}/art/ —

B.3 Not in these tables

Everything shipped that is not an art asset, for completeness:

  • Maps — plain ASCII .map files, arenas/{pack}/maps/ — data, not art (Appendix A). Bundled baseline of ≥3 per pack; the server adds more, additive only (§10.2)
  • pack.json — per-pack import settings and defaults (§2.1) — manifest data, not art
  • characters/_rig/ — the shared character skeleton and attach points (§2.1) — rig data, not art
  • Audio — music and SFX. Style Guide §13 gives direction (surf rock, a rising fuse hiss, design-for-silence) but no slot list, naming convention or count — the only shipped category without a manifest
  • The greybox pack — scripted placeholder fills for every slot, proving the build pipeline. Obviously provisional; never shipped
  • _source/ — working files, never shipped (§2.1). The Asset Generation Brief's masters land here and are renamed to real slot names at export (§3.2)

Totals. Launch scope — bundle counts, the ~1,064 total and post-launch unit costs: §9.


Part 5 — Economy & Progression

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 §10, 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. 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 sits where Framework & Flow §17.9 puts 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. The validation path — Nakama's IAP hooks or a separate service — is an open question (Framework & Flow §15 question 3); this document consumes whichever answer lands there.

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 what makes the 13+ age rating defensible (Framework & Flow §14) — 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

The weekly rollover publishes a result and pays no Sugar Cubes, no XP, and no items.

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, as Framework & Flow §17's scope boundary assumes.

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_id for 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:

  1. The daily cap of 5 — the absolute ceiling on what abuse can yield, ~50 cubes a day
  2. 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
  3. 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 (Framework & Flow §5.2)
  • 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 a separate service, per Framework & Flow §15 question 3); 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

  1. 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
  2. economy_config is 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
  3. 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
  4. Single-player claim flooding. The daily cap and plausibility check bound it to ~50 cubes a day of cosmetic-only value — §5.1
  5. 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
  6. 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