9.2 KiB
Trait Outcome Effects Implementation Plan (PR C — Gitea #43, v0.12.0)
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: The two remaining good traits (FINDS HIDDEN THINGS, TALKS TO ANYONE) get deterministic mechanical effects in the explore resolver.
Architecture: A small change to the explore resolver's seeded stream (PR A plan Task 4a) plus the recruit-chance constant; no new tables.
Tech Stack: Next.js 16 App Router, React 19, TypeScript strict, bun, SQLite via Drizzle + sql.js (WASM), Vitest unit tests, Playwright e2e (CI-only)
Spec: docs/superpowers/specs/2026-10-01-crew-driven-actions-design.md (§5)
Upstream: Builds on the post-v0.11.0 state; the explore resolver it modifies is defined in the PR A plan (resolveExploreTask, Task 4a); task numbering continues from the PR B plan (12–16).
Global Constraints
- AGENTS.md change process per PR: feature branch off master, no direct pushes to master; version bump in package.json + CHANGELOG entry at top (
## v<version> — YYYY-MM-DD HH:MM); local verify ONLYnpx tsc --noEmit,npx vitest run,npx playwright test --list(e2e is CI-only, never run locally); push → CI auto-merge on green (verify merged_at) → tag v on master → manual deploydocker compose up -d --build+ verify /api/version at https://space-wanderer.dev.kkhost.pl. - DDL in lib/db/client.ts must stay in sync with lib/db/schema.ts; schema changes only via idempotent backfill steps in save.ts ensureSave (SCHEMA_VERSION 6→7→8); no DB wipes ever.
- OpenAPI spec in lib/openapi.ts updated whenever an endpoint changes (and lib/openapi.test.ts pins, which must be updated with it).
- Single-player by design: NO auth, accounts, or multiplayer, ever.
- Determinism: every game outcome reproducible from seed + player actions; no wall-clock randomness.
- New pure modules (crew-costs.ts, tasks.ts, skills.ts) must not import lib/db/* — shared server+client like the tech.ts precedent (required for live duration preview in the UI).
Review Focus
- Existing save loaded after deploy has no tasks/crew_skills tables → backfill must create them without touching turn, crew, or inventory state, and be idempotent across restarts.
- Seeded-stream draw order must be fixed regardless of traits: an extra haul roll for FINDS HIDDEN THINGS shifts later draws on that per-planet stream — pin the exploration outcome as a pure function of (seed, planetId, assigned crew) with determinism tests both with and without the trait.
Task 17: Trait Outcome Effects — extra haul item + recruit chance (PR C — Gitea issue #43)
Files:
lib/game/save.ts— modifyresolveExploreTask(defined in PR A, Task 4a): insert the FINDS HIDDEN THINGS extra haul-item roll between the regular haul and the recruit roll on the per-planet seeded stream; switch the recruit chance from the hardcoded 0.18 to theRECRUIT_CHANCE_TALKER/RECRUIT_CHANCE_BASEconstants fromcrew-costs.tslib/game/save.test.ts— extend the existingdescribe('resolveExploreTask (A/B against explorePlanet)')block with three newitcaseslib/game/crew-costs.ts(created in PR A, Task 1) — consumeRECRUIT_CHANCE_BASE(0.18) andRECRUIT_CHANCE_TALKER(0.30); no new constants addedlib/game/crew.ts— add an optionalchance?: numberparameter torollCrewMember(JC-30); defaultCREW_CHANCEso the no-trait path is bit-identical
Interfaces:
- Consumes:
explorationRng(seed, planetId),rollExploration(rng),rollCrewMember(rng, takenNames)— all already insave.tsfrom PR A;RECRUIT_CHANCE_BASE/RECRUIT_CHANCE_TALKERfrom./crew-costs; theTaskrow'sleadCrewIdandhelpers(assigned crew); crew trait columns (traitGood/traitBad) from thecrewtable - Produces:
resolveExploreTask(db, task) → Promise<{outcome: FoundItem[]; crewFound: CrewDto | null}>— same signature and return shape; the trait-modified draw order is internal and invisible to callers (explorePlanet, the tick resolver)
-
- Write tests — extend the
resolveExploreTaskdescribe block inlib/game/save.test.ts; addimport { RECRUIT_CHANCE_BASE, RECRUIT_CHANCE_TALKER } from './crew-costs'to the import. Three newitcases: - Regression pin — an explore with NO trait-bearing crew assigned produces a byte-identical haul + recruit outcome to the pre-change (PR A) behavior for the same seed and planet.
- Review Focus 5 pin — same seed + same planet; compare assignment [A] (no traits) vs [A, B] where B has FINDS HIDDEN THINGS: the first N haul items are identical between the two, the FHT run has exactly one extra item, and the whole outcome (haul + recruit) is reproducible across repeated runs of the same save.
- TALKS TO ANYONE constant pin — with a TALKS TO ANYONE member assigned the recruit roll uses 0.30: assert
RECRUIT_CHANCE_BASE === 0.18andRECRUIT_CHANCE_TALKER === 0.30, and (via a seeded case or by asserting the exact constant is consumed byrollCrewMember) that the TALKS TO ANYONE path selects 0.30.
- Write tests — extend the
-
- Run — expect failure:
npx vitest run lib/game/save.test.ts→ the three new tests fail: the current code has no extra haul roll and uses the hardcoded 0.18 recruit chance.
- Run — expect failure:
-
- Implement — in
resolveExploreTask(lib/game/save.ts): read assigned crew ids fromtask.leadCrewId+task.helpers; load their trait columns; sethasFHT/hasTalkerbooleans; ifhasFHT, callrollExploration(rng)one additional time on the same seeded stream and append the result tooutcome; passhasTalker ? RECRUIT_CHANCE_TALKER : RECRUIT_CHANCE_BASEas the recruit chance torollCrewMember(add an optionalchanceparam defaulting toRECRUIT_CHANCE_BASEif not already present — see JC-30).
- Implement — in
-
- Run — expect green:
npx vitest run lib/game/save.test.ts→ all tests pass (the three new ones plus all pre-existing PR A tests).
- Run — expect green:
-
- Commit:
git commit -m "feat(game): trait outcome effects — extra haul item + recruit chance (#43)"
- Commit:
Judgment calls
- JC-29 — Trait-name matching uses the literal strings
'FINDS HIDDEN THINGS'and'TALKS TO ANYONE'from the design spec §5, checking bothtraitGoodandtraitBadcolumns (defensive; in practice both are good traits). If PR A introduced trait-name constants increw-costs.tsor elsewhere, use those instead — verify at implementation time. - JC-30 —
rollCrewMember's current signature (PR A code is the source of truth) may or may not accept a chance parameter. If 0.18 is hardcoded inside the function, add an optionalchance?: numberparam defaulting toRECRUIT_CHANCE_BASEso the no-trait path is bit-identical; if it already takes one, just pass the correct value.
Task 18: Ship v0.12.0 (PR C — Gitea issue #43)
Files:
docs/superpowers/plans/2026-10-01-trait-outcome-effects.md— this plan (commit on the PR C branch; the spec doc is unchanged — already committed in PR A)package.json(version bump 0.11.0 → 0.12.0)CHANGELOG.md(new top entry)
This is a process task that ships the PR C already built by Task 17. No failing-test step; ends in a verification gate.
-
- Commit the PR C plan doc on the PR C branch (
feat/trait-outcome-effects, created in Task 17) — only this plan file needs committing (spec unchanged):git add docs/superpowers/plans/2026-10-01-trait-outcome-effects.md && git commit -m "docs: trait outcome effects implementation plan (#43)".
- Commit the PR C plan doc on the PR C branch (
-
- Version bump —
package.json→"version": "0.12.0"(from 0.11.0).CHANGELOG.md: new top entry## v0.12.0 — YYYY-MM-DD HH:MM(fill date/time at execution) with bullets: FINDS HIDDEN THINGS grants +1 exploration haul item; TALKS TO ANYONE raises crew-recruit chance 18% → 30%.
- Version bump —
-
- Local verify — ONLY these three (e2e is CI-only, never run locally):
npx tsc --noEmit(clean),npx vitest run(all pass),npx playwright test --list(every spec compiles + lists). Do NOT runnpx playwright test.
- Local verify — ONLY these three (e2e is CI-only, never run locally):
-
- Push + open the PR to master —
git push -u origin feat/trait-outcome-effects, then open a PR tomasteron Gitea (repoOpenCode/SpaceWanderer). Credentials per JC-20 (gitea MCP preferred, elseGITEA_TOKENfrom repo-root.env).
- Push + open the PR to master —
-
- Auto-merge on green —
POST /repos/OpenCode/SpaceWanderer/pulls/<n>/mergebody{"do":"merge","merge_style":"merge","merge_when_checks_succeed":true}. Wait for CI (~7 min); poll untilmerged_atis set. If CI is red, STOP and report the failure — do not fix it in this step.
- Auto-merge on green —
-
- Tag on master —
git checkout master && git pull && git tag v0.12.0 && git push origin v0.12.0.
- Tag on master —
-
- Manual deploy —
docker compose up -d --buildin the repo root, then verifycurl -s https://space-wanderer.dev.kkhost.pl/api/versionreports 0.12.0.
- Manual deploy —
-
- Close Gitea issue #43 — close #43 with a comment summarizing the shipped state (v0.12.0, tag v0.12.0, live deploy verified).
Verification gate (whole task): /api/version = 0.12.0 AND Gitea issue #43 is closed.
Judgment calls — none (process task mirroring the PR A plan's Task 11; no new judgment calls arise here).