v1.10.0: Quality of Life - #16
Merged
Merged
Conversation
Four small features from the post-v1.9 backlog, grouped because none of them changes the shape of the game: peak-based legacy cores on the leaderboard, a buy-to-next-milestone button, minigame personal bests, and badge progress bars. Owner decisions: the leaderboard shows best-ever cores rather than 0 (which also makes the existing `.value > 0` filter correct instead of needing a special case); the milestone button is all-or-nothing and stays disabled with its cost visible; personal bests only, no global board. Three things the spec's self-review corrected, all found by checking the code rather than trusting the design conversation: - evaluate() lives in shared/state.js, not gameRules.js, and returns early when elapsedSec < 1. - A peak maintained only in evaluate() would be WRONG. /api/actions applies a batch with no evaluation between actions, so a Migrate followed by a Singularity in one batch destroys the cores before anything observes them. The peak is therefore also captured in singularity() immediately before it zeroes, through one shared helper, with a test that fails if that call site is ever removed as redundant. - shared/reducer.js does not use computeMults at all, so buy() cannot reach the discounted milestone thresholds today. computeMults' threshold expression is extracted into an exported milestoneThresholds(meta, config) used by both, rather than copied. Also records why the client must not compute the milestone target: the infiniteloop shard upgrade discounts the thresholds, so a client on stale config would visibly miss the milestone it promised. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ten tasks across four features, each ending in an independently testable deliverable. Written against the approved spec. The self-review pass found and fixed four things: - Two steps told the implementer to look a value up rather than giving it. Replaced with the real ones: migrateGain = floor(sqrt(lifetimeRun / 1e6) * mult), so 1e8 grants exactly 10 cores, and meta.shardUpgrades is the shard levels property. - The spec asks for an "exactly on a threshold" boundary case that no task covered - sitting on 25 must target 50, not re-buy 25. - The spec requires finish to report a new best only when the score actually beat the prior one; that behaviour was implemented but untested. - Passing ctx into the badge case was a step with no code, and the obvious shortcut - hand-rolling a partial ctx - would satisfy every achievement that exists today and break silently on the next one added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The SDD workspace is git-ignored and dies with the worktree, so the ledger needs a mirror that is committed and pushed. This is the file a new session reads to find out where the work got to. 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>
The call must run BEFORE the 1-second early-return guard to ensure the peak is recorded on every evaluate() call, including sub-1-second gaps. This also fixes report inaccuracies about call placement. 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>
Also fixes a defect in the plan that the Task 3 implementer caught: the lane is called 'tiers', not 'racks'. Seven test snippets named a lane that does not exist, including the Task 10 smoke check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UpgradesPanel and SingularityPanel now read each upgrade's maximum level from the live config.upgrades.maxLevels instead of the static definition, so admin balance edits in the dashboard are immediately reflected in these panels. This mirrors the pattern already implemented correctly in ColdStoragePanel. Both panels now accept config as a prop, threaded from RackStack.jsx. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds getMinigameBests(userId) to both db drivers - a MAX(score) GROUP BY
query over minigame_sessions, filtered to finished sessions - and exposes
it as GET /api/minigame/bests -> { bests: { <game>: number } }. No schema
change: the table already carries every column needed.
POST /api/minigame/finish now returns newBest, computed by reading the
prior best before finishMinigameSession writes this run's score (so a
run can never see its own score in the comparison).
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>
… hot buy path getMinigameBests joins the INTERFACE array db.interface.test.js iterates - that array is the net that catches one driver gaining a method the other lacks, and this repo has been bitten by driver drift before. buy() no longer derives the milestone thresholds for modes that never read them; 'max' and the integer modes were paying for a full computeEffects pass on every tap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Four quality-of-life changes, each extending an existing mechanism rather than
adding one. Plan:
docs/superpowers/plans/2026-08-08-v1.10-qol.md(10 tasks,all complete). Spec:
docs/superpowers/specs/2026-08-08-v1.10-qol-design.md.What changed
Triggering a Singularity deleted you from the Legacy Cores leaderboard. The
board read
meta.legacyCores— cores held right now — then dropped every rowwhose value was zero. A Singularity spends every core you have, so performing
the most demanding action in the game removed you from the board that measures
it. The board now reads a new
meta.stats.bestLegacyCoresand is labelledLegacy Cores (best); the zero-filter is untouched and is now correct rather
than special-cased. No migration: the stat self-seeds from
evaluate(), and isalso recorded in
singularity()before it zeroes the value, becausePOST /api/actionsapplies a whole batch with no evaluation in between.Buy-to-next-milestone on Racks, Grid and Overclock. The server computes
the target —
infiniteloopdiscounts the thresholds, so a client on a staleconfig would ask for the wrong number. New
mode: 'milestone'on thebuyaction, and a new
no_milestoneerror distinguishing "past the last threshold"from "cannot afford the jump". The label's cost is display only.
Minigame personal bests, derived server-side from the
minigame_sessionsrows that already existed — so every score set before this shipped is already
there.
GET /api/minigame/bests, plus anewBestflag onPOST /api/minigame/finish.Progress bars on locked badges. An achievement now declares
progress/targetand the unlock is derived from them, so the bar and theunlock cannot drift. The two genuinely yes/no achievements stay boolean and
show no bar. No threshold moved.
Also: the Upgrades and Singularity panels now read
config.upgrades.maxLevelsinstead of the static definitions, so admin balance edits reach them.
Verification
All three run after the final code change:
TEST_BACKEND=sqlite npx vitest runnpx vitest run(Postgres)TEST_BACKEND=sqlite npm run smokeBaseline at merge-base
45841e6was 698 / 724 / 50. The newtests/e2e/smoke-v110.mjscontributes 7 checks, and Playwright resolved on thisrun, so its client-side check really drove the built client rather than
SKIPping.
After merging
Tag
main— never the branch — asv1.10.0and push the tag. That push iswhat triggers the GHCR publish; a merge alone publishes nothing, and a tag
without the leading
vdoes not match the workflow'sv*.*.*glob.🤖 Generated with Claude Code