20 KiB
Idlegame — M0 Bootstrap Plan (handoff artifact for superpowers)
How to use this file: the interview/brainstorm phase is complete; everything below is locked. Hand this plan to superpowers for execution (this copy in the working dir at
docs/plans/2026-06-11-m0-bootstrap.mdis canonical). Tasks are bite-sized with per-task verification soexecuting-planscan run them sequentially. Feature work after M0 flows through superpowers' brainstorm → plan → TDD cycle per feature.Refined 2026-06-11 (same-day review pass, approved by ginnoir): T3 split into T3.1–T3.4; T8 deploy auth + Caddy mechanics specified (romhacks-wiki precedent); Node policy
engines >=24/ CI on 24 LTS; pnpm 10 postinstall allowlist note; CI-required protection toggle moved from T6 to T7. No design decisions re-opened. Review-gate addition: per-task tool + model routing for M0 (§AI tool routing).
Context
ginnoir is starting a commercial-grade idle/incremental game: a text fantasy RPG inspired by Your Chronicle (timed actions, resource generate/consume loops, story objectives), structured like a visual novel with branching choices, ending runs in a prestige system that avoids same-y repeats. Monetization, if ever: upfront purchase or cosmetics only — QoL and speed-up are never paywalled; any process completed once becomes automatable.
M0 scope: capture the locked design in the vault, stand up the multi-tool workflow (Gitea + CI + internal playtest deploys + Plane + Obsidian), and scaffold a walking-skeleton codebase. Gameplay (M1 vertical slice) is seeded as Gitea issues and built in later sessions.
Starting state: C:\Users\MattC\Documents\idlegame is empty except docs/plans/, not a git repo. Gitea runs at gitea.ginnoir.com (owner ginnoir, private default, HTTPS creds in GCM). No Gitea Actions runner exists yet. Plane is self-hosted on valhalla. Obsidian vault is the cross-project source of truth. Superpowers drives development workflow; all four AI tools (Claude Code, Codex, Antigravity, Cursor) get Obsidian MCP + local vault access.
Locked design pillars (interview, 2026-06-11)
| Pillar | Decision |
|---|---|
| Platform | Responsive web + PWA; plays well on phones from day one. Steam (Tauri/Electron) and app-store (Capacitor) wraps are later milestones |
| Visuals | Text-first UI: panels, progress bars, action lists, event log. No art pipeline |
| Combat | Light/abstracted — timed actions with stat checks and risk/reward; no battle screen in v1 |
| First playable (M1) | Vertical slice: one short story arc → first prestige reset; branching choice; automation unlock; offline progress; save/load |
| Prestige | Hybrid: aggressive catch-up within a layer (Magic Research style — keep knowledge, blast through known content); genuinely new mechanics between layers (Antimatter Dimensions style) |
| Numbers | Human-readable throughout (≤ billions). No scientific-notation spectacle |
| Pacing | Semi-active (Your Chronicle-like): queue actions, choices every few minutes; idles/offline fine |
| Story authoring | Outline together in Obsidian; Claude drafts prose; ginnoir reviews/edits |
| Automation principle | Complete a process once → it becomes automatable (generous, progression-gated) |
| CI/CD | Gitea Actions runner on valhalla; typecheck/lint/test/build on push; auto-deploy dev build to internal-only playtest URL |
| Tracking | Plane = roadmap/epics. Gitea Issues = code-level tasks (commit/PR auto-linking) |
| Codename | idlegame (rename later is cheap in Gitea) |
Tech stack (researched recommendation)
TypeScript (strict) + Vite + React 19 + Zustand, with the game engine as a pure-TS module that never imports React.
- Genre precedent: every successful game in this genre is a web app — Antimatter Dimensions (Vue), Magic Research (React), Melvor Idle (web, wrapped for Steam/mobile). Text/UI-heavy idle games are DOM games; Godot/Unity fight you on UI and bloat web exports.
- React over Svelte/Vue: deepest fluency across all four AI tools — important when agents write most of the code. Perf is a non-issue with the core/view split: engine ticks on its own loop; UI subscribes to snapshots at ~10 fps via Zustand selectors.
- The engine/view split is the load-bearing decision: headless-testable game core (Vitest, no DOM); Tauri/Capacitor wraps or even a framework swap later touch only the view.
- Considered & rejected: Profectus (MIT, Vue, Modding-Tree lineage, ~34 stars — license fine but Vue-locked, shaped for tree-prestige games, community too small for a commercial bet); The Modding Tree (older JS, same shape); Godot (canvas UI friction for a text game).
Dependencies (all permissive; the game stays proprietary — no GPL anywhere):
| Dep | License | Role |
|---|---|---|
| react, zustand | MIT | view + state bridge |
| zod | MIT | schema validation for content definitions and saves |
| lz-string | MIT | compressed save export/import strings |
| idb-keyval | Apache-2.0 | IndexedDB persistence (localStorage fallback) |
| vite, vite-plugin-pwa, workbox | MIT | build + installable PWA |
| tailwindcss v4 | MIT | styling for the text-first UI |
| biome, vitest, (playwright later) | MIT/Apache-2.0 | lint+format, unit tests, E2E later |
Toolchain: pnpm, Node 24 LTS as the CI/runtime baseline — engines: { "node": ">=24" } (a floor, not an exact pin: local boxes may run newer — Node 25.9 verified working 2026-06-11; a strict 24.x pin makes every local pnpm install warn), .nvmrc = 24, and CI jobs run a node:24 image so the LTS pin is enforced where determinism matters. Numbers: plain JS numbers behind src/engine/num.ts (format + branded type); ADR documents the Decimal escape hatch if a late prestige layer ever needs it.
Workflow architecture
- Repo:
gitea.ginnoir.com/ginnoir/idlegame, private. Trunkmain(protected, CI required), feature branches + PRs, conventional commits, self-merge allowed (solo + AI dev). - Issues: Gitea labels (
type/feat|fix|content|infra,area/engine|ui|story|workflow,prio/1-3), milestonesM0 Scaffold,M1 Vertical Slice. Issue templates (feature/bug/content) + PR template in.gitea/. - Plane: project "Idlegame" with epics (Vertical Slice, Workflow & Infra, Game Design). Epics link Gitea issues by URL; day-to-day work lives in Gitea.
- CI (
.gitea/workflows/ci.yml): pnpm install →typecheck→biome ci→vitest run --coverage→build→ uploaddistartifact. - Deploy (on push to
main): rsyncdist/over SSH (dedicated deploy key stored as repo Actions secrets) to valhalla's Caddy site dir/config/caddy/site/idlegame/→idlegame.ginnoir.com, internal-only during development (Caddyimport internal_only— same pattern as other internal sites; no auth layer needed). Precedent: romhack-archive-site static deploy (/config/caddy/site/romhacks-wiki). - Runner:
act_runnercontainer (docker mode, outbound polling only) added to homelabstack'sdevstack, following that repo's conventions — readHomelab/_Claude.md+Deploy.mdin the vault before touching homelabstack (applies to T7 and T8).
Cross-tool agent layer
AGENTS.mdis canonical — Linux Foundation standard; Codex, Cursor, and Antigravity read it natively. Contents: project overview, commands, architecture map, conventions (commits, PR flow, engine/view boundary rules), the context-layer contract, and the tool-routing table below.CLAUDE.md=@AGENTS.mdimport + Claude-only extras (superpowers notes, vault skill pointers).- Context-layer contract (stated in AGENTS.md): Obsidian vault (all tools have MCP + the local folder
C:\Users\MattC\Documents\Obsidian Vault\; record both in AGENTS.md) = design docs, story, decisions, session memory. API tokens tools authenticate with live in the vault atclaude/Credentials.md— never in the repo. Repodocs/= code-truth (README, ADRs, architecture, superpowers plans indocs/plans/). Gitea Issues = work items. Every tool writes session outcomes back to the vault.
AI tool routing (light)
One tool per issue/branch at a time; everything lands via PR; AGENTS.md + vault are the shared brain regardless of tool.
| Tool | Best at | Route these tasks |
|---|---|---|
| Claude Code (+ superpowers) | Architecture, multi-step planning, nuanced writing, big multi-file features, code review | Engine/systems design, prestige & balance design, story outlining + prose drafts, milestone planning, reviewing other tools' PRs, vault stewardship |
| Codex | Long autonomous runs on well-scoped tasks, gnarly debugging, test discipline | Well-scoped implementation tickets, bug hunts with repro steps, test-coverage passes, mechanical refactors |
| Antigravity | Frontend work with built-in browser verification, multimodal/screenshot feedback, large-context research | UI implementation + polish, responsive/mobile layout passes, visual verification of the playtest build, competitive research (other idle games) |
| Cursor | Interactive pair-programming, fast small edits while ginnoir drives; agent mode now frontier-class (Composer 2.5 ≈ Opus 4.8 on benchmarks, June 2026) | Quick fixes, small scoped tweaks, exploratory fiddling, anything ginnoir wants to steer by hand; credible alternate for Codex-lane scoped tickets |
Rule of thumb: design and words → Claude; heads-down scoped code → Codex; anything you need to see → Antigravity; anything you're driving yourself → Cursor.
Per-task routing (M0)
Applies the rules above to T1–T10. Claude Code legitimately dominates M0 — it's workflow/infra/writing-heavy by design; Codex and Antigravity take the tasks where they genuinely win. Cursor has no M0 assignment (its lane is ginnoir-driven interactive edits; M0 runs autonomously — Cursor's turn starts at M1). Default execution mode is unchanged: a single superpowers/Claude Code executing-plans run per the header; the table says who's best per task — route T2/T3.2/T3.3/T3.4 out to Codex/Antigravity only if ginnoir wants to shake down the multi-tool workflow during M0. For M1+ feature work the table's logic is the standing template.
Claude Code primary is Opus 4.8 — Fable 5 access ends 2026-06-22, so nothing durable relies on it. ⭐F marks the tasks that benefit most from Fable 5 while it lasts (the highest-judgment writing and architecture calls); if M0 executes before the cutoff, running the whole thing on Fable 5 is fine.
| Task | Tool | Model | Why |
|---|---|---|---|
| T1 vault notes | Claude Code | Opus 4.8 ⭐F | Vault stewardship; GDD/story prose quality compounds downstream |
| T2 scaffold | Codex | GPT-5.5-Codex (medium) | Well-scoped ticket, command-line verification, gotchas pre-spelled-out; alt: Cursor agent (Composer 2.5) |
| T3.1 num + tick loop | Claude Code | Opus 4.8 ⭐F | Load-bearing engine architecture: API shape + TDD, not heads-down typing |
| T3.2 resource + action | Codex | GPT-5.5-Codex (medium) | Scoped code on shapes T3.1 fixed; fully test-gated |
| T3.3 saves + persistence | Codex | GPT-5.5-Codex (highest effort tier) | Save integrity = the data-loss surface; strongest test discipline |
| T3.4 Zustand + React shell | Antigravity | Gemini 3 Pro (high thinking) | Browser verification watches the progress bar, reload, offline credit |
| T4 AGENTS.md + docs | Claude Code | Opus 4.8 ⭐F | The conventions doc every other tool reads |
| T5 git + Gitea repo | Claude Code | Sonnet 4.6¹ | API glue with vault-held creds; mechanical |
| T6 repo config | Claude Code | Sonnet 4.6¹ | Labels/protection/templates via API |
| T7 Actions runner | Claude Code | Opus 4.8 | Touches the live homelab stack; vault conventions + care |
| T8 playtest deploy | Claude Code | Opus 4.8 | Secrets, Caddy, cross-repo infra; Antigravity optional for browser/PWA verify-assist |
| T9 Plane + M1 seeding | Claude Code | Opus 4.8 ⭐F | Issue prose = the contract for M1 sessions |
| T10 write-back | Claude Code | Sonnet 4.6¹ | Vault stewardship, mechanical |
¹ Tier note: if M0 runs as one continuous executing-plans session (lowest-friction path), stay on one model throughout — the Sonnet 4.6 entries mean "this tier suffices when run standalone / cost-sensitive."
Caveats: (a) all of T3 predates the repo (git init is T5), so M0 handoffs to Codex/Antigravity land directly in the working dir without the PR gate — hand off only at task boundaries with green tests; full one-tool-per-branch discipline starts at M1. (b) Model names current as of 2026-06-11 (Opus 4.8 / Sonnet 4.6 standing, Fable 5 only until 2026-06-22; GPT-5.5-Codex; Gemini 3 Pro; Cursor Composer 2.5, which benchmarks ≈ Opus 4.8 — making it Cursor's default for everything and Cursor agent a credible Codex-lane alternate at M1+) — substitute each tool's newest equivalent tier at execution time.
Obsidian vault additions
Per claude/Vault.md convention: Idlegame/_Claude.md (what it is, working dir, repo URL, standing rules — monetization stance, automation principle, prestige philosophy — quick-nav, session log, [[Context]] backlink); Idlegame/GDD.md (pillars + core loops from the interview); Idlegame/Story/Outline.md (authoring-workflow skeleton); Idlegame/Decisions.md (design ADR-lite; code ADRs live in repo). Add the row to claude/Context.md's project table.
Scaffold layout
idlegame/
AGENTS.md CLAUDE.md README.md LICENSE.md (proprietary notice)
.gitea/workflows/{ci,deploy}.yml .gitea/issue_template/ PR template
.nvmrc package.json tsconfig.json (strict) biome.json vite.config.ts (PWA)
docs/adr/0001-tech-stack.md docs/architecture.md docs/plans/ (superpowers plans)
src/engine/ ← pure TS: fixed-timestep tick loop + offline catch-up, resources,
action queue, story graph, prestige hooks, versioned saves
(zod-validated, lz-string export), num.ts
src/content/ ← data-driven, zod-validated definitions (resources, actions, story nodes)
src/state/ ← zustand bridge: engine snapshots → view
src/ui/ ← React shell: action panel, resource bar, event log, settings
src/engine/__tests__/ ← engine core target: 80%+ coverage
The scaffold ships a walking skeleton, not gameplay: 1 resource, 1 timed action, tick loop with offline catch-up, save → reload → intact. Proves the architecture end to end; M1 makes it a game.
Tasks (superpowers-executable, in order)
T1 — Vault project setup. Idlegame/_Claude.md and the claude/Context.md row already exist (created at planning handoff) — extend _Claude.md, don't recreate. Create Idlegame/GDD.md, Idlegame/Story/Outline.md, Idlegame/Decisions.md (content from this plan). Verify: vault_read returns each note; Context.md table has the row.
T2 — Scaffold the app. Vite react-ts template → strict tsconfig, Biome, Vitest, Tailwind, vite-plugin-pwa, folder layout above (except .gitea/workflows/deploy.yml — deferred to T8 so deploy never runs red before its secrets exist), .nvmrc (24) + engines: { "node": ">=24" }. pnpm 10 blocks dependency postinstall scripts by default — allowlist build deps (esbuild etc.) via pnpm.onlyBuiltDependencies in package.json (or pnpm approve-builds), or the toolchain silently breaks. Verify: pnpm typecheck && pnpm lint && pnpm test && pnpm build all green; pnpm dev serves; manifest in build output.
T3 — Walking skeleton (TDD, four sub-tasks). Tests first throughout; engine code never imports React.
- T3.1 — Engine core:
num.ts+ tick loop. Branded number type + human-readable formatting; fixed-timestep accumulator loop (advance(now)) where offline catch-up is the same code path (large delta → many ticks, batch-capped). Configure Vitest coverage thresholds here (≥80% onsrc/engine/). Verify: tests green; determinism test — N elapsed seconds always yields the same tick count. - T3.2 — One resource, one timed action. Zod-validated content definitions in
src/content/: one resource, one timed action (duration → yields resource); action progress advances per tick, completion grants the yield. Verify: accrual + completion unit tests green. - T3.3 — Versioned saves + persistence. Zod save schema with version field, serialize/deserialize, lz-string export/import string, idb-keyval persistence (localStorage fallback), autosave + load-on-boot, offline elapsed credited on load. Verify: round-trip test; invalid/tampered save rejected cleanly; offline-credit unit test.
- T3.4 — Zustand bridge + React shell. Engine snapshots → Zustand store (~10 fps); minimal UI: resource readout, start-action button with progress bar, event log stub. Verify: engine files ≥80% coverage overall; manual — action completes, resource increments, reload preserves state, offline time credited.
T4 — Agent layer + docs. AGENTS.md (incl. routing table + context contract + vault local path), CLAUDE.md import, README, ADR-0001 (stack), docs/architecture.md; this plan already lives at docs/plans/2026-06-11-m0-bootstrap.md — keep it current. Verify: files exist; AGENTS.md states commands that actually run.
T5 — Git + Gitea repo. git init -b main (git 2.46 may default to master), conventional commits, create private repo via Gitea API, push main. Verify: repo visible at gitea.ginnoir.com/ginnoir/idlegame with full tree.
T6 — Repo config. Labels, milestones M0/M1, issue/PR templates live, protect main (no direct pushes, PRs required, self-merge ok). The CI-required status check is deliberately deferred to T7 — flipping it on before a runner exists would leave main unmergeable. Verify: direct push to main rejected; templates render on new-issue page.
T7 — Actions runner. Read Homelab/_Claude.md + Deploy.md first. Add act_runner (docker mode) to homelabstack dev stack per that repo's conventions; register with token; enable Actions on the repo. After the first green run, flip CI-required on main's protection (deferred from T6). Verify: a test branch push runs CI to green on the runner (main rejects direct pushes by now); merging to main now requires green CI.
T8 — Playtest deploy. Read Homelab/_Claude.md + Deploy.md first (same rule as T7). Auth: generate a dedicated ed25519 deploy keypair; append the pubkey to the ginnoir user's authorized_keys on valhalla; store private key + known_hosts as repo Actions secrets via the Gitea API. Create .gitea/workflows/deploy.yml here (deferred from T2): on push to main, build → rsync dist/ over SSH to valhalla:/config/caddy/site/idlegame/ (mirrors the romhacks-wiki precedent). Caddy block in homelabstack: idlegame.ginnoir.com with import internal_only, file_server, SPA try_files fallback to /index.html (PWA routing); lands via the existing Caddyfile push workflow. Verify: DNS resolves on LAN (add a host entry per homelab conventions if no wildcard covers it); URL loads the walking skeleton on desktop and phone (LAN); PWA installable.
T9 — Plane + M1 seeding. (Caddy bypass done 2026-06-11, commit 9935041 in homelabstack — /api/* + X-API-Key now routes directly to plane_api:8000, bypassing Authentik; verified.) Plane project "Idlegame" + 3 epics via API (key in vault claude/Credentials.md); ~10 M1 issues in Gitea (engine tick/offline, resource & action definitions, action queue UI, story graph + first branch, automation unlock, prestige reset, save migrations, mobile layout pass, opening-arc content, balance pass), labeled and milestoned, linked from epics. Verify: Plane shows epics; Gitea milestone M1 lists the issues.
T10 — Write-back. Session outcomes → Idlegame/_Claude.md session log; mark M0 complete in Gitea. Verify: vault note updated; M0 milestone closed.
No task needs to ask ginnoir for secrets or approval: the Gitea admin API token and the Plane API key live in the vault at claude/Credentials.md (local file: C:\Users\MattC\Documents\Obsidian Vault\claude\Credentials.md). Runner registration tokens are minted at deploy time via POST /api/v1/admin/actions/runners/registration-token (the GET route 404s on Gitea 1.26.2; CLI fallback: docker exec gitea gitea actions generate-runner-token on valhalla). The T8 deploy keypair is generated at execution time and stored via the Gitea secrets API — no secret passes through ginnoir's hands. Verified 2026-06-11: Gitea token has admin scope, Actions enabled, 0 runners registered. Plane API Caddy bypass is live (homelabstack 9935041, 2026-06-11) — all tasks T1–T10 can run fully autonomously.
Out of scope (later milestones)
M1 gameplay itself; Steam/Tauri and Capacitor wraps; Playwright E2E; release tagging + changelog; i18n; cloud saves; public playtest access.