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>
apps/api runs ts-jest with isolatedModules: true, which skips cross-file
type checking. Task 3 proved the gap concretely: the suite reported
233/234 green while npm run build reported 21 real errors across 6
files. Every full-suite step now runs test && build, with a Global
Constraints note explaining why the build half is not redundant.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1. inventory.service.ts (API production code) surfaces requiredLevel in
its response DTO. Task 3 deletes the entity column, so this would
fail to compile and keep shipping a dead field. No task covered it:
Task 10 swept only test fixtures, Task 12 only the web model. Folded
into Task 10 as a new Step 0.
2. No web-side sweep task existed. Task 12's model changes break
CharacterResponse/InventoryItem fixtures in app.spec.ts,
world.store.spec.ts, and inventory.store.spec.ts, none of which
Tasks 13-16 touch. Added Task 17 mirroring Task 10's sweep rule.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
16 tasks: pure-function power curve and rank resolver, one migration
covering Character/ItemDefinition/ItemType/CombatReward changes plus
five new tables, three new domain services (Renown, Reputation,
TurnIn), XP removal from the combat reward pipeline, requiredLevel
gate removal, seed content for Grenzwacht/Räuberabzeichen/turn-ins,
a fixture sweep, and the web-side Renown/Reputation surfaces.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records every judgment call the spec left open — Renown/Reputation
data model, service signatures, migration strategy, combat reward
pipeline changes, and API/frontend surface — as an explicit, auditable
ruling with its cost if wrong, per instruction to implement this slice
without confirmation stops.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Also updates the pre-existing exact-equality playerState assertion in
combat-equipment-integration.spec.ts, which broke from the new
potionsRemaining field but wasn't listed in the task brief's file scope.
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 <noreply@anthropic.com>
Covers the engine rewrite for HEAVY_STRIKE/SHIELD_BASH/DEFEND/POTION,
deterministic monster telegraphing/interrupt, the DB migration for the
new combat event types, and the web action bar + telegraph banner.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes 4 Important findings from the final whole-branch review:
- /inventory never called WorldStore.load(), leaving the TopBar stuck on
"loading" and characterLevel() silently defaulting to 1 for any character
above level 1. Mirrors the same guard already used in HuntPageComponent.
- The combat/equipment snapshot-immutability integration test asserted only
status/round, never the actual playerState snapshot the whole test claims
to prove is untouched after a post-fight equip.
- Documented (comment only, no behavior change) that the demo character's
armor dropping from the old hardcoded 6 to 0 is an intentional,
spec-sanctioned tradeoff (Slice 0.5 spec Section19), not a bug.
- inventory.store.spec.ts's equip test used an identical inventory fixture
before and after equip(), so a regression dropping the post-equip
inventory re-fetch would still have passed. Now asserts the refetched
fixture is actually reflected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The equippedItemInSelectedSlot computed is the riskiest logic this
component owns, but every existing test mocked selectedItem() as null,
so the slot-matching branch never ran and a regression to "any equipped
item" would have gone unnoticed. Adds a test with three items across two
slots that asserts the detail panel receives the same-slot equipped item,
not just any equipped item.
The CharacterItem idempotency check was looking up by the seed's own
literal id instead of the (characterId, itemDefinitionId) unique index
that CharacterItem actually enforces. If the demo character had already
looted a worn-short-sword naturally, re-running the seed would miss
that row and try to insert a colliding duplicate, breaking seeding
instead of being a safe no-op. Look up by the real domain key and reuse
whatever id is found when wiring up the CharacterEquipment row.