Skip to content

Bunkers that are actually different from each other (§7) - #211

Merged
0xward merged 1 commit into
mainfrom
claude/nullstate-onchain-text-q6g2a3
Jul 31, 2026
Merged

Bunkers that are actually different from each other (§7)#211
0xward merged 1 commit into
mainfrom
claude/nullstate-onchain-text-q6g2a3

Conversation

@0xward

@0xward 0xward commented Jul 31, 2026

Copy link
Copy Markdown
Owner

§7's last [TARGET] flips to [TODAY]. No smart contract, ABI, or contract address is touched.

What was actually there before

Not "the scaling half-exists already", which is what §7 claimed. Measured with the new npm run measure:acts, which mounts the real engine at every act:

Bunker 5 vs Bunker 1
walkable floor area 1.00×
rooms 0.95×
enemies 0.93×

The last bunker was very slightly emptier than the first.

Floor size came from depth alone, which runs 1–5 inside every bunker, so the act never entered the arithmetic. Worse: actHardMode had one entry, for act 4 — so bunkers 1 through 4 were mechanically identical, since enemy stats also scale on depth. The only thing that changed across those four was which archetypes could roll: variety, not difficulty.

"Which bunker should I raid?" had no mechanical answer to give.

The two axes, now real

RiskactHardMode covers all five acts. Gentle at the start so a new player's first raid is not harder than the campaign taught them; steep at the end so the last bunker earns its T3 shards. Act 4 is unchanged at 2.0/1.3 — those numbers were tuned against the boss one-shotting players, and nothing here reopens that.

TimeACT_SIZE in dungeon.js adds width, height and one room per act. Rooms are what carry enemies and containers, so effort and reward scale together without a second knob.

Bunker Risk Enemy HP Shards Length
1 · Treeline ▰▱▱▱▱ ×1.00 T1
2 · Sunken Field ▰▰▱▱▱ ×1.20 T1 +15%
3 · Frostline ▰▰▰▱▱ ×1.45 T2 +30%
4 · Hollow Market ▰▰▰▰▱ ×1.70 T2 +55%
5 · The Last Light ▰▰▰▰▰ ×2.00 T3 +65%

Measured after: 1.65× the area, 1.55× the enemies. Deliberately modest — a MiniPay session is 3–10 minutes on a mid-range phone, so bunker 1 stays the short option. That is the point of an axis rather than a global difficulty bump.

And the player can see it

A difference nobody is shown is not a choice. The map's bottom bar now carries risk pips, shard tier and length under the bunker's name — on the way to the ENTER button, not somewhere to go looking for. Locked bunkers show it too: knowing Bunker 5 is the only T3 source is a reason to keep going.

Keeping this file honest

lib/constants/bunkers.ts is the same shape as the bunkers block that made game-config.ts a liability — a table describing bunkers, sitting next to the engine that really decides. So it is guarded differently:

npm run test:bunkers mounts the engine, reads its own actHardMode and shard-tier rule, measures floor size per act, and fails if any number in the constants disagrees. The engine stays the source of truth; the constants are a view of it the React shell can import (the engine is a static <script> — rule 1).

A bug this surfaced

apply() in WorldMapHub runs twice — once from the instant localStorage draft, again when the Firestore copy lands, typically a network round trip later, which is exactly while the player is looking at the map. Its clamp (prev > highest ? highest : prev) ran both times.

So tapping a locked bunker to read what it holds snapped the selection back to your own bunker a beat later, with no explanation. The clamp now applies only until the player taps something.

Found by the UI test failing on an assertion I had assumed was about my own markup — I spent three rounds treating it as a test race before dumping the panel HTML and seeing the selection had genuinely reverted.

One presentation fix

Length reads "+65% LONGER" rather than "1.6×": at one decimal place bunker 4 (1.55) and bunker 5 (1.65) both rendered ~1.6×, so two rungs of the ladder looked identical on the one line meant to tell them apart. A test now asserts every note is distinct.

Verification

