From 0dfb62bfc4906619220c204c15457c89afa5622c Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Sun, 23 Aug 2026 17:24:03 +0200 Subject: [PATCH 1/2] fix(api): clean up this slice's own lint violations Fixed five prettier/prettier formatting errors in the three spec files Slice 0.10 wrote or edited (world-discovery.service.spec.ts, local-location-interaction.spec.ts, vertical-slice.seed.spec.ts). Scoped by hand to just those locations so the pre-existing errors elsewhere in the workspace, unrelated to this slice, are left alone. Also corrects the implementation notes, which previously understated the lint state as entirely pre-existing. Co-Authored-By: Claude Opus 5 --- .../src/database/seeds/vertical-slice.seed.spec.ts | 12 +++--------- .../world/discovery/world-discovery.service.spec.ts | 4 +++- .../api/src/world/local-location-interaction.spec.ts | 8 +++++--- .../0.10-Abandoned-Watchpost-implementation-notes.md | 12 ++++++++++++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/apps/api/src/database/seeds/vertical-slice.seed.spec.ts b/apps/api/src/database/seeds/vertical-slice.seed.spec.ts index d8ac3f5..ce662b8 100644 --- a/apps/api/src/database/seeds/vertical-slice.seed.spec.ts +++ b/apps/api/src/database/seeds/vertical-slice.seed.spec.ts @@ -1284,9 +1284,7 @@ describe('seedVisibleVerticalSlice', () => { await seedVisibleVerticalSlice(dataSource); - const hide = itemRepository.rows.find( - (row) => row.key === 'scorched-hide', - ); + const hide = itemRepository.rows.find((row) => row.key === 'scorched-hide'); const mark = itemRepository.rows.find( (row) => row.key === 'raider-warband-mark', ); @@ -1334,9 +1332,7 @@ describe('seedVisibleVerticalSlice', () => { )?.silverReward as number; // The longer trip has to pay, or §10's loop has no pull (slice §6). - expect(silverFor('scorched-hide')).toBeGreaterThan( - silverFor('tough-hide'), - ); + expect(silverFor('scorched-hide')).toBeGreaterThan(silverFor('tough-hide')); expect(silverFor('raider-warband-mark')).toBeGreaterThan( silverFor('bandit-insignia'), ); @@ -1569,9 +1565,7 @@ describe('seedVisibleVerticalSlice', () => { await seedVisibleVerticalSlice(dataSource); - const ashPit = locationRepository.rows.find( - (row) => row.key === 'ash-pit', - ); + const ashPit = locationRepository.rows.find((row) => row.key === 'ash-pit'); const pool = locationMonsterRepository.rows.filter( (row) => row.locationId === ASH_PIT_ID, ); diff --git a/apps/api/src/world/discovery/world-discovery.service.spec.ts b/apps/api/src/world/discovery/world-discovery.service.spec.ts index 6526441..76f1afc 100644 --- a/apps/api/src/world/discovery/world-discovery.service.spec.ts +++ b/apps/api/src/world/discovery/world-discovery.service.spec.ts @@ -46,7 +46,9 @@ function buildService(options: { const locationRepository = { findOneBy: jest.fn(({ key }: { key: string }) => - Promise.resolve(locations.find((location) => location.key === key) ?? null), + Promise.resolve( + locations.find((location) => location.key === key) ?? null, + ), ), } as unknown as Repository; diff --git a/apps/api/src/world/local-location-interaction.spec.ts b/apps/api/src/world/local-location-interaction.spec.ts index b72c8a9..d40b23a 100644 --- a/apps/api/src/world/local-location-interaction.spec.ts +++ b/apps/api/src/world/local-location-interaction.spec.ts @@ -111,9 +111,11 @@ function buildService(options: { alreadyDiscovered?: boolean; }) { const currentLocation = location(BURNED_ROAD_ID, options.pointsOfInterest); - const discover = jest.fn().mockResolvedValue( - options.alreadyDiscovered ? null : { key: 'ash-pit', name: 'Ash Pit' }, - ); + const discover = jest + .fn() + .mockResolvedValue( + options.alreadyDiscovered ? null : { key: 'ash-pit', name: 'Ash Pit' }, + ); const worldDiscovery = { discover, isTravelAllowed: () => Promise.resolve(true), diff --git a/docs/playable-slices/0.10-Abandoned-Watchpost-implementation-notes.md b/docs/playable-slices/0.10-Abandoned-Watchpost-implementation-notes.md index 0913a7d..23e8cd6 100644 --- a/docs/playable-slices/0.10-Abandoned-Watchpost-implementation-notes.md +++ b/docs/playable-slices/0.10-Abandoned-Watchpost-implementation-notes.md @@ -283,6 +283,18 @@ None of these were judged to change behavior a player can hit; they are seams a future slice's tests should tighten, most likely whichever slice next touches the combat engine or the seed's encounter-pool weights. +**Lint state.** `npm run lint --workspace=@ashen-realms/api` (scoped to the +whole API workspace) fails: roughly a hundred `@typescript-eslint` and +`prettier/prettier` errors remain, all in the quest system, the rewards +service, the shops module, migration-runner specs and other files this +slice never touched — confirmed pre-existing by `git blame` timestamps +predating this branch's base commit. This slice's own files pass lint +cleanly; the five formatting violations `eslint --fix` originally found in +`world-discovery.service.spec.ts`, `local-location-interaction.spec.ts` and +`vertical-slice.seed.spec.ts` were fixed by hand, scoped to just those +locations. The pre-existing errors are left alone, out of this slice's +scope and the AGENTS.md rule against unrelated refactoring. + --- ## 7. Outstanding manual verification From 67287faf96d9b8ed3c713e1fbbe5c5e4c9bfc1a0 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Sun, 23 Aug 2026 17:34:08 +0200 Subject: [PATCH 2/2] docs: move acceptance-criteria evidence out of the spec document The 0.10 spec is the project owner's requirements document, not a place for an implementer's verification narrative. Move the per-criterion evidence added for the ten ticked boxes into the implementation notes as a new section 8, leaving the spec's Acceptance Criteria section as just the ten checkboxes. While moving it, make explicit that criterion 2's evidence (server-authoritative travel timing) is inference over a generic TravelService test plus this connection's seed values, not a test exercising the Burned Road to Watchpost leg directly. Co-Authored-By: Claude Opus 5 --- ...bandoned-Watchpost-implementation-notes.md | 82 +++++++++++++++++++ .../0.10-Abandoned-Watchpost.md | 69 ---------------- 2 files changed, 82 insertions(+), 69 deletions(-) diff --git a/docs/playable-slices/0.10-Abandoned-Watchpost-implementation-notes.md b/docs/playable-slices/0.10-Abandoned-Watchpost-implementation-notes.md index 23e8cd6..ae272c5 100644 --- a/docs/playable-slices/0.10-Abandoned-Watchpost-implementation-notes.md +++ b/docs/playable-slices/0.10-Abandoned-Watchpost-implementation-notes.md @@ -329,3 +329,85 @@ duplicate-key error and no duplicated rows (AGENTS.md §8). 6. A Raider Veteran fight shows the guard banner; Shield Bash breaks it. 7. A Burned Hound below 35 % HP shows the enrage banner and hits harder. 8. Scorched Hide and Raider Warband Mark both drop and both sell to Borin. + +--- + +## 8. Acceptance criteria evidence + +`0.10-Abandoned-Watchpost.md` §12 has all ten criteria ticked. This is the +per-criterion evidence for each tick — moved here from the spec document +itself, which should record what a slice must satisfy, not the case for +whether it did. Evidence strength varies by criterion; most rest on a test +that names this exact slice's content directly, one rests on a more +indirect chain and is flagged as such below. + +1. **Full playable location** — seeded as an `OUTPOST` with four points of + interest (hunt, investigate, search, map-out) and its own encounter pool + (`vertical-slice.seed.spec.ts`: "seeds the watchpost as a huntable + outpost"); local content in `local-location.content.ts`. +2. **Server-authoritative travel** — the weakest evidence chain of the ten. + The Burned Road ↔ Watchpost connection is seeded both ways at 15 s / 10 % + ambush (`"connects the burned road and the watchpost both ways without a + gate"`), but nothing exercises that specific leg through + `TravelService`. The server-authoritative mechanism itself — that + `arrivesAt` is computed server-side from the connection's + `travelDurationSeconds` — is covered only generically, by + `travel.service.spec.ts` tests that use other connections. The tick rests + on inference (the mechanism is generic and this connection's seed values + are correct) rather than a test that drives this exact route end to end. +3. **Stronger, distinct pool** — `"gives the watchpost its own encounter + pool"` seeds exactly Road Bandit, Raider Scout, Raider Veteran, Burned + Hound and the rare Raider Captain; `"marks only the captain as a rare + encounter"` confirms the rarity split. +4. **An enemy combining learned mechanics** — the Raider Veteran carries both + `telegraph` (existing, from the Burned Road) and the new `guard` + (`"arms the veteran with a telegraph and a guard on different + cadences"`); the priority between them is covered in + `combat-engine.service.spec.ts`. +5. **Both bag categories matter** — Scorched Hide is seeded `HIDE`, Raider + Warband Mark is seeded `RAIDER_TROPHY` + (`vertical-slice.seed.spec.ts`), and both categories were already + load-bearing bag mechanics before this slice (Slice 0.7.5/0.9). +6. **Tier-1 equipment improved** — the Raider Veteran's own loot table adds + Plunderer Gloves, Reinforced Leather Jacket and Watchman's Leggings on top + of the Raider Warband Mark, and the Raider Scout carries Bandit Hood at a + raised chance (`item-content.ts`); the Road Bandit's own table (already + present) is left untouched at the same values + (`"leaves the road bandit loot table untouched"` pins Bandit Blade at + `0.1800`), so the Watchpost's gear opportunities are additive, not a + rebalance of the Burned Road. +7. **Investigation points to the Ash Pit** — the `inspect-watchpost` hotspot + carries the §8 clue text verbatim and `discoversLocationKey: 'ash-pit'` + (`"points the watchpost investigation at the ash pit"`). +8. **Ash Pit discoverable without a level gate** — the gate is + `requiresDiscovery`, not `minRecommendedLevel`; `WorldDiscoveryService` + contains no level check at all. Covered end to end: the hotspot writes the + discovery (`local-location-interaction.spec.ts`: `"discovers the route the + hotspot points at"`), the map hides/reveals it + (`world.service.spec.ts`: `"hides a gated connection until the character + has discovered it"` / `"shows a gated connection once it has been + discovered"`), and travel itself refuses/allows it + (`world-discovery.service.spec.ts` and `travel.service.spec.ts`, both: + `"refuses a gated route the character has not discovered"` / + `"allows a gated route once it has been discovered"`). +9. **Merchant/reputation loop continues** — both new trade goods have + exchange rules paying Silver and regional reputation, and pay more than + their Burned Road equivalents (`"lets Borin buy both watchpost trade + goods"`, `"pays more for watchpost goods than for road goods"`). No new + code path grants Silver, reputation or Renown directly from a kill; the + pack-wide rule (README.md) that only the exchange grants those was already + enforced before this slice and nothing in Slice 0.10 bypasses it. +10. **English content** — every string seeded for the Watchpost and Ash Pit + (descriptions, hotspot titles and result text, monster flavour text) was + read during this review and is English. + +None of the ten criteria needed the running app to produce this evidence — +even criterion 2's weaker chain is inference over existing automated tests +and seed data, not a claim that required starting the server. What none of +this covers, because it cannot be produced by static evidence: actually +applying migration `1798000000000` to a real PostgreSQL database, confirming +the second `db:seed` run is idempotent against real constraints, and a +hand-played pass through the loop in a browser — including actually walking +the Burned Road → Watchpost leg, which would also close criterion 2's gap. +Those are listed precisely in §7 above as outstanding work for the project +owner. diff --git a/docs/playable-slices/0.10-Abandoned-Watchpost.md b/docs/playable-slices/0.10-Abandoned-Watchpost.md index ae01715..45476e2 100644 --- a/docs/playable-slices/0.10-Abandoned-Watchpost.md +++ b/docs/playable-slices/0.10-Abandoned-Watchpost.md @@ -237,75 +237,6 @@ Graufurt - [x] Existing merchant/reputation loop continues to work. - [x] All player-facing content is English. -### Verification status - -Every criterion above is supported by evidence from the automated test suite, -the build, or the seeded content itself — no criterion here needed the -running app to confirm structurally: - -1. **Full playable location** — seeded as an `OUTPOST` with four points of - interest (hunt, investigate, search, map-out) and its own encounter pool - (`vertical-slice.seed.spec.ts`: "seeds the watchpost as a huntable - outpost"); local content in `local-location.content.ts`. -2. **Server-authoritative travel** — the Burned Road ↔ Watchpost connection - is seeded both ways at 15 s / 10 % ambush (`"connects the burned road and - the watchpost both ways without a gate"`); `TravelService` computes - `arrivesAt` server-side and is covered generically by - `travel.service.spec.ts`. -3. **Stronger, distinct pool** — `"gives the watchpost its own encounter - pool"` seeds exactly Road Bandit, Raider Scout, Raider Veteran, Burned - Hound and the rare Raider Captain; `"marks only the captain as a rare - encounter"` confirms the rarity split. -4. **An enemy combining learned mechanics** — the Raider Veteran carries both - `telegraph` (existing, from the Burned Road) and the new `guard` - (`"arms the veteran with a telegraph and a guard on different - cadences"`); the priority between them is covered in - `combat-engine.service.spec.ts`. -5. **Both bag categories matter** — Scorched Hide is seeded `HIDE`, Raider - Warband Mark is seeded `RAIDER_TROPHY` - (`vertical-slice.seed.spec.ts`), and both categories were already - load-bearing bag mechanics before this slice (Slice 0.7.5/0.9). -6. **Tier-1 equipment improved** — the Raider Veteran's own loot table adds - Plunderer Gloves, Reinforced Leather Jacket and Watchman's Leggings on top - of the Raider Warband Mark, and the Raider Scout carries Bandit Hood at a - raised chance (`item-content.ts`); the Road Bandit's own table (already - present) is left untouched at the same values - (`"leaves the road bandit loot table untouched"` pins Bandit Blade at - `0.1800`), so the Watchpost's gear opportunities are additive, not a - rebalance of the Burned Road. -7. **Investigation points to the Ash Pit** — the `inspect-watchpost` hotspot - carries the §8 clue text verbatim and `discoversLocationKey: 'ash-pit'` - (`"points the watchpost investigation at the ash pit"`). -8. **Ash Pit discoverable without a level gate** — the gate is - `requiresDiscovery`, not `minRecommendedLevel`; `WorldDiscoveryService` - contains no level check at all. Covered end to end: the hotspot writes the - discovery (`local-location-interaction.spec.ts`: `"discovers the route the - hotspot points at"`), the map hides/reveals it - (`world.service.spec.ts`: `"hides a gated connection until the character - has discovered it"` / `"shows a gated connection once it has been - discovered"`), and travel itself refuses/allows it - (`world-discovery.service.spec.ts` and `travel.service.spec.ts`, both: - `"refuses a gated route the character has not discovered"` / - `"allows a gated route once it has been discovered"`). -9. **Merchant/reputation loop continues** — both new trade goods have - exchange rules paying Silver and regional reputation, and pay more than - their Burned Road equivalents (`"lets Borin buy both watchpost trade - goods"`, `"pays more for watchpost goods than for road goods"`). No new - code path grants Silver, reputation or Renown directly from a kill; the - pack-wide rule (README.md) that only the exchange grants those was already - enforced before this slice and nothing in Slice 0.10 bypasses it. -10. **English content** — every string seeded for the Watchpost and Ash Pit - (descriptions, hotspot titles and result text, monster flavour text) was - read during this review and is English. - -What this status does **not** cover, because it cannot be produced by static -evidence: actually applying migration `1798000000000` to a real PostgreSQL -database, confirming the second `db:seed` run is idempotent against real -constraints, and a hand-played pass through the loop in a browser. Those are -listed precisely in the implementation notes -(`0.10-Abandoned-Watchpost-implementation-notes.md`, §7) as outstanding work -for the project owner. - --- ## 13. Out of Scope