Login streak — the last empty slot in the daily layer (§5.3) - #210
Merged
Conversation
Daily Contracts answer "why play today". They do not answer "why open this
at all today", and those are different questions. A player with ten spare
minutes plays; a player with one spare minute opens the app or does not —
and if they do not, what breaks is the habit, not the session.
Seven escalating days, breaking it resets to 1. Half of this already
existed as the Season Pass daily claim (+1 energy, +3 t1 shards per UTC
day) — a login reward that was never framed as one, and only for pass
holders. This is the version everyone gets, guests included: a guest id has
the same 20-byte shape as a wallet and keys the same records, and a player
who has not connected a wallet yet is exactly the player a retention
mechanic is for.
THE LADDER, AND WHY IT IS SHAPED LIKE THIS
1: 80 Point 2: 2 t1 3: +1 energy 4: 3 t1
5: 150 Point 6: 4 t1 7: 8 t1
Every rung is t1 shards, energy or Point on purpose. Shard TIER is
act-gated — _shardTierForAct() drops t1 on acts 1-2, t2 on 3-4, t3 on act
5 — so paying a t2 shard would hand a new player a currency they cannot
spend and did not earn.
Day 7 is 8 t1 shards because EVOLUTION_SHARD_COSTS[0] is 8: a full week is
worth exactly ONE weapon evolution. That is the ratchet §4 asks for in so
many words ("the weapon is tier 3, so next week is faster"), and it is a
prize a player can name, which a scattering of shards is not.
Sized against Daily Contracts (200-400 Point or 2-4 t1 for real work): a
whole week of merely opening the app is worth roughly one day of playing
it. Showing up should be rewarded; it must never out-earn showing up AND
playing.
TRUST
The day is the server's UTC day, the streak is derived from the stored last
day rather than sent, and the whole decision (continuation, restart, or
repeat?) happens inside ONE transaction — so two tabs cannot both advance
it or both be paid. A grant that throws still counts the day: a
re-claimable day is worse than one missed grant, and it is logged.
No claim step, matching the decision Daily Contracts already made — opening
the app IS the event. The chip sits first on the bar because it is the one
number there the player can lose, and loss aversion only works when the
thing at risk is in front of them. A streak of 0 renders nothing rather
than a "0" nobody can read.
ALSO: FIXES A FLAKY TEST I SHIPPED
test-seeded-floors.js failed about one run in four on "the opened container
stays open". A floor averages 1.6 lockable containers, so a fixed depth
sometimes generated none and the container assertions had nothing to act
on. It now retries the MOUNT until the floor has one — probing and
remounting does not work, because every fresh mount draws a new RUN_SEED,
so the probe's floor is not the floor the next mount builds. Stable across
five consecutive runs.
npm run test:streak 31 assertions against a stubbed RTDB (day
boundaries cannot be tested in a browser without
waiting for midnight)
npm run test:streak-ui 16 assertions in a real browser
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017A764RdnwpyWnG7uCNhMiQ
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fills the final
[TARGET]in layer 2. §5.3 flips to[TODAY], and the four-layer summary in §3 no longer says the daily layer is empty.No smart contract, ABI, or contract address is touched.
Why this is separate from Daily Contracts
Contracts answer why play today. They do not answer why open this at all today, and those are different questions.
A player with ten spare minutes plays. A player with one spare minute opens the app or does not — and if they do not, what breaks is the habit, not the session.
Half of this already existed as the Season Pass daily claim (+1 energy, +3 t1 shards per UTC day) — a login reward that was never framed as one, and only for pass holders. This is the version everyone gets, guests included: a guest id has the same 20-byte shape as a wallet and keys the same records, and a player who has not connected a wallet yet is exactly the player a retention mechanic is for.
The ladder
Every rung is t1 shards, energy or Point on purpose. Shard tier is act-gated —
_shardTierForAct()drops t1 on acts 1–2, t2 on 3–4, t3 on act 5 — so paying a t2 shard would hand a new player a currency they cannot spend and did not earn.Day 7 is 8 t1 shards because
EVOLUTION_SHARD_COSTS[0]is 8: a full week is worth exactly one weapon evolution. That is the ratchet §4 asks for in so many words — "the weapon is tier 3, so next week is faster" — and it is a prize a player can name, which a scattering of shards is not.Sized against Daily Contracts (200–400 Point or 2–4 t1 for real work): a whole week of merely opening the app is worth roughly one day of playing it. Showing up should be rewarded; it must never out-earn showing up and playing.
Trust
The day is the server's UTC day, the streak is derived from the stored last day rather than sent, and the whole decision — continuation, restart, or repeat? — happens inside one transaction, so two tabs cannot both advance it or both be paid.
A grant that throws still counts the day. A re-claimable day is worse than one missed grant; it is logged loudly because it is the only outcome here that costs the player something.
UI
No claim step, matching the decision Daily Contracts already made — opening the app is the event. The chip sits first on the daily bar because it is the one number there the player can lose, and loss aversion only works when the thing at risk is in front of them. A streak of 0 renders nothing rather than a "0" nobody can read.
The reward is announced once, on the visit that earned it, as a dismissible line above the bar — deliberately not a modal, so it never stands between the player and the ENTER button they came for.
Also: fixes a flaky test I shipped in #209
test-seeded-floors.jsfailed about one run in four on "the opened container stays open". A floor averages 1.6 lockable containers, so a fixed depth sometimes generated none and the container assertions had nothing to act on.It now retries the mount until the floor has one. Probing and remounting does not work — every fresh mount draws a new
RUN_SEED, so the probe's floor is not the floor the next mount builds. That subtlety is why the first fix for this flake failed. Stable across five consecutive runs.Verification
npm run test:streak(new)npm run test:streak-ui(new)npm run test:seedednpm run test:raidnpm run test:contractsnpm run test:floorsnpm run test:inventorynpm run test:traversabletsc --noEmitlintaudit·check:copy·check:market·check:cssvars·check:attributionbuildDocs:
GAME-DESIGN.md§5.3 rewritten as shipped with the reasoning behind each rung, §3 and the build order updated, andgame-mechanics.mdgains a player-facing table.Generated by Claude Code