Commit Graph

125 Commits

Author SHA1 Message Date
Bastian Wagner
36adc32659 feat(content): gate the trophy pouch, hide bag and bandit blade 2026-08-22 20:04:48 +02:00
Bastian Wagner
80be829309 test(shops): make the gate's guards fail when they are removed 2026-08-22 18:52:23 +02:00
Bastian Wagner
7a5e800a18 feat(shops): sell loot bags, honour bypass conditions, explain locks 2026-08-22 18:34:24 +02:00
Bastian Wagner
7e1b79315e test(shops): cover non-finite and missing-key guards in describeRequirement 2026-08-22 18:18:44 +02:00
Bastian Wagner
149a9521be feat(shops): describe offer requirements and effects in English 2026-08-22 18:14:12 +02:00
Bastian Wagner
9bd28b5e2c feat(conditions): report the current value behind each condition 2026-08-22 18:07:57 +02:00
Bastian Wagner
c295bae63a test(shops): add entity-schema cross-check for sellable loot bags
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.
2026-08-22 17:59:37 +02:00
Bastian Wagner
3b28450fb1 feat(shops): let an offer sell a loot bag and carry bypass conditions
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.
2026-08-22 17:51:47 +02:00
Bastian Wagner
081c9f83f9 docs 2026-08-22 16:41:47 +02:00
Bastian Wagner
dfa62fd152 fix: translate the last five German fixture strings found by the final review's re-review
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.
2026-08-22 09:17:23 +02:00
Bastian Wagner
8418a93329 fix: correct final whole-branch review findings for 0.6.6 English content
- 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
2026-08-22 09:03:13 +02:00
Bastian Wagner
6b32f14bb5 fix(test): translate the e2e smoke test's monster name assertions to English
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
2026-08-21 23:36:18 +02:00
Bastian Wagner
fdd83f2dee fix(test): translate the remaining Worn Shortsword and Padded Helm fixture names
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
2026-08-21 23:18:12 +02:00
Bastian Wagner
1322285b0f fix: close out remaining German player-facing text across web and API tests
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
2026-08-21 22:59:34 +02:00
Bastian Wagner
e48409fbf1 feat(reputation): translate rank labels to English
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.
2026-08-21 20:08:59 +02:00
Bastian Wagner
b8ee56ca1a feat(content): translate seeded items, locations, and reputation faction to English
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.
2026-08-21 20:00:04 +02:00
Bastian Wagner
cedd87f648 Merge branch 'master' into worktree-playable-slice-0.6.5-renown-reputation
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>
2026-08-21 19:23:10 +02:00
Bastian Wagner
23ed527eea fix: address the final whole-branch review's findings
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>
2026-08-21 18:50:30 +02:00
Bastian Wagner
0c9f00d9a8 fix(web): finish the reward-preview sweep the seed change started
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>
2026-08-21 18:28:52 +02:00
Bastian Wagner
caf63b34a4 chore(api): anchor the seeded demo character's HP regeneration
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 15:53:20 +02:00
Bastian Wagner
cfe1866956 test(api): update combat/equipment integration harness for HP vitals wiring 2026-08-21 15:49:35 +02:00
Bastian Wagner
52178ba79d test(api): make equip() re-anchor test exercise the overflow scenario
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.
2026-08-21 15:46:28 +02:00
Bastian Wagner
dd204db4b3 feat(api): re-anchor HP regeneration before an equipment-driven max-HP change 2026-08-21 15:40:27 +02:00
Bastian Wagner
b070bf2b0d feat(api): mirror HP onto the character each round and resume regen at combat end 2026-08-21 15:08:04 +02:00
Bastian Wagner
ac1329be46 feat(api): carry HP into combat and gate starting a fight on it 2026-08-21 14:56:05 +02:00
Bastian Wagner
432483e958 feat(api): add CHARACTER_TOO_WOUNDED combat error 2026-08-21 14:48:05 +02:00
Bastian Wagner
909f793eb6 feat(api): expose effective HP and regen anchor from GET /characters/me
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 14:45:31 +02:00
Bastian Wagner
02037b2917 feat(api): CharacterStatsService reports effective (regenerated) HP
- 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>
2026-08-21 14:41:17 +02:00
Bastian Wagner
47494abf50 feat(api): provide CharacterVitalsService from CharactersModule 2026-08-21 14:36:22 +02:00
Bastian Wagner
71f58bc1b6 feat(api): add CharacterVitalsService for anchored HP regeneration
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 14:32:18 +02:00
Bastian Wagner
7609c49899 feat(characters): expose renown on GET /api/characters/me instead of level/experience 2026-08-21 14:31:03 +02:00
Bastian Wagner
e5752c7ee9 fix(content): stop promising rewards the burned road can no longer drop
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>
2026-08-21 14:30:53 +02:00
Bastian Wagner
ea26e4b844 feat(api): add HP regeneration rate constant 2026-08-21 14:29:35 +02:00
Bastian Wagner
40ff950390 test(combat): stop asserting the deleted experience reward contract
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>
2026-08-21 14:27:20 +02:00
Bastian Wagner
19e08f0b16 feat(api): add hp_regen_since column for persistent HP regeneration
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 14:26:44 +02:00
Bastian Wagner
b55518b451 refactor(api): move Clock abstraction from travel to shared 2026-08-21 14:21:39 +02:00
Bastian Wagner
34a7bb9dbd fix(api): sweep every remaining test fixture off level/experience/requiredLevel/experienceReward 2026-08-21 14:19:12 +02:00
Bastian Wagner
8835671657 fix(monsters): finish deleting experienceReward, column included
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>
2026-08-21 14:10:19 +02:00
Bastian Wagner
dce26e00ad feat(content): migrate ItemType, seed Räuberabzeichen/Grenzwacht/turn-ins, zero direct monster rewards 2026-08-21 13:49:49 +02:00
Bastian Wagner
413fafd574 feat(equipment): remove the requiredLevel equipment gate - owned items are always equippable 2026-08-21 13:34:10 +02:00
Bastian Wagner
83eef5d9e5 docs(rewards): drop the stale XP reference from the no-partial-writes comment
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>
2026-08-21 13:28:53 +02:00
Bastian Wagner
c9d9144b5f feat(rewards): remove classical XP from the combat reward pipeline 2026-08-21 13:23:46 +02:00
Bastian Wagner
6fb61792e7 test(turn-in): cover the unknown-character rejection path
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>
2026-08-21 13:19:17 +02:00
Bastian Wagner
4e3af9fe39 feat(turn-in): add TurnInService with atomic item/silver/reputation exchange and POST /api/turn-ins 2026-08-21 13:07:46 +02:00
Bastian Wagner
987242541d inventory 2026-08-21 12:52:44 +02:00
Bastian Wagner
321738496d fix(reputation): validate the character exists before granting reputation 2026-08-21 09:30:04 +02:00
Bastian Wagner
79d4e04172 feat(reputation): add ReputationService, rank resolver integration, and GET /api/reputation 2026-08-21 09:22:36 +02:00
Bastian Wagner
4c9397336f feat(renown): add RenownService with milestone completion and base-stat recomputation 2026-08-21 09:14:36 +02:00
Bastian Wagner
4fa1923525 feat(renown): add Reputation/Renown/TurnIn entities and update Character/ItemDefinition/CombatReward 2026-08-21 09:03:37 +02:00
Bastian Wagner
3662029ec9 test(renown): pin the migration's backfill and constraint statement ordering
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 08:56:48 +02:00