Skip to content

Make a week of playing always pay: Vault Fragments - #199

Merged
0xward merged 2 commits into
mainfrom
claude/nullstate-onchain-text-q6g2a3
Jul 30, 2026
Merged

Make a week of playing always pay: Vault Fragments#199
0xward merged 2 commits into
mainfrom
claude/nullstate-onchain-text-q6g2a3

Conversation

@0xward

@0xward 0xward commented Jul 30, 2026

Copy link
Copy Markdown
Owner

First item from the GAME-DESIGN.md build order. No contract changes — Firebase and the engine only.

The problem

Old Paper and the Golden Key were pure 16% rolls on rare containers, capped one per wallet per week. Two outcomes, both bad and both common:

  • Lucky player got both on day 1 → nothing to do for six days.
  • Unlucky player ground all week and went home with nothing. A week of effort that paid zero is not a setback, it is a reason to uninstall.

It also produced the dead end that started this whole thread: reach the vault door holding only one of the two, and there was no recourse at all.

The rule now

Luck may shortcut the grind; effort must guarantee it.

Open an interactive container +1 Vault Fragment
12 fragments 📄 Old Paper, granted
28 fragments 🔑 Golden Key, granted

The 16% roll is untouched and still fires on top, so the early thrill survives — it just no longer gates the reward.

What this really buys is the thing the game did not have: playing on day 2 now visibly purchases the day-6 payout.

How it stays safe

The grant writes the same records the old path did. paperClaims/ and goldenKeyClaims/, through the same abort-if-present transaction /api/paper/claim and /api/goldenkey/claim use. So the weekly 1-per-wallet cap is enforced by the records themselves, a fragment grant racing the lucky roll for the same item resolves to exactly one winner, and /api/vault/submit honours an item earned this way without a line changing.

openContainerWindow is the credit hook because of what does not reach it. onOpenButtonTap routes the vault door to openVaultWindow and the sealed/premium caches to grantCacheLoot, so neither can farm the bar. Decor.open() guards on this.opened, so a container cannot be credited twice.

The request carries no amount. The client reports "a container was opened"; the server decides what that is worth. A client-supplied quantity would let anyone mint the weekly reward in one request.

No double-award on the client either. The engine grants the item locally before the server answers, so a fragment grant also zeroes that run's remaining roll for the same item — without it a player could briefly hold two Papers against one claim record.

Where the player sees it

  • A progress bar in the inventory, directly under the two items it buys (gone once both are held)
  • A run-log line every fifth fragment
  • An exact "open N more" count in the vault door's requirements line — "go search" becomes a countable distance

On the thresholds

12 and 28 are estimated from the spawn code, not measured: max 5 props per room, 12% rare, northOnly types falling back to tables on side walls → roughly 8–15 interactive containers per bunker. That puts Paper at about one bunker and the Key at about three.

lib/server/vault-fragments.ts says so in a comment, in as many words: watch the first week and raise both if the bar fills on day one.

Verification

Award logic checked against a stubbed RTDB — all passed:

✓ paper granted at exactly 12          ✓ golden key granted at exactly 28
✓ paper granted exactly once           ✓ golden key granted exactly once
✓ a Paper already held by luck is never re-granted
✓ the lucky claim record is untouched  ✓ a new week starts the bar at zero
✓ another wallet is unaffected         ✓ nextGoal is the Paper while unheld

Plus: tsc --noEmit clean · next build passes (/api/vault/fragments registers ƒ) · lint 34 errors, unchanged from main · engine parses and minifies · check:market, audit, check:copy, check:cssvars all pass.

Not verified: how the thresholds feel in real play, and the bar's look on a real phone.

Docs

GAME-DESIGN.md §5.1 flipped [TARGET][TODAY] per its own rule, with the container list corrected (plaque_coin was missing — it is interactive too). game-mechanics.md gains a player-facing explanation.


Generated by Claude Code

