Commit Graph

47 Commits

Author SHA1 Message Date
Bastian Wagner
624c47e5a6 Merge branch 'worktree-slice-0.4-first-loot' 2026-08-20 10:33:39 +02:00
Bastian Wagner
048cf80e78 test(slice-0.4): assert migration SQL and top-bar silver/XP rendering
Adds a real-SQL-inspection test for CreateLootAndRewards1788600000000
(mirroring the visible-vertical-slice pattern) so the one-reward-per-combat
unique index and other DB-level invariants can't be silently deleted
without failing a test, and asserts the TopBar renders character.silver
and character.experience values already present in the app.spec.ts fixture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 10:25:26 +02:00
Bastian Wagner
b92aecb71a renaming 2026-08-20 10:16:51 +02:00
Bastian Wagner
b0e769d0da combat 2026-08-20 09:47:24 +02:00
Bastian Wagner
21b377f70c feat(web): show authoritative silver and XP in the top bar after victory
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 09:40:20 +02:00
Bastian Wagner
6711d51bcd feat(combat): replace the victory placeholder with the reward summary
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 09:34:21 +02:00
Bastian Wagner
cf576133ff feat(web): add reward models and the reusable item card
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 09:24:58 +02:00
Bastian Wagner
35559e3d2b feat(items): extract tier-1 item icons from the art sheet
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 23:47:42 +02:00
Bastian Wagner
67237f5ad8 effekte 2026-08-19 23:42:18 +02:00
Bastian Wagner
fd9852bfbf animation 2026-08-19 23:24:36 +02:00
Bastian Wagner
76e8ef4320 Merge branch 'worktree-slice-0.3-first-combat' 2026-08-19 22:47:34 +02:00
Bastian Wagner
6c4b7d29d0 feat(combat): play the round a beat at a time with attack and hit frames
The server resolves a whole round in one call, so both blows used to land
at the same instant. The page now keeps its own view of the combat and
plays the round back: the swing animates, the monster's HP and log line
land, then after a beat the monster strikes and the player recoils.

Both animations are six-frame sprite sheets driven by steps(6), which is
why the phase durations mirror the stylesheet.

The status row reflows on the stage's own width via a container query —
the side rails can squeeze it narrow while the viewport is still wide,
which previously overlapped the round marker with the player's name. The
component-style budget moves to 12kB to fit this screen's stylesheet.
2026-08-19 22:46:17 +02:00
Bastian Wagner
54f9d59ef4 feat(combat): use the cut-out enemy art and scale sprites per monster
Swap the enemy body art for the background-free versions and trim every
sprite to its opaque bounds so the fighters share one ground baseline
instead of floating in a padded box.

Sprite height is now a share of the battlefield rather than a fixed
clamp, set per monster key, so a low-slung rat and a standing bandit keep
believable proportions against the player at any stage size.
2026-08-19 22:46:05 +02:00
Bastian Wagner
f33b0c0e4a feat(combat): rejoin the running combat instead of dead-ending
Attacking while a combat is already active returned COMBAT_ALREADY_ACTIVE
and left the hunt page showing an error the player could not act on, with
no way back into the fight they were already in.

Add GET /api/combats/active so the client can resolve that combat, and
have the hunt page navigate into it when an attack is rejected for this
reason. CombatStore now also exposes the error code so callers can tell
this case apart from a genuinely failed attack.
2026-08-19 22:45:53 +02:00
Bastian Wagner
e42957d91e Merge branch 'worktree-slice-0.3-first-combat' 2026-08-19 22:40:32 +02:00
Bastian Wagner
92b9f1cd35 feat(combat): rebuild the combat screen around the reference layout
Move both health bars to a status row at the top of the scene with a
circular portrait beside each, and place full-body sprites for the
player and the monster standing on the location background instead of
square portraits. The Angriff action now uses the ornate HUD frame art,
with the keybind in the frame's own tab.