Check Result
npm run measure:acts (new) the before/after table above
npm run test:bunkers (new) 34/34 — constants checked against the live engine
npm run test:bunkers-ui (new) 15/15 in a real browser, three consecutive runs
npm run test:traversable 0 failures in 100 floors — bigger floors stay walkable
npm run test:seeded 15/15
npm run measure:bunker fragment thresholds unchanged (act 0 is untouched)
npm run test:raid · test:contracts · test:floors · test:inventory · test:streak · test:streak-ui all pass
tsc --noEmit clean
lint 34 errors — unchanged baseline
audit · check:copy · check:market · check:cssvars · check:attribution all pass
build succeeds

Docs: GAME-DESIGN.md §7 rewritten with the measured before/after, §2 marker and the build order updated, and game-mechanics.md's bunker table now carries the real numbers instead of "Beginner / Easy / Medium / Hard".

Still open

How long is one bunker in real play? The sandbox can measure rooms and enemies, not a person's minutes. If a full 5-floor clear of Bunker 5 runs past ~10 minutes, a "raid one floor and extract" option is the next thing to add — that is the shape a MiniPay session actually has. Left as a stated open question in §7 rather than guessed at.


Generated by Claude Code

WHAT WAS ACTUALLY THERE. Not "the scaling half-exists already", which is
what §7 claimed. Measured with the new `npm run measure:acts`, which mounts
the real engine at every act:

  bunker 5 vs bunker 1:  floor area 1.00x   rooms 0.95x   enemies 0.93x

The last bunker was very slightly EMPTIER than the first. Floor size came
from `depth` alone, which runs 1-5 inside every bunker, so the act never
entered the arithmetic at all. Worse, actHardMode had ONE entry, for act 4 —
so bunkers 1 through 4 were mechanically identical, since enemy stats also
scale on depth. The only thing that changed across those four was which
archetypes could roll: variety, not difficulty.

"Which bunker should I raid?" had no mechanical answer to give.

THE TWO AXES, NOW REAL

RISK — actHardMode covers all five acts: 1.00/1.20/1.45/1.70/2.00 HP.
Gentle at the start so a new player's first raid is not harder than the
campaign taught them, steep at the end so the last bunker earns its T3
shards. Act 4 is UNCHANGED at 2.0/1.3 — those numbers were tuned against
the boss one-shotting players and nothing here reopens that.

TIME — ACT_SIZE in dungeon.js adds width, height and one room per act.
Rooms are what carry enemies and containers, so effort and reward scale
together without a second knob. Measured after: 1.65x the area of bunker 1
and 1.55x the enemies. Deliberately modest — a MiniPay session is 3-10
minutes on a mid-range phone, so bunker 1 stays the SHORT option. That is
the point of an axis rather than a global difficulty bump.

AND THE PLAYER CAN SEE IT

A difference nobody is shown is not a choice. The map's bottom bar now
carries risk pips, shard tier and length under the bunker's name — on the
way to the ENTER button. Locked bunkers show it too: knowing Bunker 5 is the
only T3 source is a reason to keep going.

lib/constants/bunkers.ts holds what the map renders, and it is the same
shape as the `bunkers` block that made game-config.ts a liability. So it is
guarded differently: test:bunkers mounts the engine, reads its own
actHardMode and shard-tier rule, MEASURES floor size per act, and fails if
any number disagrees. The engine stays the source of truth.

A BUG THIS SURFACED, in WorldMapHub

apply() runs twice — once from the instant localStorage draft, again when
the Firestore copy lands, typically a network round trip later, which is
exactly while the player is looking at the map. Its clamp
(prev > highest ? highest : prev) ran both times, so tapping a locked bunker
to read what it holds snapped the selection back to your own bunker a beat
later, with no explanation. The clamp now applies only until the player taps
something. Found by the UI test failing on an assertion I had assumed was
about my own markup.

Length is shown as "+65% LONGER" rather than "1.6x": at one decimal place
bunker 4 (1.55) and bunker 5 (1.65) both rendered "~1.6x", so two rungs of
the ladder looked identical on the one line meant to tell them apart.

  npm run measure:acts      the before/after table above
  npm run test:bunkers      34 assertions, constants vs the live engine
  npm run test:bunkers-ui   15 assertions in a real browser

Re-measured after: fragment thresholds unchanged (act 0 is untouched),
traversability still 0 failures in 100 floors, seeded floors still 15/15.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017A764RdnwpyWnG7uCNhMiQ
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nullstate Ready Ready Preview Jul 31, 2026 6:57am

@0xward
0xward merged commit c5f9607 into main Jul 31, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant