Adds five global income multiplier upgrades (Essence Infusion IāV) purchasable
purely with essence at escalating costs (1T ā 500T essence). These give late-
prestige players a meaningful ongoing use for the massive essence reserves that
accumulate once all other upgrades are purchased.
## Summary
Closes#61
- Adds the **Autonomous Recruitment** prestige upgrade (50 runestones) to both the API and web data files
- Adds `autoAdventurer?: boolean` to the `GameState` type for backwards-compatible saves
- Adds tick-loop logic in GameContext that automatically purchases the highest-tier unlocked adventurer the player can afford each frame when the toggle is enabled
- Adds `toggleAutoAdventurer` callback and exposes it through the context
- Adds toggle UI in the Prestige Shop (mirrors the existing Auto-Prestige toggle pattern)
- Updates the How to Play guide in the About panel to document the new automation feature
⨠This issue was created with help from Hikari~ šø
Reviewed-on: #76
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Adds a `š”` stacking note directly in the upgrade panel below the progress counter so players see it without visiting the About page
- Updates the About panel's Upgrades how-to-play entry to replace the vague "compound with each other" with explicit multiplicative stacking language, including an example (two Ć2 upgrades = Ć4) and a note that global upgrades multiply on top of adventurer-specific ones
## Test plan
- [ ] Verify the stacking note appears in the upgrade panel below the progress counter
- [ ] Verify the About panel Upgrades entry reflects the updated wording
- [ ] Confirm lint, build, and tests all pass
Closes#60
Reviewed-on: #75
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Locked exploration zones now show a `š This zone is locked. Unlock exploration by:` hint above the area list, with the specific `āļø Defeat: {boss}` and `š Complete: {quest}` required
- Updated the About panel's Exploration how-to-play entry to document the zone unlock rule explicitly
- No new data required ā unlock conditions are read directly from `zone.unlockBossId` and `zone.unlockQuestId` already in state
## Test plan
- [ ] Verify locked exploration zones display the correct boss and quest unlock hints
- [ ] Verify already-unlocked zones show no hint
- [ ] Verify starter zone (no unlock conditions) shows no hint
- [ ] Verify the About panel Exploration entry reflects the updated description
- [ ] Confirm lint, build, and tests all pass
Closes#59
Reviewed-on: #74
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Adds a `āļø Combat Power` entry to the always-visible resource bar
- Value is computed client-side as the sum of each adventurer's `combatPower Ć count`
- No new props required ā computed directly from `state` via the existing `useGame()` hook
- Players can now see their combat strength at a glance before attempting boss fights or quests
## Test plan
- [ ] Verify the Combat Power stat appears in the resource bar
- [ ] Verify the value increases as more adventurers are recruited
- [ ] Verify the value displays correctly with `formatNumber` for large numbers
- [ ] Confirm lint, build, and tests all pass
Closes#58
Reviewed-on: #72
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Adds a `getCurrentProgress` helper that mirrors the tick engine's achievement-checking logic to compute the player's current progress for each condition type
- Locked achievement cards now display a `<progress>` bar and a numeric `{current} / {target}` label so players can see exactly how close they are to each achievement
- Unlocked achievements are unaffected ā no progress bar shown once earned
## Test plan
- [ ] Verify locked achievement cards display a progress bar and numeric label
- [ ] Verify the progress values match what the tick engine uses for unlock checking
- [ ] Verify unlocked achievement cards show no progress bar
- [ ] Confirm lint, build, and tests all pass
Closes#57
Reviewed-on: #71
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Adds a `š”ļø Affects: {Name}` label to upgrade cards that target a specific adventurer
- Resolves player confusion caused by class-based language (e.g. "doubles cleric output") without specifying which adventurer tiers count as that class
- Label appears in all three card states: available, purchased, and locked
## Test plan
- [ ] Verify adventurer-targeted upgrade cards display the correct adventurer name
- [ ] Verify global, click, boss, and prestige upgrade cards show no affects label
- [ ] Confirm lint, build, and tests all pass
Closes#56
Reviewed-on: #70
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
Two improvements to the equipment system in one PR:
### Balance fixes (closes#54)
Full equipment audit revealed 9 items with duplicated stats, regressions, or purchasable items weaker than free boss drops:
| Item | Change | Reason |
|---|---|---|
| Void Conduit | 4x ā 7x combat | 100M essence sink was equal to a zone-6 boss drop |
| Void Edge | 2.75x ā 3.25x combat | Purchasable was weaker than free Celestial Blade (3x) |
| Astral Robe | 2.25x ā 2.75x gold | Boss drop was weaker than purchasable Titan's Aegis (2.5x) |
| Philosopher's Stone | 2x ā 2.25x click | Duplicated Frost Crystal's click multiplier |
| Eternal Flame | 1.15x ā 1.25x gold | Gold regressed vs Philosopher's Stone (1.25x) |
| Celestial Focus | 2.5x ā 3x click | 20M essence sink was weaker than free Angel's Halo (2.75x click + 1.3x gold) |
| Abyssal Tome | 3x ā 3.75x gold | 50M essence sink was equal to free Heaven's Mantle (3x) |
| Crystal Matrix | 4x ā 4.75x gold | 20M crystal sink was equal to free Sinslayer Aegis (4x) |
| Infernal Gem | 3.5x ā 4x click | 5M crystal sink was identical to free Prism Eye |
### Equipment sorting (closes#55)
Equipment cards within each slot now render in ascending order of combined bonus power ā the sum of all multiplier bonuses ā so stronger items always appear further down the list. Hybrid items such as Volcanic Plate sort correctly without needing a per-slot primary stat.
## Test plan
- [ ] All purchasable weapons/armour/trinkets now exceed the stats of the highest free boss drop at their tier
- [ ] No duplicate stat values between adjacent items in the same progression track
- [ ] Equipment cards within each slot render weakest ā strongest
- [ ] Hybrid multi-stat items sort sensibly alongside single-stat items
- [ ] Full pipeline green (lint + build + tests at 100% coverage)
⨠This PR was created with help from Hikari~ šø
Reviewed-on: #69
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Exploration timers showed more time than the area's stated duration when the server clock was ahead of the client's
- The timer was derived from `startedAt = endsAt - durationMs`, then computed as `durationSeconds - (clientNow - startedAt) / 1000` ā any server/client clock skew directly inflated the result
- Now stores `endsAt` (the server-computed completion timestamp) directly in `ExplorationAreaState` and computes the timer as `(endsAt - Date.now()) / 1000`, which is immune to clock drift
- Old saves without `endsAt` fall back gracefully to the previous `startedAt`-based calculation
## Test plan
- [ ] Start a new exploration ā timer should show exactly the area's stated duration (no more "1h area shows 1h15m")
- [ ] Refresh the page mid-exploration ā timer should resume from the correct remaining time (using server-anchored `endsAt`)
- [ ] Old saves with `startedAt` but no `endsAt` should still display a timer via the fallback path
Closes#53⨠This PR was created with help from Hikari~ šø
Reviewed-on: #68
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Bosses defeated before `bountyRunestonesClaimed` was introduced had `status: "defeated"` but the field `undefined`
- After prestige, the preservation check (`=== true`) missed these bosses, so the first-kill bounty was re-awarded on the next run
- Now also treats `status === "defeated"` as proof the bounty was already earned, covering the migration case
## Test plan
- [ ] Existing test: `preserves bountyRunestonesClaimed flag on bosses across prestige` ā still passes
- [ ] New test: `sets bountyRunestonesClaimed on bosses defeated before the flag was introduced` ā covers the legacy save migration path
- [ ] Full coverage maintained at 100%
Closes#52⨠This PR was created with help from Hikari~ šø
Reviewed-on: #67
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- `buildPostPrestigeState` was constructing the post-prestige `GameState` from `initialGameState`, which hard-codes `autoQuest` and `autoBoss` to `false`
- Neither flag was being carried forward, so both automation settings silently reset after every prestige
- Now both values are explicitly preserved from `currentState` (with `?? false` fallback for safety)
Closes#51⨠This issue was created with help from Hikari~ šø
Reviewed-on: #66
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Adds a new **Debug** tab to the game UI with two self-service tools for players with broken save state
- **Force Unlocks**: scans the player's save and grants any zones, quests, bosses, and exploration areas they've earned but that are still locked ā shows a breakdown of what was unlocked (or reports nothing needed fixing)
- **Hard Reset**: wipes progress back to a fresh save (preserving lifetime stats), guarded behind a confirmation modal to prevent accidental clicks
## Files added
- `apps/api/src/routes/debug.ts` ā two POST endpoints (`/force-unlocks`, `/hard-reset`)
- `apps/web/src/components/game/debugPanel.tsx` ā the Debug tab UI
- `apps/web/src/components/ui/confirmationModal.tsx` ā reusable confirmation modal
## Files modified
- `apps/api/src/index.ts` ā registers the debug router
- `packages/types/src/interfaces/api.ts` ā adds `ForceUnlocksResponse` type
- `packages/types/src/index.ts` ā exports the new type
- `apps/web/src/api/client.ts` ā adds `forceUnlocks()` and `debugHardReset()` API calls
- `apps/web/src/context/gameContext.tsx` ā wires both functions into game context
- `apps/web/src/components/game/gameLayout.tsx` ā adds the Debug tab
- `apps/web/src/styles.css` ā styles for action buttons, cards, result messages, and confirmation modal
⨠This PR was created with help from Hikari~ šø
Reviewed-on: #65
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
Closes#50
## Summary
- Calls `forceSync()` before every boss challenge so the server always fights against the player's live state (equipped items, upgrades, etc.) rather than a potentially stale snapshot
- Adds a `bossError` state that captures and displays error messages from failed manual boss challenges in the boss panel, matching the existing `autoBossError` display pattern
⨠This PR was created with help from Hikari~ šø
Reviewed-on: #64
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Applies the same sticky-zone pattern from #48 to the crafting panel (`elysium_craft_zone` key in sessionStorage)
- Introduces a `handleZoneSelect` wrapper so sessionStorage is updated alongside React state on every zone change
- Gracefully falls back to `verdant_vale` if no stored value exists
## Test plan
- [x] Lint ā zero errors, zero warnings
- [x] Build ā all packages build cleanly
- [ ] Manual: select a non-default zone in the crafting panel, navigate away and back ā zone should still be selected
- [ ] Manual: log out and back in ā zone should reset to Verdant Vale
⨠This PR was created with help from Hikari~ šø
Reviewed-on: #49
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- **#35** ā Adventure multiplier selection is now persisted in `localStorage` (`"elysium_batch_size"`). The chosen batch size is restored automatically on the next visit, with a graceful fallback to `1` for missing or unrecognisable values.
- **#36** ā Zone selection in the boss panel and quest panel is now persisted in `sessionStorage` (`"elysium_boss_zone"` / `"elysium_quest_zone"`). The selected zone survives navigation within a session and resets cleanly when the session ends, defaulting to Verdant Vale if no stored value exists.
## Test plan
- [x] Lint ā zero errors, zero warnings
- [x] Build ā all packages build cleanly
- [x] Tests ā 415 tests passing, 100% coverage across all packages
- [ ] Manual: select a non-default batch size, refresh the page ā multiplier should be restored
- [ ] Manual: switch to a non-default zone in the boss panel, navigate away and back ā zone should still be selected
- [ ] Manual: repeat for the quest panel
- [ ] Manual: log out and back in ā zone selection should reset to Verdant Vale
⨠This PR was created with help from Hikari~ šø
Reviewed-on: #48
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
Resolves#37, resolves#38, and resolves#39 ā three related bugs where prestige incorrectly reset data that should survive all prestige resets.
## Changes
### fix: preserve lifetime player stats across prestige (#37)
After prestige, `GameState.player.lifetime*` fields were stale ā they reflected values from *before* the current run. The Prisma Player record was incremented correctly, but the GameState JSON saved to the DB had old values, so the UI showed wrong all-time totals on reload.
`buildPostPrestigeState` now computes the run-stat contributions (bosses defeated, quests completed, adventurers recruited, achievements unlocked, gold earned, clicks) and folds them into the fresh player object before writing the prestige state.
### fix: preserve achievements across prestige (#38)
`buildPostPrestigeState` was reconstructing achievements from `defaultAchievements` (via `initialGameState`), resetting all unlocked achievements on every prestige. Achievements are now carried forward from `currentState.achievements` instead.
### fix: preserve boss first-kill state across prestige (#39)
Added `bountyRunestonesClaimed?: boolean` to the `Boss` type. The boss challenge route now:
- Only awards the first-kill bounty runestones if `bountyRunestonesClaimed !== true`
- Sets `bountyRunestonesClaimed = true` on first defeat
`buildPostPrestigeState` maps the fresh boss list and carries the `bountyRunestonesClaimed` flag forward from the current state, so the bounty is never re-awarded in subsequent prestige runs. The boss panel badge is also hidden for bosses whose bounty is already claimed.
## Test Coverage
All three fixes include new tests covering the new behaviours. API coverage remains at 100%.
⨠This PR was created with help from Hikari~ šø
Reviewed-on: #47
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Auto-boss now turns itself **off** when a boss fight is **lost**, so the player can reassess rather than the system silently looping. A "š¤ Last fight: [Boss] ā ā Lost" status line appears in the boss panel.
- Auto-boss also turns off (with an ā ļø error message) when the API call fails outright (e.g. party has no adventurers), replacing the previous behaviour of silently hammering the API every animation frame.
- Auto-quest now turns itself **off** whenever a quest fails the random-chance check, detected inside the tick's `setState` callback immediately after `applyTick`.
- `autoBoss: false` and `autoQuest: false` are now part of `initialGameState`, so these fields persist through save/load cycles from the very first session ā preventing a race window where the boss-route DB write could strip them before the first auto-save.
- `toggleAutoBoss` clears both `autoBossLastResult` and `autoBossError` on each toggle so the panel always reflects the current session cleanly.
## Test plan
- [x] `pnpm lint` ā 0 errors, 0 warnings
- [x] `pnpm build` ā all packages clean
- [x] `pnpm test` ā 100% coverage maintained across the board
Closes#40⨠This issue was created with help from Hikari~ šø
Reviewed-on: #46
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Adds `flags: 4096` (`MessageFlags.SUPPRESS_NOTIFICATIONS`) to the Discord webhook payload in `postMilestoneWebhook`
- Milestone announcements (prestige, transcendence, apotheosis) will now appear in the channel without triggering desktop or mobile push notifications
- Defines the magic number as a documented `suppressNotifications` constant for self-documentation
- Updates the webhook test to assert `flags: 4096` is present in the outgoing payload
Closes#41
## Test plan
- [ ] Lint passes: `pnpm lint`
- [ ] Build passes: `pnpm build`
- [ ] Tests pass with 100% coverage: `pnpm test`
- [ ] Trigger a prestige/transcendence/apotheosis in-game and verify the Discord webhook message arrives without pinging anyone
⨠This issue was created with help from Hikari~ šø
Reviewed-on: #45
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Add comprehensive try/catch error handling across all API routes, middleware, and the Hono global error handler, piping every unhandled error to the `@nhcarrigan/logger` service to prevent silent crashes and unhandled Promise rejections
- Add a `logError` utility on the frontend that forwards errors through the overridden `console.error` to the backend telemetry endpoint; apply it to every silent `catch {}` block in the game context, sound, notification, and clipboard utilities, and wrap the React tree in an `ErrorBoundary`
- Add Plausible analytics, Open Graph + Twitter Card meta tags, Tree-Nation widget, and Google Ads to `index.html`
- Make the game sidebar sticky with a `--resource-bar-height` CSS custom property offset so it stays viewport-height without overlapping the resource bar; reset sticky behaviour in the mobile responsive override
## Test plan
- [ ] Lint passes: `pnpm lint`
- [ ] Build passes: `pnpm build`
- [ ] Verify errors thrown in API routes appear in the logger service rather than crashing the process
- [ ] Verify frontend errors appear in the `/api/fe/error` backend log
- [ ] Verify Open Graph tags render correctly when sharing the URL
- [ ] Verify Plausible analytics fires on page load
- [ ] Verify Tree-Nation badge renders in the sidebar
- [ ] Verify sidebar stays fixed while the main content scrolls on desktop
- [ ] Verify mobile layout is unaffected
⨠This issue was created with help from Hikari~ šø
Reviewed-on: #44
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Adds `apps/web/src/utils/cdn.ts` with a `cdnImage(folder, id)` helper that builds URLs from `https://cdn.nhcarrigan.com/elysium/`
- Wires CDN art into all 13 game panels (bosses, quests, adventurers, companions, equipment, upgrades, prestige, transcendence, achievements, explorations, crafting, story, codex)
- Zone selector tabs now display 16:9 zone art thumbnails in place of emoji icons
- Adds a fixed background image at 15% opacity via `body::before`
- Documents the art generation and CDN upload process in `CLAUDE.md` for future expansions
Resolves#15⨠This PR was created with help from Hikari~ šø
Reviewed-on: #43
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
## Summary
- Installs `react-markdown@10.1.0` in `apps/web`
- Replaces the `<pre>` tag in the changelog section with the `<Markdown>` component for proper rendering
- Updates CSS to style markdown elements (paragraphs, lists, headings, code blocks, links, bold text)
Closes#31⨠This PR was created with help from Hikari~ šø
Reviewed-on: #33
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
- Load route syncs characterName from Player record so profile updates
are reflected immediately on next load
- Save route preserves Player record's characterName so auto-saves
cannot overwrite profile updates
- Public profile response now includes completedChapters
- Character sheet panel displays completed story chapters with outcome
- Removed stale CSS for old achievement/codex toast classes
Each story choice now has a concise third-person description used on
the public character page, keeping narrative spoilers out of the
profile view whilst still conveying the character's path.
- Move bossVictory sound and notification from gameContext into BattleModal,
fired at the 5.2s reveal timeout so the animation plays before the spoiler
- Replace CSS width transition with a setInterval tick (50ms steps over 5s)
so bossHpPercent and partyHpPercent update incrementally during the animation
- Both bars now use a shared getHpColour helper: green >50%, yellow 25-50%,
red <25%, causing colour to shift naturally as the bar visually drains
- Merge .codex-toast and .achievement-toast into a single .game-toast class
- Fix storyToast inner class names and replace <button> wrapper with <div>
- Add QuestCompleteToast and QuestFailedToast components
- Add MilestoneToast for prestige, transcendence, and apotheosis events
- Move shared toast container to gameLayout so all toasts stack in one column
- Wire quest detection in GameContext to store full Quest objects for toast names
- Trigger prestige toast from both auto-prestige and manual prestige panel
## Summary
This PR represents the full v1 prototype, implementing the core game systems for Elysium.
- Full idle/clicker RPG loop: resource collection, crafting, boss fights, exploration, and quests
- Adventurer hiring with batch size selector and progressive tier cost scaling
- Prestige, transcendence, and apotheosis systems with auto-prestige support
- Character sheet, titles, leaderboards, companion system, and daily login bonuses
- Auto-quest and auto-boss toggles
- Discord webhook notifications on prestige/transcendence/apotheosis
- Discord role awarded on apotheosis
- Responsive design and overarching story/lore system
- In-game sound effects and browser notifications for key events
- Support link button in the resource bar
- Full test coverage (100% on `apps/api` and `packages/types`)
- CI pipeline: lint ā build ā test
## Closes
Closes#1Closes#2Closes#3Closes#4Closes#5Closes#6Closes#7Closes#8Closes#9Closes#10Closes#11Closes#12Closes#13Closes#14Closes#16Closes#19Closes#20Closes#21Closes#22Closes#23Closes#24Closes#25Closes#26Closes#27Closes#29⨠This issue was created with help from Hikari~ šø
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Reviewed-on: #30
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>