Files
SpaceWanderer/docs/superpowers/plans/2026-10-01-trait-outcome-effects.md
T

9.2 KiB
Raw Blame History

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 ONLY npx 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 deploy docker 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

  1. 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.
  2. 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 — modify resolveExploreTask (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 the RECRUIT_CHANCE_TALKER / RECRUIT_CHANCE_BASE constants from crew-costs.ts
  • lib/game/save.test.ts — extend the existing describe('resolveExploreTask (A/B against explorePlanet)') block with three new it cases
  • lib/game/crew-costs.ts (created in PR A, Task 1) — consume RECRUIT_CHANCE_BASE (0.18) and RECRUIT_CHANCE_TALKER (0.30); no new constants added
  • lib/game/crew.ts — add an optional chance?: number parameter to rollCrewMember (JC-30); default CREW_CHANCE so the no-trait path is bit-identical

Interfaces:

  • Consumes: explorationRng(seed, planetId), rollExploration(rng), rollCrewMember(rng, takenNames) — all already in save.ts from PR A; RECRUIT_CHANCE_BASE / RECRUIT_CHANCE_TALKER from ./crew-costs; the Task row's leadCrewId and helpers (assigned crew); crew trait columns (traitGood / traitBad) from the crew table
  • 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 resolveExploreTask describe block in lib/game/save.test.ts; add import { RECRUIT_CHANCE_BASE, RECRUIT_CHANCE_TALKER } from './crew-costs' to the import. Three new it cases:
    • 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.18 and RECRUIT_CHANCE_TALKER === 0.30, and (via a seeded case or by asserting the exact constant is consumed by rollCrewMember) that the TALKS TO ANYONE path selects 0.30.
    • 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.
    • Implement — in resolveExploreTask (lib/game/save.ts): read assigned crew ids from task.leadCrewId + task.helpers; load their trait columns; set hasFHT / hasTalker booleans; if hasFHT, call rollExploration(rng) one additional time on the same seeded stream and append the result to outcome; pass hasTalker ? RECRUIT_CHANCE_TALKER : RECRUIT_CHANCE_BASE as the recruit chance to rollCrewMember (add an optional chance param defaulting to RECRUIT_CHANCE_BASE if not already present — see JC-30).
    • 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).
    • Commit: git commit -m "feat(game): trait outcome effects — extra haul item + recruit chance (#43)"

Judgment calls

  • JC-29 — Trait-name matching uses the literal strings 'FINDS HIDDEN THINGS' and 'TALKS TO ANYONE' from the design spec §5, checking both traitGood and traitBad columns (defensive; in practice both are good traits). If PR A introduced trait-name constants in crew-costs.ts or 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 optional chance?: number param defaulting to RECRUIT_CHANCE_BASE so 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)".
    • 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%.
    • 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 run npx playwright test.
    • Push + open the PR to master — git push -u origin feat/trait-outcome-effects, then open a PR to master on Gitea (repo OpenCode/SpaceWanderer). Credentials per JC-20 (gitea MCP preferred, else GITEA_TOKEN from repo-root .env).
    • Auto-merge on green — POST /repos/OpenCode/SpaceWanderer/pulls/<n>/merge body {"do":"merge","merge_style":"merge","merge_when_checks_succeed":true}. Wait for CI (~7 min); poll until merged_at is set. If CI is red, STOP and report the failure — do not fix it in this step.
    • Tag on master — git checkout master && git pull && git tag v0.12.0 && git push origin v0.12.0.
    • Manual deploy — docker compose up -d --build in the repo root, then verify curl -s https://space-wanderer.dev.kkhost.pl/api/version reports 0.12.0.
    • 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).