Sprite and icon derivatives are keyed by monster key so both seeded
monsters resolve; the Aschenratte body art still carries its original
backdrop until a cut-out version replaces it at the same path.
2026-08-19 22:11:07 +02:00
Bastian Wagner
dcb248bd15 statusbar 2026-08-19 22:07:19 +02:00
Bastian Wagner
6affb9eddc Merge branch 'master' into worktree-slice-0.3-first-combat 2026-08-19 21:53:29 +02:00
Bastian Wagner
aa8c374db0 feat(combat): start real combats from the hunt page and navigate to /combat/:combatId 2026-08-19 17:03:35 +02:00
Bastian Wagner
1fd62cddde feat(combat): add CombatPageComponent and replace the combat/new placeholder route
Wires up the Slice 0.3 combat screen (player/monster HP bars, round
display, Angriff action, grouped German combat log, victory/defeat
panels) and replaces the Slice 0.2 combat/new placeholder route with
combat/:combatId loading CombatPageComponent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 16:53:59 +02:00
Bastian Wagner
4b1e7f034c feat(combat): add CombatStore
Signal-based store wrapping GameApiService.startCombat/getCombat/
performCombatAction with loading/error/actionPending state, following
the HuntingStore/WorldStore pattern. startCombat failures clear any
previously-loaded combat; loadCombat/attack failures preserve the
last-known-good combat. attack() guards against re-entrancy and
no-loaded-combat calls.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 16:46:36 +02:00
Bastian Wagner
8cf15afaee refactor(web): extract shared runtime monster-artwork lookup
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 16:41:28 +02:00
Bastian Wagner
a6450817de feat(combat): add frontend Combat models and API methods
Adds Combat domain models (Combat, CombatPlayer, CombatMonster, CombatEvent)
and three GameApiService methods to interact with backend combat endpoints:
- startCombat(encounterId): POST /api/hunt-encounters/:id/attack
- getCombat(combatId): GET /api/combats/:id
- performCombatAction(combatId, action): POST /api/combats/:id/actions

Includes test coverage for all three methods.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 16:33:37 +02:00
Bastian Wagner
85184e6e53 Cursor 2026-08-19 16:06:51 +02:00
Bastian Wagner
833fa52d8d perf: serve optimized JPEG derivatives for monster artwork
encounter-card.component rendered the raw 2.39MB ash-rat.png and
2.00MB road-bandit.png directly, up to 3 cards per hunt (~7MB/load,
re-rendered on "Neu suchen"). Add 560px-wide JPEG runtime derivatives
(generated via PowerShell System.Drawing, HighQualityBicubic, quality
82) and switch to the <picture>/<source srcset> pattern already used
by context-panel for location backgrounds, keeping the original PNGs
as the <img> fallback. Also add loading="lazy" decoding="async".

ash-rat.png: 2,506,677 B -> ash-rat-560.jpg: 35,896 B
road-bandit.png: 2,097,049 B -> road-bandit-560.jpg: 50,797 B
2026-08-19 14:33:32 +02:00
Bastian Wagner
26909d7e2a fix: load WorldStore on direct /hunt navigation
HuntPageComponent never called WorldStore.load(), so opening /hunt
directly (bookmark/hard refresh) without first visiting /world left
currentLocation() at null forever, stranding the page and the context
panel on their empty states with no recovery. Add ngOnInit that calls
worldStore.load() only when no location is present yet, mirroring
WorldPageComponent's existing call and avoiding a duplicate request.
2026-08-19 14:33:15 +02:00
Bastian Wagner
4e684dc45e feat: add hunt page, combat placeholder, and Jagd navigation
Wires up Slice 0.2 end-to-end: HuntPageComponent renders the
hunting-unavailable/ready/loading/encounters-found states off
HuntingStore and WorldStore, Angreifen hands the HuntEncounter id to a
new inert CombatPlaceholderPageComponent via /combat/new, the Jagd nav
entry is enabled with router-driven active state (matching Karte's),
and the context panel now lists possible encounters for hunting-enabled
locations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 14:06:57 +02:00
Bastian Wagner
784bd3ce3a fix: replace invented hex color with existing --ar-bg token
Code review flagged .encounter-card__artwork-frame using a new
hardcoded #050607 background instead of reusing an existing design
token, violating the no-new-hex-colors requirement.
2026-08-19 13:39:35 +02:00
Bastian Wagner
15aa83d221 feat: add EncounterCard component for hunt encounter selection
Task 9 of Playable Slice 0.2 (First Hunt). Artwork-forward encounter
card reusing travel-panel's dark-fantasy panel chrome (tokens, border,
gradient, button style). Emits encounter.id (never monster.key) on
Angreifen click, preserving the frontend security boundary.
2026-08-19 13:33:33 +02:00
Bastian Wagner
59269a7608 feat: add DangerBadge shared component
Renders the 5 HuntEncounter danger tiers as German text plus a
per-tier BEM modifier class, mirroring travel-panel's text+class
danger pattern so color is never the sole signal. Colors map across
the existing 3 semantic tokens (success/warning/danger).
2026-08-19 13:26:58 +02:00
Bastian Wagner
49eb110b25 feat: add HuntingStore for hunt/encounter state
Mirrors WorldStore's signal-store architecture to own hunt state for
Slice 0.2 (currentHunt, selectedEncounterId, loading, error) with a
computed encounters accessor and the four hunt error codes mapped to
German messages.
2026-08-19 13:20:15 +02:00
Bastian Wagner
ed03726931 feat: add hunt API client and models for Playable Slice 0.2
Add to game-api.models.ts:
- DangerRating type for hunt encounter danger levels
- MonsterSummary interface with key, name, level, artworkPath
- HuntEncounter interface for individual hunt encounters
- HuntResult interface for hunt completion results, reusing LocationSummary

