docs: approved crew-driven-actions spec + implementation plan (#44)

This commit is contained in:
2026-10-03 00:57:11 +00:00
parent a0307e4ccd
commit babae7c392
2 changed files with 2686 additions and 0 deletions
@@ -0,0 +1,121 @@
# Crew-Driven Actions & Skill Progression — Design Spec
- Date: 2026-10-01
- Status: approved
- Tracking: Gitea issues #43–#45 (created after spec approval)
- Repo rules: AGENTS.md change process applies to every issue below
## 1. Background
SpaceWanderer is a single-player, turn-based space adventure (dungeon-crawler flavor; reference: Out There). As of v0.9.12 the loop is jump → scan planets → collect hauls → spend on ship upgrades & research → take missions for generic rewards. Every action resolves instantly and costs exactly 1 turn (jumps cost hop count, 1–2 with NAV AI tech). Crew members carry a role and two traits but change nothing mechanically — ORCHESTRATOR.md's "manage crew of flawed humans" is unimplemented.
This iteration makes **crew the central resource**: every action requires an assigned team, takes variable duration based on that team's roles/skills/traits, and time advances only when the player presses NEXT TURN. Doing jobs builds skill levels, so crews specialise over a playthrough.
## 2. Goals / Non-goals
Goals:
1. Every game action (jump, explore, ship work, research) becomes a queued task with an assigned team (lead + optional helpers).
2. Duration is computed from work points ÷ team power, modified by role mismatch and the lead's traits — min 1 turn.
3. Manual time: NEXT TURN button (bottom-right) advances exactly 1 turn; empty queue = free skip.
4. Skill progression: 4 skills keyed by action type; XP from completed tasks; levels 0–5 with power bands; role-based starting specialisation; titles at L3/L5.
5. All 20 traits get mechanical meaning (duration modifiers now; two outcome effects in #43; three combat traits marked STANDBY for the future encounters iteration).
Non-goals (explicitly out of scope):
- Random encounters, combat, damage, death (next roadmap item per ORCHESTRATOR.md §7)
- Real-time mode — but the work-point model is time-independent by design to ease that migration later
- Crew injury/departure, supplies/hunger systems, task cancellation
## 3. Core Model: Queued Tasks + Turn Tick
- New `tasks` table (schema v7): one row per queued/active task. Fields: id, saveId, type (`JUMP|EXPLORE|SHIP_UPGRADE|SHIP_EXPAND|SHIP_BUILD|RESEARCH`), payload (JSON — targetSystemId / planetId / systemType+action / techSlug), leadCrewId, helpers (JSON array of crew ids), duration, remaining, status (`ACTIVE|PENDING|DONE`).
- **One active task per crew member** (as lead or helper). A crew member may hold a chain of PENDING tasks (FIFO pre-queueing); when their current task completes on a turn tick, the next pending auto-starts. Helper slots are fixed at queue time; a committed helper is busy for that task's duration and cannot be added elsewhere meanwhile.
- **Costs deduct at queue time** (ship work & research consume resources/parts immediately — prevents mid-turn affordability races); effects apply on completion. Jumps/explores have no resource cost.
- `save.turn` increments only via `POST /api/turn`. The tick: for every ACTIVE task, remaining−=1; tasks reaching 0 resolve (per-type resolvers reusing existing logic from save.ts / ship / research), grant XP (§6), write events, free the team, auto-start any FIFO successor, then run mission-completion checks for what just resolved.
- **In-transit rule:** while any JUMP task is ACTIVE, no new tasks can be queued (409). Pending jumps do not block; already-active/pending non-jump work continues through the jump and resolves against its target regardless of current system location. Only one JUMP may exist at a time (active or pending) — prevents two arrivals on one tick.
- **Mission accept becomes instant/free** (paperwork: no crew, no turn cost). Completion checks run when the satisfying task completes (jump arrival → DELIVER; scan → EXPLORE/FIND), same as today's hooks moved into tick resolution.
## 4. Duration Model: Work Points & Team Power
`duration = max(1, ceil(work(action) / totalPower) + mismatchPenalty + traitModifierSum)`
- `work(action)` — work points (balance knobs, §11): EXPLORE=4 · JUMP=hops×3 (single 3 / two-hop 6) · SHIP work=6 · RESEARCH=9. Durations are deliberately much longer than today; teams and skill levels claw back the time.
- `totalPower` — sum over lead + helpers of each member's power band in that action type's skill: L0–L2 → 1, L3–L4 → 2, L5 → 3. (Until #45 lands there is no skill table, so every crew member contributes power 1 — teamwork already works; progression layers on top.)
- `mismatchPenalty` — +1 turn if the LEAD's role is not a preferred role for the action type (preferred: JUMP → PILOT/NAVIGATOR · EXPLORE → SCIENTIST · SHIP work → ENGINEER/TECHNICIAN · RESEARCH → SCIENTIST). Helpers are never penalised. STEADY UNDER PRESSURE cancels it.
- `traitModifierSum` — sum of the LEAD's trait modifiers for this action type (§5 table). Helper traits do not apply.
Solo durations (power 1, no mismatch/traits): EXPLORE 4 · JUMP single 3 / two-hop 6 · SHIP work 6 · RESEARCH 9. A maxed specialist + helpers converges back to ~1–2 turns (§6 examples).
## 5. Trait Mechanics — all 20 traits
Duration modifiers (lead only, additive in §4 formula):
| Good trait | Effect |
|---|---|
| STEADY UNDER PRESSURE | Cancels role-mismatch penalty on any action |
| RECKLESS COURAGE | JUMP −1 turn |
| CALM IN A CRISIS | EXPLORE −1 turn |
| FIXES ANYTHING | SHIP work −1 turn |
| READS STARS BLIND | JUMP −1 turn |
| QUICK HANDS | Any action −1 turn |
| IRON WILL | RESEARCH −1 turn |
| Bad trait | Effect |
|---|---|
| RIGID — SLOW TO ADAPT | RESEARCH +1 turn |
| OVERCAUTIOUS | Any action +1 turn |
| SLOWS THE CREW DOWN | Any action +1 turn |
| CARELESS WITH FUSES | SHIP work +1 turn |
| HOARDS RATIONS | SHIP work +1 turn |
| FORGETS TO EAT | EXPLORE +1 turn |
| ARGUES WITH EVERYONE | RESEARCH +1 turn |
| DOUBTS THE CAPTAIN | JUMP +1 turn |
Outcome effects (#43): FINDS HIDDEN THINGS → exploration hauls gain +1 item (extra roll on the same seeded per-planet stream) · TALKS TO ANYONE → crew-recruit chance 18% → 30%.
Encounter-ready STANDBY (activate when random encounters land; shown as "STANDBY" in UI): SHARP SHOOTER, SHORT FUSE, JINXED IN A DOGFIGHT.
## 6. Skill Progression & Specialisation
- **Skills** — one per action type: NAVIGATION (jumps), SCANNING (exploration), ENGINEERING (ship work), RESEARCH. Stored in a new `crew_skills` table (schema v8): saveId, crewId, skill, level, xp — extensible for future COMBAT/MEDICAL skills.
- **Roles keep their names** — the existing 8 roles are unchanged; skills are a separate progression axis. A role matters only for: starting levels and the lead mismatch penalty.
- **Starting specialisation (role → L2 in its skill(s)):** PILOT/NAVIGATOR → NAVIGATION · SCIENTIST → SCANNING + RESEARCH · ENGINEER/TECHNICIAN → ENGINEERING · COMMANDER/MEDIC/GUNNER → all skills L0 (generalists; MEDIC/GUNNER become relevant with encounters).
- **Buildup by doing the job:** on task completion, every assigned crew member (lead and helpers) gains +1 XP in that action type's skill. Level thresholds (cumulative completions): L1=5 · L2=10 · L3=15 · L4=20 · L5=25 (cap).
- **Power bands:** L0–L2 → 1 · L3–L4 → 2 · L5 → 3 (§4).
- **Titles** (display only, role name preserved): highest skill level ≥5 → "MASTER <ROLE>", ≥3 → "SENIOR <ROLE>". Level-ups write an event ("BEN OKONKWO is now a SENIOR PILOT — NAVIGATION L3").
Duration examples (work values §4): EXPLORE solo rookie 4 turns → solo master 2 → master+rookie helper 1 · JUMP two-hop solo rookie 6 → solo master 2 → two masters 1 · SHIP work solo rookie 6 → solo master 2 · RESEARCH solo rookie 9 → solo master 3 (stays the long pole by design).
## 7. API Changes
- All action endpoints become queueing operations and gain crew params: `POST /api/jump {targetSystemId, leadCrewId, helperCrewIds[]}` · `POST /api/explore {planetId, ...}` · `POST /api/ship/{upgrade|expand|build} {...}` · `POST /api/research {techSlug, ...}`. Validation at queue time: unknown crew → 404; busy/committed crew → 409; in-transit or second jump → 409; unreachable target / unaffordable cost / build gate → existing error codes. No turn increment on queue.
- New `POST /api/turn` — advances exactly one turn (§3 tick), returns full snapshot.
- Snapshots (world/system/crew responses) gain an active+pending `tasks` array: {id, type, targetLabel, leadName, helperNames[], remaining} so the UI can render the task strip and disable busy crew. The admin CRUD panel picks up new tables automatically.
- OpenAPI spec in lib/openapi.ts updated accordingly; DDL in lib/db/client.ts kept in sync with lib/db/schema.ts (repo rule).
## 8. UI
- **NEXT TURN button fixed bottom-right** with current turn counter ("TURN 12") — the game's heartbeat; pressing it with an empty queue is a free skip.
- **Crew picker:** action buttons open an inline team builder — pick lead (radio), toggle helpers (checkboxes); live-computed duration updates as the team changes ("Lead AVA VANCE · PILOT L2 + 1 helper → 3 turns"); busy/committed members disabled with reason.
- **Active-tasks strip** (bottom-left): each task's type, target, team, remaining turns; pending FIFO tasks shown dimmed.
- **CREW tab:** per member — role (+title), traits, and the 4 skill levels; STANDBY traits labelled as encounter-ready.
- **Event log:** task-completion and level-up events.
## 9. Determinism & Testing
- Duration is a pure function `(actionType, hopCount, lead, helpers) → number` — unit-tested across work values × power bands × mismatch × trait matrix.
- XP/level-ups are deterministic (no rng). Trait outcome effects (#43) draw from the existing seeded per-planet streams (same save+planet → same haul), keeping everything reproducible.
- Unit tests (colocated *.test.ts): duration matrix; queue validation rules (busy 409, in-transit 409, second jump 409); tick resolution (completion effects, XP grant, level-up event, FIFO auto-start, mission checks); cost deduction at queue time.
- E2E (CI-only per AGENTS.md): assign team → NEXT TURN ×N → effect lands; parallel tasks complete independently; pre-queued FIFO starts after predecessor completes; in-transit queuing blocked; empty-queue skip works.
## 10. Delivery Plan — Gitea issues as TODO list
One issue = one feature branch/PR/version bump, per AGENTS.md loop (branch off master → version bump + CHANGELOG entry → local verify `tsc --noEmit` / `vitest run` / `playwright test --list` → push, CI auto-merge on green → tag → manual deploy → close issue with comment).
| Issue | Version | Scope |
|---|---|---|
| #44 Crew-driven actions + NEXT TURN engine | v0.10.0 | tasks table (schema v7; backfill creates empty table for existing saves), POST /api/turn tick, all action endpoints → queueing with lead+helpers, duration model (§4; power 1 for everyone until #45), trait duration modifiers (§5), UI: NEXT TURN button bottom-right + turn counter, crew picker with live duration, task strip. OpenAPI update. Rework existing unit/e2e assertions from instant-resolution to the queue+tick model |
| #45 Skill progression & specialisation | v0.11.0 | crew_skills table (schema v8; idempotent backfill seeds rows per role mapping for existing saves), XP on completion for all assigned crew, level-up events, power bands active (§6), UI: skill display in CREW tab + titles, picker shows per-member contribution |
| #43 Trait outcome effects | v0.12.0 | FINDS HIDDEN THINGS → hauls +1 item; TALKS TO ANYONE → recruit chance 30% |
## 11. Balance Knobs (single tuning surface)
All constants live in one new module `lib/game/crew-costs.ts` (alongside ship-costs.ts): WORK_POINTS per action type, POWER_BANDS, XP_THRESHOLDS, STARTING_LEVELS per role, recruit chance values. Tuning a number = editing one file; the duration formula and resolvers read from it.