Merge branch 'slice/0.10-abandoned-watchpost'

This commit is contained in:
Bastian Wagner
2026-08-23 17:36:06 +02:00
5 changed files with 105 additions and 82 deletions

View File

@@ -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
@@ -317,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.

View File

@@ -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