feat: gameplay changes — cost scaling, hacks prestige gate, endgame surge#20
Conversation
…urge Three gameplay changes: 1. Building cost multiplier 1.15 → 1.30 for all 12 buildings Makes progression ~2x longer (intern: 15→20→25→33) 2. Hacks require "Hacker Mindset" prestige upgrade (5,000 rep) Removes early-game power spike, makes hacks a mid-game reward 3. Escalating Surge when 9+ buildings mastered - Production multiplier starts at 2x, +1x every 30 seconds - Buffs spawn 2x faster with doubled duration - Surge indicator in TopBar shows current multiplier - Makes the final push for last 3 buildings feel like a crescendo Also updates Help drawer with all mechanic changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The refactor button was only in the desktop TD section (hidden on mobile). Now the mobile compact TD indicator includes a small Refactor button that shows a countdown timer during refactoring. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Gate activateHack behind hack_access prestige upgrade - Clamp surge elapsed to >= 0 (handles clock backwards) - Reset surgeStartedAt to now on load (prevents offline surge growth) - Add rescheduleTrigger to BuffSpawn for surge state changes - Add 6 surge selector tests + 2 surge tick tests + hack gate test - Update hack test to require hack_access - Remove stale comment in calculations test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The card was computing TD from raw baseProduction * techDebtRatio * buildingMult, ignoring purchased TD reduction upgrades. Now applies computeTdReduction() so the displayed rate matches the actual net TD rate (e.g. "Notebook Best Practices" -40% is reflected). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
showHacks already includes !hideHacks in its computation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Export getPurchasedSet from selectors and use it in BuildingCard instead of creating a new Set(purchasedUpgrades) on every render. The memoized version caches by array reference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cleaner buildings (Senior Dev, DevOps, etc.) now show the boosted cleanup rate in the card, matching the engine's computeCleanerBonus. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change truthy check to != null so a hypothetical timestamp of 0 isn't incorrectly treated as null. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename "Internal helpers" to "TD modifier helpers" since functions are now exported - Restore TD threshold requirement text in Hacks help section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Manage surge state | ||
| const surgeActive = selectIsSurgeActive(state); | ||
| let surgeStartedAt = state.surgeStartedAt; | ||
| if (surgeActive && !surgeStartedAt) { |
There was a problem hiding this comment.
Surge activation uses if (surgeActive && !surgeStartedAt) which treats any falsy number (e.g. 0) as “not started”. Since surgeStartedAt is number | null, use a nullish check (surgeStartedAt == null) for correctness/consistency with the load path.
| if (surgeActive && !surgeStartedAt) { | |
| if (surgeActive && surgeStartedAt == null) { |
There was a problem hiding this comment.
Keeping as-is: surgeStartedAt is always either Date.now() (a large positive number) or null. It is never 0 — there is no code path that sets it to 0.
|
|
||
| // Apply surge multiplier if active | ||
| let surgeMultiplier = 1; | ||
| if (state.surgeStartedAt) { |
There was a problem hiding this comment.
computeAllProduction gates surge with if (state.surgeStartedAt). Since surgeStartedAt is number | null, a valid (but falsy) timestamp like 0 would incorrectly disable surge. Prefer state.surgeStartedAt != null (and mirror the same nullish check in selectors/tick) for consistency.
| if (state.surgeStartedAt) { | |
| if (state.surgeStartedAt != null) { |
There was a problem hiding this comment.
Keeping as-is: same reasoning — surgeStartedAt is set via Date.now() or null. The value 0 is not a valid state.
| getInterval: () => { | ||
| const surgeActive = selectIsSurgeActive(useGameStore.getState()); | ||
| const divisor = surgeActive ? 2 : 1; | ||
| return { | ||
| min: GAME_CONFIG.buffs.minSpawnIntervalMs / divisor, | ||
| max: GAME_CONFIG.buffs.maxSpawnIntervalMs / divisor, | ||
| }; |
There was a problem hiding this comment.
getInterval uses selectIsSurgeActive(...) (mastery-threshold check) to speed up buff spawns. Because selectIsSurgeActive can be true before surgeStartedAt is set (surge production hasn’t started yet), buff spawns can speed up slightly earlier than the actual surge. Consider keying this off useGameStore.getState().surgeStartedAt != null (or a dedicated “surge active” selector) so all surge effects start at the same moment.
There was a problem hiding this comment.
Keeping as-is: the one-tick gap between selectIsSurgeActive becoming true and surgeStartedAt being set is 50ms — imperceptible for buff spawn timing.
| if (result.productionMultiplier || result.clickMultiplier) { | ||
| let duration = result.duration ?? 30; | ||
| if (state.prestige.prestigeUpgrades.includes("buff_mastery")) duration *= 2; | ||
| if (selectIsSurgeActive(state)) duration *= 2; |
There was a problem hiding this comment.
Buff duration doubling is currently based on selectIsSurgeActive(state) (mastery threshold), which can become true before the surge has actually started (surgeStartedAt is set in the next tick). To keep behavior consistent with the production surge, consider checking state.surgeStartedAt != null (or a dedicated “surge active” selector) here instead.
| if (selectIsSurgeActive(state)) duration *= 2; | |
| if (state.surgeStartedAt != null) duration *= 2; |
There was a problem hiding this comment.
Keeping as-is: same as above — one-tick (50ms) delay between mastery threshold and surge start is imperceptible for buff duration.
Summary
Three gameplay changes to extend progression and make the endgame interesting:
Changes
buildings.tsprestige.tsStatsPanel/MobileBottomNavgameConfig.tstypes/game.tssurgeStartedAtto GameStategameStore.tsproduction.tsselectors.tsBuffSpawn.tsxTopBar.tsxHelpDrawer.tsxTest plan
🤖 Generated with Claude Code