Skip to content

v1.10.0: Quality of Life - #16

Merged
NeverEndingCode merged 25 commits into
mainfrom
v1.10-qol-spec
Aug 8, 2026
Merged

v1.10.0: Quality of Life#16
NeverEndingCode merged 25 commits into
mainfrom
v1.10-qol-spec

Conversation

@NeverEndingCode

Copy link
Copy Markdown
Owner

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 row
whose 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.bestLegacyCores and is labelled
Legacy Cores (best); the zero-filter is untouched and is now correct rather
than special-cased. No migration: the stat self-seeds from evaluate(), and is
also recorded in singularity() before it zeroes the value, because
POST /api/actions applies a whole batch with no evaluation in between.

Buy-to-next-milestone on Racks, Grid and Overclock. The server computes
the target — infiniteloop discounts the thresholds, so a client on a stale
config would ask for the wrong number. New mode: 'milestone' on the buy
action, and a new no_milestone error 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_sessions
rows that already existed — so every score set before this shipped is already
there. GET /api/minigame/bests, plus a newBest flag on
POST /api/minigame/finish.

Progress bars on locked badges. An achievement now declares
progress/target and the unlock is derived from them, so the bar and the
unlock 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.maxLevels
instead of the static definitions, so admin balance edits reach them.

Verification

All three run after the final code change:

suite result
TEST_BACKEND=sqlite npx vitest run 720 passed, 29 skipped, 0 failed
npx vitest run (Postgres) 746 passed, 3 skipped, 0 failed
TEST_BACKEND=sqlite npm run smoke 57 PASS, 0 FAIL, 0 SKIP, exit 0

Baseline at merge-base 45841e6 was 698 / 724 / 50. The new
tests/e2e/smoke-v110.mjs contributes 7 checks, and Playwright resolved on this
run, so its client-side check really drove the built client rather than
SKIPping.

After merging

Tag main — never the branch — as v1.10.0 and push the tag. That push is
what triggers the GHCR publish; a merge alone publishes nothing, and a tag
without the leading v does not match the workflow's v*.*.* glob.

🤖 Generated with Claude Code

Evan Phyillaier and others added 25 commits August 8, 2026 13:14
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>
@NeverEndingCode
NeverEndingCode merged commit c7af6ee into main Aug 8, 2026
4 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