Old Paper and the Golden Key were pure 16% rolls on rare containers,
capped one per wallet per week. That produced two outcomes and both were
bad. A lucky player got both on day one and had nothing to do for six
days. An unlucky one ground all week and went home with nothing — and a
week of effort that paid zero is not a setback, it is a reason to
uninstall. It also produced the dead end that started this: reach the
vault door holding only one of the two and there was no recourse at all.

The rule now is that luck may shortcut the grind but effort must
guarantee it. Opening an interactive container earns a fragment; twelve
grants the Paper, twenty-eight the Golden Key. The 16% roll is untouched
and still fires on top, so the early thrill survives — it just no longer
gates the reward. What this really buys is the thing the game did not
have: playing on day two now visibly purchases the day-six payout.

The grant writes the SAME paperClaims/ and goldenKeyClaims/ records that
/api/paper/claim and /api/goldenkey/claim already write, through the same
abort-if-present transaction. So the weekly cap is enforced by the records
themselves, a fragment grant racing the lucky roll for the same item
resolves to exactly one winner, and /api/vault/submit honours an item
earned this way without a line changing.

openContainerWindow is the credit hook precisely because of what does not
reach it: onOpenButtonTap routes the vault door and the sealed/premium
caches elsewhere, and Decor.open() guards on this.opened, so neither the
door nor a re-opened chest can farm the bar. The request carries no
amount — a client that could name its own reward would not need to play.

The client grants the item locally before the server answers, so a
fragment grant also zeroes that run's remaining roll for the same item;
without it a player could briefly hold two Papers against one claim
record.

Thresholds are estimated from the spawn code (max five props per room,
12% rare, northOnly types falling back to tables on side walls) at roughly
8-15 containers per bunker — about one bunker for the Paper and three for
the Key. That is a reading, not a measurement, and the module says so:
watch the first week and raise both if the bar fills on day one.

Verified the award logic against a stubbed RTDB: grants land on exactly
the threshold, exactly once, never a second time for an item already held
by luck, with the lucky claim record left intact, and weeks and wallets
isolated from each other.
@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 30, 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 30, 2026 4:46pm

Owner asked whether the fragment bar was crowding the loot grid, and
whether earning fragments would compete with looting food and gear.
Neither, as it turned out — the bar shrinks below, and lockable containers
are already the best gear source in the game, so the two incentives point
at the same chest. But checking the second question turned up something
much worse.

Decor.open() marks a prop `opened`, and updateActionButton skipped
anything opened when choosing a target. So a thing could be opened exactly
once, ever, whatever state it was left in.

For the vault door that is the dead end the owner reported, one layer
below where we thought it was. closeVaultWindow's own comment promised
"the vault door stays interactive, so you can check your Paper and re-open
it to enter the code" and it was simply not true: the door was already
marked opened by then, so the target scan skipped it forever. Tapping the
button that says BACK TO THE BUNKER — the escape hatch made prominent two
PRs ago — locked the player out of the vault for the rest of the run,
leaving only LEAVE WITHOUT OPENING, which ends the campaign.

For containers it destroyed loot. Close the window with slots untaken,
which on a phone is one mis-tap, and that loot was gone with nothing said.

_canReopenDecor now keeps the vault door permanently re-targetable, and
keeps a container re-targetable while it still holds untaken slots, where
the button reads REOPEN. Closing a container with loot inside says so in
the run log, since a player who closed by accident has no other way to
know anything is still in there.

Neither is farmable. rollLootSlots memoises on this.lootSlots, so a
reopened container shows the same remaining slots rather than a fresh
roll, and the vault fragment moved out of openContainerWindow — which is
now also the reopen path — into the first-open branch of onOpenButtonTap,
where reaching the line at all means open() returned true. The vault door
and the sealed/premium caches take earlier branches and never earn one.

Bar shrunk to a single line as asked: label, count, hairline track, and
the how-to-earn sentence moved to a title attribute. 62px to 34px,
measured in headless Chromium at 390px wide — about half an item cell.
@0xward
0xward merged commit abc9921 into main Jul 30, 2026
4 of 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