Merge branch 'slice/0.10-abandoned-watchpost'
This commit is contained in:
@@ -1284,9 +1284,7 @@ describe('seedVisibleVerticalSlice', () => {
|
|||||||
|
|
||||||
await seedVisibleVerticalSlice(dataSource);
|
await seedVisibleVerticalSlice(dataSource);
|
||||||
|
|
||||||
const hide = itemRepository.rows.find(
|
const hide = itemRepository.rows.find((row) => row.key === 'scorched-hide');
|
||||||
(row) => row.key === 'scorched-hide',
|
|
||||||
);
|
|
||||||
const mark = itemRepository.rows.find(
|
const mark = itemRepository.rows.find(
|
||||||
(row) => row.key === 'raider-warband-mark',
|
(row) => row.key === 'raider-warband-mark',
|
||||||
);
|
);
|
||||||
@@ -1334,9 +1332,7 @@ describe('seedVisibleVerticalSlice', () => {
|
|||||||
)?.silverReward as number;
|
)?.silverReward as number;
|
||||||
|
|
||||||
// The longer trip has to pay, or §10's loop has no pull (slice §6).
|
// The longer trip has to pay, or §10's loop has no pull (slice §6).
|
||||||
expect(silverFor('scorched-hide')).toBeGreaterThan(
|
expect(silverFor('scorched-hide')).toBeGreaterThan(silverFor('tough-hide'));
|
||||||
silverFor('tough-hide'),
|
|
||||||
);
|
|
||||||
expect(silverFor('raider-warband-mark')).toBeGreaterThan(
|
expect(silverFor('raider-warband-mark')).toBeGreaterThan(
|
||||||
silverFor('bandit-insignia'),
|
silverFor('bandit-insignia'),
|
||||||
);
|
);
|
||||||
@@ -1569,9 +1565,7 @@ describe('seedVisibleVerticalSlice', () => {
|
|||||||
|
|
||||||
await seedVisibleVerticalSlice(dataSource);
|
await seedVisibleVerticalSlice(dataSource);
|
||||||
|
|
||||||
const ashPit = locationRepository.rows.find(
|
const ashPit = locationRepository.rows.find((row) => row.key === 'ash-pit');
|
||||||
(row) => row.key === 'ash-pit',
|
|
||||||
);
|
|
||||||
const pool = locationMonsterRepository.rows.filter(
|
const pool = locationMonsterRepository.rows.filter(
|
||||||
(row) => row.locationId === ASH_PIT_ID,
|
(row) => row.locationId === ASH_PIT_ID,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ function buildService(options: {
|
|||||||
|
|
||||||
const locationRepository = {
|
const locationRepository = {
|
||||||
findOneBy: jest.fn(({ key }: { key: string }) =>
|
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<LocationDefinition>;
|
} as unknown as Repository<LocationDefinition>;
|
||||||
|
|
||||||
|
|||||||
@@ -111,9 +111,11 @@ function buildService(options: {
|
|||||||
alreadyDiscovered?: boolean;
|
alreadyDiscovered?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const currentLocation = location(BURNED_ROAD_ID, options.pointsOfInterest);
|
const currentLocation = location(BURNED_ROAD_ID, options.pointsOfInterest);
|
||||||
const discover = jest.fn().mockResolvedValue(
|
const discover = jest
|
||||||
options.alreadyDiscovered ? null : { key: 'ash-pit', name: 'Ash Pit' },
|
.fn()
|
||||||
);
|
.mockResolvedValue(
|
||||||
|
options.alreadyDiscovered ? null : { key: 'ash-pit', name: 'Ash Pit' },
|
||||||
|
);
|
||||||
const worldDiscovery = {
|
const worldDiscovery = {
|
||||||
discover,
|
discover,
|
||||||
isTravelAllowed: () => Promise.resolve(true),
|
isTravelAllowed: () => Promise.resolve(true),
|
||||||
|
|||||||
@@ -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
|
seams a future slice's tests should tighten, most likely whichever slice
|
||||||
next touches the combat engine or the seed's encounter-pool weights.
|
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
|
## 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.
|
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.
|
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. 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.
|
||||||
|
|||||||
@@ -237,75 +237,6 @@ Graufurt
|
|||||||
- [x] Existing merchant/reputation loop continues to work.
|
- [x] Existing merchant/reputation loop continues to work.
|
||||||
- [x] All player-facing content is English.
|
- [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
|
## 13. Out of Scope
|
||||||
|
|||||||
Reference in New Issue
Block a user