Skip to content

v1.5.0: Social & Retention — contracts, leaderboards, achievements, daily streak - #4

Merged
NeverEndingCode merged 11 commits into
mainfrom
worktree-v1.5-social
Aug 1, 2026
Merged

v1.5.0: Social & Retention — contracts, leaderboards, achievements, daily streak#4
NeverEndingCode merged 11 commits into
mainfrom
worktree-v1.5-social

Conversation

@NeverEndingCode

@NeverEndingCode NeverEndingCode commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Implements v1.5, the final phase of the v1.2–v1.5 expansion. Spec and 12-task plan are in the first two commits; the remaining ten are the implementation, one per task.

What's in it

  • Daily contracts — three a day, rotating at midnight UTC, generated deterministically from the date so everyone gets the same three types, with targets scaled to each player's own output and level.
  • Global leaderboards — six boards aggregated server-side from canonical saves behind a ~60s cache, with avatars, usernames and badge mini-icons.
  • Achievements — 19, pure prestige, unlocked automatically (including from offline accrual), shown as a badge case with an unlock toast.
  • Daily streak — 7-day escalating claim in the header banner; a missed UTC day resets it.
  • social.* tunables — all the numbers above are Balancing-tab editable and event-overlayable.

Design points worth a look

  • Type ids aren't stored in the save. They're re-derived from dateKey, which is what guarantees client and server agree without a sync step. Targets and baselines are stored — a rate-scaled target recomputed on every read would recede as fast as the player approached it. tests/contracts.test.js and the e2e suite both pin this.
  • Locked-lane substitution. Three of the six contract types need Cold Storage. Rather than hand a new player two dead contracts, locked picks are deterministically swapped for base-lane ones — two locked players still match each other exactly.
  • The achievement sweep runs in two places, because the two ways state advances are disjoint: after every successful action (applyAction), and after evaluate() on the load path. The second is what catches lifetime-FLOPS thresholds crossed while a player was offline, which no action touches.

A real bug the e2e suite caught

The global boards are cache-fronted, so opting out left a player visible for up to 60 seconds. The per-event board has always been immediate — v1.4 went out of its way to make opt-out live rather than a join-time snapshot ("hard requirement 1") — and this control must not quietly mean something weaker on the newer boards. PUT /api/me/leaderboard-opt-out now invalidates the cache, with a unit regression test pinning it. This is exactly the class of thing the whole-branch review has historically caught, so it's worth a second pair of eyes.

Two pre-existing tests needed updating

Adding unlockedAchievements to successful action results broke one toEqual assertion, and the sweep's goalCtx call broke two tests that hand-built a runtime-fields-only config. Both fixed at the assertion/fixture level rather than by suppressing the sweep.

The one judgment call, flagged again

social.contractFlopsMin is a floor on the FLOPS contract target (no upper clamp, so it stays a pure rate scale above it). Without it a zero-output player — fresh save, or the instant after a Migrate — gets a target of 0, which is already met, and the contract auto-completes free. Set it to 0 in the Balancing tab to restore the unguarded behaviour. Documented in spec §5.1 and the README.

Verification

  • npm test415 passed, 28 files (was 318 on main)
  • cd client && npm run build — clean
  • tests/e2e/smoke-v15.mjs7/7 PASS, all seven checklist items
  • tests/e2e/smoke-v14.mjs9/9 PASS, no regressions from the shared-code changes

Not tagged or pushed as a release — per the usual ritual that's merge, then tag main, then push the tag.

🤖 Generated with Claude Code

https://claude.ai/code/session_013HLPH7zGymsz31RarDnNKS

Evan Phyillaier and others added 11 commits July 31, 2026 19:35
Expands §6 of the master expansion spec into an implementable design:
daily contracts board, global leaderboards, achievements/badges, and
the daily streak. Records the four owner-resolved design forks (single
Social tab, rate-scaled contract targets, explicit streak claim with
missed-day reset, admin-tunable `social` config section).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twelve TDD tasks from the design spec: day-cycle helpers and the social
config section, canonical state additions, the four shared modules
(daily/contracts/streak/achievements), the two reducer actions plus the
automatic achievement sweep, load-path rollover, the leaderboard service
and route, three client tasks, and release prep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…unlocks

Two pre-existing event tests needed updating: one asserted a successful
result with toEqual (now also carries unlockedAchievements), and two built
a bare runtime-fields-only config that the sweep's goalCtx call can't read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…banner

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…treak

Adds the e2e smoke suite, README/CHANGELOG entries, and the version bump.

The e2e suite caught a real bug before it shipped: the global leaderboards
are served from a ~60s cache, so opting out left a player visible for up to
a minute. The per-event board has always been immediate (v1.4's "hard
requirement 1"), so the route now invalidates the cache on opt-out, with a
unit regression test pinning it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@NeverEndingCode NeverEndingCode changed the title v1.5 Social & Retention: design spec and implementation plan v1.5.0: Social & Retention — contracts, leaderboards, achievements, daily streak Aug 1, 2026
@NeverEndingCode
NeverEndingCode marked this pull request as ready for review August 1, 2026 04:41
@NeverEndingCode
NeverEndingCode merged commit c0b19c6 into main Aug 1, 2026
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