Extend CurrentLocationResponse with possibleMonsters: string[] field.

Add to game-api.service.ts:
- startHunt(): Observable<HuntResult> method posting to /api/hunts

Update test fixtures to include possibleMonsters field in mock locations.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-19 13:14:07 +02:00
Bastian Wagner
9423c28bd8 feat: seed Aschenratte and Straßenräuber for Verbrannte Straße
Adds the two starter monster definitions and their location-monster
mapping (weights 70/30) to the vertical-slice seed, following the
same findOneBy/update-or-insert pattern used for locations, plus an
upsert on (locationId, monsterId) for the mapping. Copies the source
artwork into the served images/monsters directory and extends the
seed spec harness to cover both idempotent inserts.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-19 12:08:26 +02:00
Bastian Wagner
2e7b280270 fix: map backend travel error codes to German messages and resync on failed start
HttpErrorResponse implements the Error interface structurally but does not
extend the Error class, so `error instanceof Error` was always false for
HTTP failures and every travel error (400/409/500/network) fell through to
the same generic fallback string, hiding the backend's specific
TravelDomainError code/message (e.g. TRAVEL_ALREADY_ACTIVE) from the user.

toErrorMessage now checks `error instanceof HttpErrorResponse` first and
maps known travel error codes to specific German messages, falling back to
the generic message for unknown codes and to `error.message` for genuine
non-HTTP errors.

startTravel()'s catch block also now resyncs current travel state from the
server (reusing the existing pollCurrentTravel/refreshCurrentTravel path)
so a failed start caused by a race with another tab/request doesn't leave
the store's local state stale.
2026-08-19 10:50:20 +02:00
Bastian Wagner
e0e6db3ae8 chore: move unreferenced source art out of the served web assets
apps/web/public/images was shipping ~90 MB of source art (enemies, npc,
combat status icons, HUD icon originals/difficulty badges, unused
background paintings) that no component, template, or stylesheet
actually references, since Angular copies public/ verbatim into every
browser build. Moved everything not referenced under apps/web/src to a
new art/ directory at the repo root, preserving the original subfolder
layout; only the 11 files actually loaded by the app (runtime HUD/
background derivatives and their PNG fallbacks) remain under
apps/web/public/images. Documented the split in README.md.
2026-08-19 10:50:00 +02:00
Bastian Wagner
ec6c34e341 test: verify first visible vertical slice
Drives the full world/travel flow in Chromium via Playwright against the
live API and seeded Postgres, capturing the three required desktop
viewports and comparing them against the accepted design reference.

Fixes two evidenced defects found during verification:
- world-page scene sizing overflowed the viewport at all three required
  desktop sizes, pushing the footer (and, at 1440x900/1366x768, the travel
  button) below the fold; scene height now accounts for surrounding chrome
  so nothing is clipped.
- the generic HTTP-failure fallback message was hardcoded in English while
  the rest of the UI is German; now uses a matching German string.

Adds Playwright as a web devDependency for this and future browser
verification passes (no product-code dependency).
2026-08-19 10:13:40 +02:00
Bastian Wagner
6c35c18018 bilder 2026-08-19 09:06:10 +02:00
Bastian Wagner
8e6e77b027 fix: avoid duplicate world map background loads 2026-08-19 09:02:14 +02:00
Bastian Wagner
1a30eb3491 fix: harden world travel presentation 2026-08-19 08:51:26 +02:00
Bastian Wagner
1e2bfe6e4b feat: render interactive ashen fields world 2026-08-19 08:25:42 +02:00
Bastian Wagner
fce03e220d fix: optimize shell accessibility and assets 2026-08-19 07:42:28 +02:00
Bastian Wagner
b9e5c66c8e feat: build dark fantasy application shell 2026-08-18 22:36:21 +02:00
Bastian Wagner
6304703d67 fix: clear recovered travel errors 2026-08-18 22:07:08 +02:00
Bastian Wagner
a738c61611 fix: harden world travel polling 2026-08-18 21:52:54 +02:00
Bastian Wagner
610c404759 feat: add signal-driven world state 2026-08-18 21:36:27 +02:00
Bastian Wagner
2903a0d670 chore: configure angular and postgres foundation 2026-08-18 18:02:40 +02:00
Bastian Wagner
9e92731645 chore: bootstrap ashen realms monorepo 2026-08-18 17:01:21 +02:00