From 96d04d8ba395b329d9e4bf48fcbb94ccd321fa01 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Thu, 20 Aug 2026 21:04:19 +0200 Subject: [PATCH] Fix plan defect found in SDD pre-flight scan: Task 8 fixture patch hunt-page.component.spec.ts and resume-combat.spec.ts each build a direct type-annotated Combat literal that the new required CombatPlayer/CombatMonster fields would break at compile time. Verified with an isolated tsc --strict probe, and used the same probe to confirm the combat.service.spec.ts `as Combat` fixtures do NOT break (structural widening through the type assertion), so no change needed there. Co-Authored-By: Claude Sonnet 5 --- ...20-playable-slice-0.6-full-first-combat.md | 66 +++++++++++++++++-- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/docs/superpowers/plans/2026-08-20-playable-slice-0.6-full-first-combat.md b/docs/superpowers/plans/2026-08-20-playable-slice-0.6-full-first-combat.md index ff47231..850aa19 100644 --- a/docs/superpowers/plans/2026-08-20-playable-slice-0.6-full-first-combat.md +++ b/docs/superpowers/plans/2026-08-20-playable-slice-0.6-full-first-combat.md @@ -288,7 +288,7 @@ git commit -m "feat(combat): migrate combat_event_type_enum for Slice 0.6 event - Consumes: nothing new (pure type changes). - Produces: `CombatIntent = 'HEAVY_ATTACK'` (exported from `combat-engine.types.ts`). `CombatEngineCombatantStats` gains optional `potionsRemaining?: number` and `pendingAction?: CombatIntent`. `CombatCombatantState` (entity) gains optional `pendingAction?: CombatIntent`; `CombatPlayerState` gains required `potionsRemaining: number`. Task 5 (engine) and Task 7 (service DTOs) both depend on these exact field names. -This task is pure types with no runtime behavior, so there's no new test — its correctness is verified by the whole project still compiling and the existing suite staying green. +This task is pure types with no runtime behavior, so there's no dedicated new test — its correctness is verified by the whole project still compiling and the existing suite staying green. (Pre-flight check: the pre-existing `{ ... } as Combat` fixtures in `combat.service.spec.ts` that build `playerState` without `potionsRemaining` were checked against this change with an isolated `tsc --strict` probe — a type assertion's structural-widening direction accepts them since `Combat` is assignable to the fixture's narrower inferred type, so nothing there needs touching. Direct type-annotated literals elsewhere are a different story — see Task 8's pre-flight ruling.) - [ ] **Step 1: Extend the engine types** @@ -385,7 +385,7 @@ Leave the rest of the file (the `@Entity` class itself) unchanged. - [ ] **Step 3: Run the full API test suite to confirm nothing broke** Run: `npm run test --workspace=@ashen-realms/api` -Expected: PASS — these are purely additive optional fields plus one new required field (`potionsRemaining`) that nothing constructs yet outside of test fixtures using object literals typed loosely (`as Combat`), which TypeScript doesn't strictly check field-by-field. +Expected: PASS — these are purely additive optional fields plus one new required field (`potionsRemaining`) that nothing in the existing suite constructs directly: the only `Combat`-typed test fixtures use `{ ... } as Combat`, and a type assertion accepts this because `Combat` structurally widens into the fixture's narrower inferred type. - [ ] **Step 4: Commit** @@ -1115,11 +1115,15 @@ git commit -m "feat(combat): validate potions, persist telegraph state, and expo - Modify: `apps/web/src/app/core/api/game-api.models.ts:151-188` - Modify: `apps/web/src/app/features/combat/combat.store.ts` - Modify: `apps/web/src/app/features/combat/combat.store.spec.ts` +- Modify: `apps/web/src/app/features/hunting/hunt-page/hunt-page.component.spec.ts:50-65` (fixture only, see Step 4) +- Modify: `apps/web/src/app/core/resume-combat.spec.ts:8-23` (fixture only, see Step 4) **Interfaces:** - Consumes: nothing new outside the web app. - Produces: `CombatAction` (web) = `'ATTACK' | 'HEAVY_STRIKE' | 'SHIELD_BASH' | 'DEFEND' | 'POTION'`. `CombatEventType` (web) gains `'HEAL' | 'DEFEND' | 'TELEGRAPH' | 'INTERRUPT'`. `CombatPlayer` gains `potionsRemaining: number` and `potionsMax: number`. `CombatMonster` gains `pendingIntent: 'HEAVY_ATTACK' | null`. `CombatStore.performAction(action: CombatAction): Promise` replaces `CombatStore.attack(): Promise` — consumed by Task 9 (combat page component). +**Pre-flight ruling:** making `potionsRemaining`/`potionsMax`/`pendingIntent` required breaks two more `const x: Combat = {...}` fixtures outside the combat feature — `hunt-page.component.spec.ts` and `resume-combat.spec.ts` both build a full `Combat` literal for their own unrelated tests (hunt resumption, not combat mechanics). A direct type-annotated object literal missing required properties is a guaranteed TypeScript error (stricter than the `as Combat` cast fixed in Task 4). Step 4 below patches both. + - [ ] **Step 1: Extend the web models** In `apps/web/src/app/core/api/game-api.models.ts`, replace lines 151-188 (from `export type CombatStatus = ...` through the end of the `Combat` interface): @@ -1329,15 +1333,63 @@ Also add one new test, right after the "sends only the ATTACK action..." test, t }); ``` -- [ ] **Step 4: Run to verify the store spec passes** +- [ ] **Step 4: Patch the two out-of-feature fixtures** -Run: `npx ng test --watch=false --include='**/combat.store.spec.ts'` (from `apps/web`) -Expected: PASS +These two files build their own `Combat` fixture for unrelated tests (hunt resumption) and will fail to compile once `CombatPlayer`/`CombatMonster` gain required fields. -- [ ] **Step 5: Commit** +In `apps/web/src/app/features/hunting/hunt-page/hunt-page.component.spec.ts`, change the `startedCombat` fixture (around lines 50-65): + +```ts +const startedCombat: Combat = { + id: 'combat-2', + status: 'ACTIVE', + round: 1, + player: { name: 'Aric Duskwalker', maxHp: 100, currentHp: 100, potionsRemaining: 2, potionsMax: 2 }, + monster: { + key: 'road-bandit', + name: 'Straßenräuber', + level: 3, + maxHp: 75, + currentHp: 75, + artworkPath: '/images/enemies/RoadBandit.png', + pendingIntent: null, + }, + events: [], + rewards: null, +}; +``` + +In `apps/web/src/app/core/resume-combat.spec.ts`, change the `runningCombat` fixture (around lines 8-23): + +```ts +const runningCombat: Combat = { + id: 'combat-running', + status: 'ACTIVE', + round: 4, + player: { name: 'Aric Duskwalker', maxHp: 100, currentHp: 62, potionsRemaining: 2, potionsMax: 2 }, + monster: { + key: 'road-bandit', + name: 'Straßenräuber', + level: 3, + maxHp: 75, + currentHp: 30, + artworkPath: '/images/enemies/RoadBandit.png', + pendingIntent: null, + }, + events: [], + rewards: null, +}; +``` + +- [ ] **Step 5: Run to verify everything this task touches compiles and passes** + +Run: `npx ng test --watch=false --include='**/combat.store.spec.ts' --include='**/hunt-page.component.spec.ts' --include='**/resume-combat.spec.ts'` (from `apps/web`) +Expected: PASS — this also proves the Step 4 fixture patches compile, since `ng test` type-checks the whole project before running any spec. + +- [ ] **Step 6: Commit** ```bash -git add apps/web/src/app/core/api/game-api.models.ts apps/web/src/app/features/combat/combat.store.ts apps/web/src/app/features/combat/combat.store.spec.ts +git add apps/web/src/app/core/api/game-api.models.ts apps/web/src/app/features/combat/combat.store.ts apps/web/src/app/features/combat/combat.store.spec.ts apps/web/src/app/features/hunting/hunt-page/hunt-page.component.spec.ts apps/web/src/app/core/resume-combat.spec.ts git commit -m "feat(combat): generalize the web combat action and expose potions/monster intent" ```