The five offer tests this branch added all read the SHOP_OFFERS constant; none
ran the seed, so the one guarantee the stable-id design exists to provide --
re-seeding does not duplicate content -- was covered by nothing. This runs
`seedVisibleVerticalSlice` twice against an in-memory shop-offer repository and
asserts the five stable ids survive.
Confirmed catchable: reverting the conflict target to
['shopId', 'itemDefinitionId'] leaves 4 rows under the fake, because both bag
offers carry `itemDefinitionId: null` and collapse into one. On Postgres it
would fail outright.
The prices (12/30/40/35/60) and thresholds (reputation 25 and 40, renown 3) are
pinned in the same test: AGENTS.md §39 forbids silent rebalancing, and the
structural tests never looked at a number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Case 7 ("price is still required even when the reputation condition is met")
and case 3 ("sufficient regional reputation allows purchase") both built
fixtures with `conditions: []`. With no requirement present, none can be met,
so neither test touched the gate it was named after. Both now carry a
satisfied REGION_REPUTATION condition.
Case 4 ("insufficient World Renown blocks purchase") had no test at all. It
matters because a renown block must surface as SHOP_OFFER_LOCKED rather than
MERCHANT_REPUTATION_TOO_LOW -- renown is not the merchant's regard, and
telling the player to go and earn reputation would point at the wrong bar.
Verified by widening the reputation-blame check to include WORLD_RENOWN, which
fails the new test alone.
Also pins a bag offer's description to empty, so the duplicate capacity line
cannot come back through the API side.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`purchase` listed a shop's offers with no ordering while `getShopView` orders
by `sortOrder`, so the two paths answered "which offer does this key mean" by
different rules, one of them at the database's discretion. Harmless today
because item and bag keys are disjoint, but not a difference worth keeping.
The faction lookup behind requirement labels also read every faction while the
condition engine only matches enabled ones, so a gate on a disabled faction
would have shown that faction's name next to a requirement the engine treats
as unmeetable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`resolveTarget` gave a bag target the same string for `description` and
`effectSummary`, and the shop row renders both, so the slice's two flagship
offers showed "Capacity: 5 Raider Trophies" on consecutive lines. Spec §5's
worked example shows it once.
A bag definition carries no flavour text of its own, so the description is now
empty and the row omits the span entirely rather than emitting an empty one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lint was intentionally deferred through tasks 1-7 to keep each task's
diff scoped. Running it now only reformats line-wrapping in the four
shop/seed files this slice already modified; the ~86 pre-existing
problems in npcs, travel, the e2e spec and elsewhere are untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The migration spec only asserted SQL substrings against a mocked
QueryRunner and had no getMetadataArgsStorage() check that ShopOffer's
column options and partial unique indexes actually match the new
schema. Since ts-jest does not type-check in this package
(isolatedModules: true), this is the only automated guard against
entity/migration drift -- matches the house convention in
npc-system.migration.spec.ts and loot-bags.migration.spec.ts.
Adds a second, mutually exclusive target column (loot_bag_definition_id)
and a bypass_conditions column to shop_offers, so a later slice's quest
referral can open one offer that reputation alone would not. Keeps
shop.service.ts compiling against the now-nullable itemDefinition with
temporary non-null assertions; Task 4 replaces them with a real branch
on offer kind.
Fixes 5 residual findings from the re-review of Playable Slice 0.6.6's
whole-branch review: two twice-hit spec files still carried invented
or seeded German fixture values (reputation-display faction name,
inventory-detail-panel item description and item name), a world store
fixture still used a German location description, and a dev comment
in reputation-content.ts referenced a faction name that no longer
exists in the code. All are literal string substitutions using
already-translated canon English text; no keys, ids, or logic changed.
- TRAVELLING spelling: fix TRAVELING -> TRAVELLING in travel-panel.component.html
to match UK spelling used everywhere else (world/hunting/combat stores + API
error messages)
- Grenzwacht -> Border Watch: fix stale German faction display name in
reputation-display, reputation.service/controller, turn-in.service specs,
and the vertical-slice seed spec test title
- Suedtor von Graufurt -> Graufurt South Gate: fix stale ASCII-transliterated
German location name in hunting.service.spec.ts
- Aschenfelder(n) -> Ashen Fields: fix stale German location name/description
in top-bar.component.spec.ts and context-panel.component.spec.ts (key
identifiers left untouched)
- Fix 7 test titles still describing translated error messages as "German"
across world/hunting/inventory/combat store specs
- README: update demo location names from German to their current English
names (Graufurt South Gate, Burned Road)
- item-rarity.enum.ts: fix now-false comment claiming rarity labels are
German; the frontend RARITY_LABELS map is English
Pure literal-string/comment substitutions; no keys, ids, or logic changed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACkMEDYiwtcfchqKkiUJNX
visible-slice.e2e-spec.ts still asserted the German monster names
'Aschenratte'/'Straßenräuber' that Task 1 already translated in the
seed data. Update the assertion to the actual English seeded values
'Ash Rat'/'Road Bandit'. This file lives outside the *.spec.ts glob
(it's matched by jest-e2e.json's *.e2e-spec.ts pattern instead), so it
was missed by prior sweeps scoped to the unit-test glob.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACkMEDYiwtcfchqKkiUJNX
Task 10 review sweep found two German strings the closing sweep missed:
'Abgenutztes Kurzschwert' (8 occurrences across 5 spec files, the real
seeded worn-short-sword item) and 'Gepolsterter Helm' (1 occurrence, a
synthetic test-only bonus-hp-helm fixture). Translated both to their
English equivalents per the item-content.ts glossary and slice style.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACkMEDYiwtcfchqKkiUJNX
Repo-wide grep sweep (apps/web/src, apps/api/src) for leftover German
content strings missed by Tasks 1-9, mostly in spec fixtures/assertions
that mirror already-translated seed content (monster/item names, POI
titles and action labels, location names/descriptions) plus a few real
source-file gaps:
- inventory-detail-panel.component.ts: STAT_LABELS (Waffenschaden,
Angriff, Leben, Rüstung) were never translated by Task 6; now match
the identical English labels already used in inventory-page.component.html.
- app-shell.component.html: aria-label="Spielinhalt" -> "Game content"
(this file was outside every prior task's file list).
- location-interaction-panel.component.spec.ts: dead NPC-quote fixture
translated to match the real wounded-scout POI text.
Code comments referencing German source-spec section titles or
not-yet-seeded faction names, and inline calculation-documentation
comments, are left as-is per the source spec's scope (dev-facing
comments may stay German).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACkMEDYiwtcfchqKkiUJNX
Translates the six REPUTATION_RANKS labels in reputation-rank.ts
(Geachtet/Vertraut/Anerkannt/Bekannt/Geduldet/Fremder -> Esteemed/
Trusted/Recognized/Known/Tolerated/Stranger), per the glossary in
docs/superpowers/specs/2026-08-21-english-game-content-foundation-design.md
section 4. Keys and thresholds are unchanged.
Also updates reputation.service.spec.ts and reputation.controller.spec.ts,
which asserted the German rankLabel value returned by
resolveReputationRank() via the service/controller layer.
Converts all German player-facing content in the API's seed files to
English per the English Game Content Foundation glossary: item
name/description, monster name, location name/description, all
local-location POI/action content, and the border-guard reputation
faction. Technical keys are unchanged.
Reconciles Slice 0.6.5 (Renown & Reputation Foundation) against master's
persistent-HP-and-regeneration slice, which landed independently and
touches several of the same files (Character entity, CombatService,
EquipmentService, the inventory detail panel).
Conflict resolutions:
- CharacterStatsService/EquipmentService constructor wiring: kept
master's CharacterVitalsService injection, which this branch's
version of the same files didn't have yet.
- CombatService.performAction: kept master's HP-guard logic
(characterTooWounded, vitals pause-on-enter) alongside this branch's
multi-line calculate() call style.
- Inventory detail panel (.html/.ts/.scss/.spec.ts): master had
redesigned the panel (wrapping section, rarity styling, flavour
text, a shared inventory.labels.ts) on top of the OLD level-gated
component, since this branch's removal of the level gate (R4, Task
8/14) hadn't reached master yet. Kept master's visual redesign in
full, but with the level-gate concept removed throughout: no
requiredLevel stat block, no meetsLevelRequirement() branch in the
equip button, no now-dead .detail__value--unmet SCSS rule. Kept both
branches' independent tests (non-equippable-item, flavour-text).
- inventory-page.component.ts: dropped master's dead characterLevel
computed (nothing in the template read it, and the level concept is
gone); kept its independent bagCells/bagUsed/bagCapacity grid
feature, which has nothing to do with renown or level.
Post-merge fixture repairs (three files failed the Angular bundle
compile because they predate master's hpRegenPerSecond/hpRegenSince
fields or master's item description field, neither conflict-marked
since git considered them non-overlapping edits):
- app.spec.ts: a 'renders loaded character values' test added on
master after this branch forked still used the abolished level/
experience fields on its decoy fixture -- retargeted to renown.
- inventory-detail-panel.component.spec.ts: the ashPelt fixture added
by this branch's final-review follow-up predates master's required
description field.
- top-bar.component.spec.ts: this branch's fixture predates master's
required hpRegenPerSecond/hpRegenSince fields.
No database migration touches the same column: master's
1792000000000-AddHpRegeneration only adds characters.hp_regen_since,
independent of this slice's 1791000000000-CreateRenownAndReputation.
Timestamp ordering between the two was already correct with no rename
needed.
Verified: API 288/288 (267 from this slice + 21 from master), API
build zero errors, web 237/237 (230 from this slice + 7 from master).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The branch review approved the slice with no blocking findings. These
are the substantive non-blocking ones.
N1, the one no per-task review could see: every seeded monster rolls
silverMin/silverMax = 0 (R7), so the victory screen showed "Silber +0"
after every fight in the shipped game. Three tasks were each correct in
isolation -- the mechanism stays, the values are zero, the field still
exists -- and the composite was wrong. Now conditional, with a test.
This does not contradict R16: R16 deleted the XP block because the
field ceased to exist, leaving nothing to hide. Silver still exists and
can be non-zero, so a conditional is the right tool.
N2: world.store.ts still said a combat "granted XP and silver". Same
false-fact-in-a-comment defect fixed in b5bcd50, one file over.
N4: resolveReputationRank threw a TypeError on negative input, since
findIndex returns -1 and REPUTATION_RANKS[-1] is undefined. Unreachable
today, but grantReputation is public and accepts any number.
N5/N6: grantReputation resolved factions without the enabled filter the
read path applies, so a disabled faction could accumulate invisible
reputation -- "disabled" was not actually a kill switch. The dense read
also had no ORDER BY, so the list could reorder between requests.
N8: design 13 requires silver stay 0 for every seeded monster; only two
of four were pinned. Re-adding silver to the others would have shipped
silently.
N9: spec 36's "a normal kill grants no Renown" had no test. It was
structurally guaranteed but unasserted -- now locked down against a
later slice wiring renown into combat.
N10: an impossible renown: 0 fixture, and RENOWN_MIN exported but never
used to clamp the floor.
API 268/268, web 230/230, API build zero errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
When I dropped Silber and Erfahrung from the burned road's seeded
reward preview (e5752c7, because R7 zeroed every monster's silver on
that road), I updated the server content but not the web fixture that
mirrors it. burnedRoadFixture() still advertised Silber, and the
sidebar spec still asserted it rendered -- so the web tests were
proving the UI shows a category the API no longer sends.
Also clears dead level/experience fixture fields and an experience key
in a reward-service mock from combat-equipment-integration.spec.ts.
They were inert (nothing read them) but they date from after the
API-side sweep and describe fields the entity and DTO no longer have.
API 267/267, web 229/229.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The re-anchor test in equipment.service.spec.ts anchored hpRegenSince
at null, which meant currentHp already equaled the old maxHp and
nothing in equip() could change it whether settle() ran correctly, was
a no-op, or read the wrong (post-change) maxHp. Anchor currentHp at 90
with hpRegenSince 600s before the harness's fixed clock so settle()
must actually cap accumulated regen at the OLD maxHp for the assertion
to hold, catching both failure modes the original fixture missed.
- Update CharacterStatsService constructor to accept CharacterVitalsService
- Compute currentHp via CharacterVitalsService.effectiveHp() instead of raw pass-through
- Add hpRegenPerSecond and hpRegenSince fields to EffectiveCharacterStats return type
- Update spec with new test cases for regeneration calculation and field pass-through
- Equipment and combat tests now fail as expected (separate tasks will fix constructor calls)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The location view's "Mögliche Belohnungen" preview listed Silber and
Erfahrung. This slice made both false:
- Experience no longer exists as a concept (spec 1).
- Every monster seeded on this road now rolls silverMin/silverMax = 0
(design R7), so a kill yields no silver. Silver reaches the player
through turn-ins instead.
The list's own comment states the rule it was breaking: the view may not
promise a drop the roll does not guarantee (spec 8). Equipment and
material stay -- the loot tables still back both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
combat.service.spec.ts mocked CombatRewardService, so it never noticed
that design R8 removed CombatRewardDto.experience. Its mocks returned
{ experience, silver, items } and one assertion required experience: 8
back out -- encoding a contract the production DTO no longer has. A mock
lying about the real shape is worse than no test: it would keep passing
if the real DTO drifted further.
The rollback test deliberately wrote two independent fields so its
assertions could not pass vacuously ("rolled back" vs "never written").
Swapped the abolished experience for renown rather than dropping to a
single field, preserving that intent.
Monster level is untouched -- it is a monster stat this slice keeps, not
the abolished character level.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Design ruling R7 abolishes XP as a concept and says the column goes with
it, but no task in the plan actually dropped it -- the plan only dropped
characters.experience and combat_rewards.experience_granted. Task 9
removed experienceReward from the seed literals, leaving
monster_definitions.experience_reward as a NOT NULL column with no
default that nothing supplies. The first monster insert against a real
database would have failed on a constraint violation.
No suite here could have caught it: none of them connect to Postgres.
Drops the column in the slice migration (which has never been run, so
amending it in place is correct rather than stacking a second one),
removes the entity field, and clears the three test fixtures that still
set it. silver_min/silver_max deliberately stay -- spec 15 keeps a
direct currency drop available as a lore-valid exception, and XP has no
such carve-out.
Also retargets the seed idempotency test off renown: 1, which is the
seed's own default and so could not distinguish "preserved" from
"reset to default".
NOTE ON SCOPE: this commit also absorbs a Prettier reformatting pass
that was already sitting uncommitted in the working tree, which is why
it touches ~59 files. That churn is purely cosmetic line-rewrapping --
verified by inspection, and the suite is green at 267/267 with the build
at exactly the 3 expected errors owned by Tasks 10 and 11. The repo is
not Prettier-clean at baseline (119 files still flagged), so this was a
partial run by an earlier step, not a deliberate repo-wide format.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The comment sat two lines above the deleted experience computation and
still named XP as something the guard protects. Only silver survives.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TurnInService locks and validates the character row before touching
inventory, silver, or reputation, but no test exercised that branch.
Both sibling services in this slice (renown, reputation) have the
equivalent test; this closes the gap.
`characterNotFound` is shared from travel.errors and so is not a
TurnInDomainError -- assert on the wire contract (code + status) the
way reputation.service.spec.ts does for the same shared error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>