# Ashen Realms – Playable Slice 0.4: First Loot **Status:** Ready for implementation **Prerequisite:** Playable Slice 0.3 – First Combat **Scope:** First server-authoritative reward and loot flow **Primary Enemies:** Aschenratte, Straßenräuber **Next Slice:** Playable Slice 0.5 – First Upgrade --- # 1. Goal Playable Slice 0.4 extends the existing gameplay loop: ```text World → Travel → Hunt → Encounter → Combat → Victory ``` to: ```text World → Travel → Hunt → Encounter → Combat → Victory → XP → Silver → Loot → Reward Summary ``` This slice must prove that: - rewards are generated only by the backend - rewards originate only from a valid won combat - each combat can grant rewards at most once - XP and silver are persisted - loot is rolled server-side - item rewards are persisted - loot generation is deterministic in tests - the frontend only presents server-generated rewards - refreshing the result screen does not reroll loot This slice deliberately stops before equipment management. The next slice will implement: ```text Playable Slice 0.5 – First Upgrade Loot → Inventory → compare item → equip item → effective stats increase ``` --- # 2. Core player flow The intended flow is: ```text Verbrannte Straße ↓ Jagd beginnen ↓ choose encounter ↓ combat ↓ monster reaches 0 HP ↓ Combat = WON ↓ server resolves rewards ↓ XP granted ↓ silver granted ↓ loot table rolled ↓ reward persisted ↓ frontend shows reward summary ``` Example: ```text Sieg Aschenratte besiegt +8 XP +6 Silber Beute: Aschenfell ``` or: ```text Sieg Straßenräuber besiegt +16 XP +12 Silber Beute: Räuberklinge ``` --- # 3. Scope Implement: ```text persistent character XP persistent character silver ItemDefinition where needed CharacterItem minimal inventory persistence LootTable LootTableEntry LootService Reward/CombatReward persistence server-side silver roll server-side loot roll reward resolution after won combat reward idempotency reward summary API data victory reward presentation ``` --- # 4. Explicit non-goals Do not implement: ```text equipment equip/unequip actions effective equipment stats item comparison UI full inventory management 24-slot inventory restrictions selling items merchants area currencies sets boss guaranteed loot pity systems crafting item upgrades random affixes quest rewards ``` Slice 0.4 is about obtaining rewards. Slice 0.5 is about using equipment. --- # 5. Reward ownership Rewards are entirely server-authoritative. The backend decides: ```text whether the combat qualifies for rewards XP amount silver amount loot rolls item definitions item quantities whether rewards were already granted ``` The frontend must never submit: ```text XP silver itemId drop result drop chance loot-table result reward quantity ``` as authoritative data. --- # 6. Reward source Rewards may only originate from a Combat with: ```text status = WON ``` A combat with: ```text ACTIVE ``` or: ```text LOST ``` must never grant normal victory rewards. --- # 7. Reward idempotency A won combat may grant rewards exactly once. This is a critical invariant. The following must never happen: ```text win combat ↓ receive loot ↓ refresh browser ↓ receive loot again ``` or: ```text repeat reward endpoint ↓ farm duplicate rewards from one combat ``` Protect this using persistent reward state and a database uniqueness constraint where practical. --- # 8. Recommended reward persistence Introduce a persisted reward record. Conceptually: ```text CombatReward ``` Required fields may include: ```ts id: uuid; combatId: uuid; characterId: uuid; experienceGranted: number; silverGranted: number; createdAt: timestamptz; ``` The relation: ```text Combat → CombatReward ``` must effectively be one-to-one. `combatId` should therefore be unique. This record acts as proof that the combat has already been rewarded. --- # 9. Character progression fields The character needs persistent values for: ```text experience silver ``` If `experience` already exists, reuse it. If silver already exists in another persistent player-state structure, reuse that instead of adding duplicate fields. Do not create competing currency models. For this slice: ```text experience += reward XP silver += reward silver ``` --- # 10. Leveling scope XP must be persisted. However, do not expand this slice into a full progression system unless a minimal level-up path is already present. The existing XP curve is: ```text Level 1 → 2: 100 XP Level 2 → 3: 180 XP Level 3 → 4: 300 XP Level 4 → 5: 450 XP Level 5 → 6: 650 XP Level 6 → 7: 900 XP ``` If the project already supports level-up calculations, use the existing implementation. Otherwise Slice 0.4 may persist XP without implementing the complete later-level progression architecture. The public reward contract should remain compatible with future level-up information. --- # 11. ItemDefinition Reuse the project's existing `ItemDefinition` if already implemented. For this slice, at least the following item content is needed: ```text worn-short-sword bandit-blade bandit-hood ``` depending on the existing seed. Required loot-relevant fields should already support or conceptually include: ```ts id key name description type equipmentSlot rarity tier requiredLevel weaponDamage bonusHp bonusAttack bonusArmor sellPrice iconPath ``` Do not create a parallel item entity. --- # 12. CharacterItem Introduce or reuse minimal persistent player-owned item state. Conceptually: ```ts id: uuid; characterId: uuid; itemDefinitionId: uuid; quantity: number; createdAt: timestamptz; updatedAt: timestamptz; ``` For equipment items, quantity may normally be: ```text 1 ``` If the existing architecture treats equipment instances separately instead of stacking them, preserve that convention. Do not redesign the inventory model unnecessarily for this slice. --- # 13. LootTable Create or reuse: ```text LootTable ``` Conceptual fields: ```ts id: uuid; key: string; name: string; createdAt: timestamptz; updatedAt: timestamptz; ``` Monster definitions should reference their loot table through the project's chosen data model. Stable keys are preferred. Example: ```text ash-rat-loot road-bandit-loot ``` --- # 14. LootTableEntry Create: ```text LootTableEntry ``` Conceptual fields: ```ts id: uuid; lootTableId: uuid; itemDefinitionId: uuid; dropChance: decimal; minQuantity: number; maxQuantity: number; enabled: boolean; ``` The model should support future guaranteed drops cleanly. Do not add complex conditional loot scripting in this slice. --- # 15. First Aschenratte rewards According to the existing balancing specification: ## Guaranteed ```text 4–7 Silver 8 XP ``` ## Material ```text 60 % Aschenfell / Tierrest ``` ## Equipment ```text 8 % simple starter-slot equipment ``` For this technical slice, the minimal useful implementation should preserve those documented probabilities where the necessary item definitions exist. If `Aschenfell` has not yet been modeled as an item and introducing generic material items would unnecessarily expand scope, equipment loot may be prioritized first. Any deliberate omission must be documented. Do not silently invent replacement probabilities. --- # 16. First Straßenräuber rewards According to the balancing specification: ## Guaranteed ```text 9–15 Silver 16 XP ``` ## Equipment ```text 18 % Räuberklinge 12 % Räuberhaube 8 % Plündererhandschuhe 10 % Kleiner Heiltrank ``` For Slice 0.4, only already-supported item types need to be persisted. At minimum the slice should support: ```text Räuberklinge Räuberhaube ``` if these are already part of the technical mini-slice content. Do not implement potions or combat consumables solely because the final loot table contains them. Unsupported entries may be deferred explicitly. --- # 17. Loot rolls are independent Unless the existing design specifies otherwise, separate listed drops are independent rolls. Conceptually for Straßenräuber: ```text roll Räuberklinge roll Räuberhaube ``` This means one combat may produce: ```text nothing one item multiple items ``` depending on the configured loot entries. Do not force exactly one item drop. --- # 18. Silver roll Silver is guaranteed but variable. Example for Aschenratte: ```text min = 4 max = 7 ``` The backend rolls an inclusive integer: ```text 4 5 6 or 7 ``` For Straßenräuber: ```text 9–15 ``` The silver roll must use the same testable randomness abstraction approach already used for hunting where appropriate. Do not make reward tests flaky. --- # 19. Random abstraction Reuse the existing injectable `RandomSource` from Slice 0.2 if available. Do not create another independent random abstraction. The same infrastructure should be usable for: ```text encounter selection silver roll loot rolls ``` Tests must be able to inject fixed random values. --- # 20. LootService Create or extend: ```text LootService ``` The LootService should be responsible for deterministic reward calculation from content definitions. Conceptual API: ```ts rollLoot( monsterDefinitionId: string, ): Promise ``` The result may contain: ```ts { items: [ { itemDefinitionId: string; quantity: number; } ] } ``` Do not grant items inside the pure roll portion if that makes deterministic unit testing difficult. Prefer separating: ```text roll ``` from: ```text grant/persist ``` where that matches existing architecture. --- # 21. RewardService or orchestration boundary Reward resolution should be orchestrated in one clear service boundary. Possible name: ```text CombatRewardService ``` or equivalent existing architecture. Responsibilities: ```text validate won combat ↓ ensure reward not already granted ↓ load monster reward definition ↓ determine XP ↓ roll silver ↓ roll loot ↓ persist Character XP ↓ persist Character silver ↓ persist CharacterItem rewards ↓ persist CombatReward ↓ commit ↓ return reward DTO ``` All persistence must happen transactionally. --- # 22. Reward transaction The reward operation must be atomic. Conceptually: ```text lock Combat ↓ verify WON ↓ verify no CombatReward exists ↓ calculate rewards ↓ update character XP ↓ update character silver ↓ grant items ↓ create CombatReward ↓ commit ``` If persistence fails midway: ```text no partial reward should remain ``` --- # 23. When rewards are resolved Preferred behavior: When a combat action changes: ```text ACTIVE ``` to: ```text WON ``` the backend may immediately resolve rewards in the same higher-level combat completion flow. Alternatively, a dedicated reward-resolution endpoint may be used if the project architecture strongly favors that. However: > reward resolution must remain idempotent and server-owned. Do not rely on the frontend calling a special endpoint exactly once for correctness. If a separate endpoint exists, repeated calls must return the already-created result rather than grant new rewards. --- # 24. Preferred API behavior The cleanest player-facing result is for the final combat response to contain or reference reward information after victory. Example: ```json { "id": "combat-uuid", "status": "WON", "round": 4, "player": { "currentHp": 82, "maxHp": 100 }, "monster": { "name": "Aschenratte", "currentHp": 0, "maxHp": 45 }, "rewards": { "experience": 8, "silver": 6, "items": [] } } ``` If rewards are represented as a separate resource, the API should still make retrieval straightforward. --- # 25. Reward retrieval A browser refresh after victory must not lose the reward summary. Therefore: ```text GET /api/combats/:combatId ``` should either include the persisted reward or provide a stable reference to it. Do not keep victory rewards only in transient Angular state. --- # 26. Reward DTO Conceptually: ```ts interface CombatRewardDto { experience: number; silver: number; items: Array<{ characterItemId: string; item: { key: string; name: string; rarity: string; iconPath: string; }; quantity: number; }>; } ``` Do not expose: ```text drop chance random roll result loot-table internal ID ``` to the normal player-facing DTO. --- # 27. Item persistence When an item drops, the item must exist as actual persistent player state before the response is returned. The frontend must not merely show a cosmetic reward card. Example: ```text Räuberklinge dropped ↓ CharacterItem created ↓ reward DTO references CharacterItem ``` Slice 0.5 will use that persisted item. --- # 28. Duplicate items Duplicate equipment drops are allowed. The existing design explicitly allows duplicate items. Do not add: ```text duplicate protection salvaging automatic conversion pity system ``` in this slice. --- # 29. No boss guarantee logic yet The full balancing design later includes: ```text guaranteed boss progression item + special boss roll ``` Do not implement that system for Slice 0.4. The initial monsters are: ```text Aschenratte Straßenräuber ``` Keep the implementation sufficient for normal-monster loot while allowing future extension. --- # 30. Combat completion integration Modify the Slice 0.3 victory flow. Previously: ```text Combat = WON ↓ no rewards ``` Now: ```text Combat = WON ↓ reward resolution ↓ Combat response contains persisted rewards ``` The combat engine itself must remain unaware of loot. Keep this boundary: ```text CombatEngineService ↓ returns WON CombatService / orchestration ↓ persists combat completion Reward service ↓ grants rewards ``` Do not add loot logic to `CombatEngineService`. --- # 31. Frontend victory screen Replace the temporary Slice 0.3 victory placeholder with a real reward summary. Example: ```text SIEG Aschenratte wurde besiegt. Belohnungen +8 XP +6 Silber Keine besondere Beute gefunden. [ Zur Jagd ] ``` If an item drops: ```text SIEG Straßenräuber wurde besiegt. Belohnungen +16 XP +12 Silber Beute [Räuberklinge Icon] Räuberklinge Selten [ Zur Jagd ] ``` --- # 32. Reward presentation Reward priority: ```text Victory ↓ XP ↓ Silver ↓ Item loot ``` Equipment drops should receive stronger visual emphasis than routine currency. The UI should make a meaningful item drop feel valuable without using excessive mobile-game celebration effects. --- # 33. Item cards For dropped equipment, reuse or introduce a compact reusable item presentation. Show: ```text icon name rarity quantity if relevant ``` Do not implement the full comparison tooltip yet. Do not show: ```text equip button stat comparison slot replacement ``` Those belong to Slice 0.5. --- # 34. No-loot state A victory without an item drop is valid. The reward screen must still feel complete because XP and silver are guaranteed. Example: ```text Keine besondere Beute gefunden. ``` Do not frame this as an error. --- # 35. Character UI updates After reward resolution, global UI elements showing character resources should refresh appropriately. If the TopBar already displays XP or silver: ```text update from authoritative server data ``` Do not mutate those values optimistically without confirming the backend result. --- # 36. Hunt return After reviewing rewards: ```text Zur Jagd ``` returns to: ```text /hunt ``` The previous selected/consumed encounter must not become attackable again. The player can start: ```text Neu suchen ``` for another hunt. --- # 37. Database changes Create or extend the migration for: ```text combat_reward loot_table loot_table_entry character_item ``` and character currency fields where not already present. Migration requirements: - preserve all existing data - use UUID primary keys - create proper foreign keys - enforce one reward per combat - create unique content keys - create useful indexes - do not drop unrelated tables - keep `synchronize` disabled --- # 38. Seed changes Extend the existing idempotent seed. At minimum seed required loot configuration for: ```text Aschenratte Straßenräuber ``` Seed relevant item definitions that do not already exist. The seed must remain idempotent. Running it repeatedly must not: ```text duplicate loot tables duplicate loot entries duplicate item definitions reset player XP reset silver delete player items delete combat rewards ``` --- # 39. Backend tests – reward eligibility Test: ```text ACTIVE combat → rewards rejected ``` Test: ```text LOST combat → rewards rejected ``` Test: ```text WON combat → rewards granted ``` --- # 40. Backend tests – idempotency Given one won combat: ```text resolve rewards ↓ resolve rewards again ``` Verify: ```text XP only granted once silver only granted once items only granted once single CombatReward exists ``` This is one of the most important tests of the slice. --- # 41. Backend tests – Aschenratte Using deterministic randomness, verify: ```text XP = 8 silver is within 4–7 ``` Where implemented, verify expected item roll behavior. --- # 42. Backend tests – Straßenräuber Verify: ```text XP = 16 silver is within 9–15 ``` With deterministic random values, verify known item outcomes such as: ```text Räuberklinge drops ``` and: ```text Räuberklinge does not drop ``` No statistical/flaky tests. --- # 43. Backend tests – transaction Simulate or test failure during reward persistence where practical. Verify no state like: ```text XP granted but reward record missing ``` or: ```text item granted twice ``` can result. --- # 44. Backend tests – CharacterItem When an item is rolled: ```text CharacterItem persisted correct character correct ItemDefinition correct quantity ``` --- # 45. Frontend tests – victory rewards Given: ```text status = WON rewards.experience = 8 rewards.silver = 6 ``` verify: ```text victory shown 8 XP shown 6 silver shown ``` --- # 46. Frontend tests – item drop Given reward: ```text Räuberklinge ``` verify: ```text item icon rendered item name rendered rarity rendered ``` Do not require an equip action yet. --- # 47. Frontend tests – no item Given: ```text items = [] ``` verify a valid no-special-loot state. --- # 48. Frontend tests – refresh Opening an already won combat must reload its persisted rewards. The frontend must not reroll or reconstruct them. --- # 49. Server-authority checklist The server owns: ```text reward eligibility reward idempotency XP amount silver roll loot tables drop probabilities random loot rolls item granting currency persistence reward persistence ``` The client owns only: ```text displaying the reward navigating away after the player reviews it ``` --- # 50. Visual requirements The reward summary should remain part of the established Ashen Realms combat presentation. Use: ```text dark metal / stone panels bronze details large readable reward values high-quality item icons restrained rarity emphasis ``` Do not introduce: ```text loot-box presentation slot-machine effects large flashing reward popups bright mobile-game confetti generic ecommerce cards ``` A valuable item should feel valuable primarily because of: ```text icon rarity name presentation hierarchy ``` --- # 51. Definition of Done Playable Slice 0.4 is complete when this full browser flow works: ```text Südtor ↓ Verbrannte Straße ↓ Jagd ↓ choose encounter ↓ combat ↓ win ↓ Combat = WON ↓ backend grants XP ↓ backend rolls and grants silver ↓ backend rolls loot ↓ any dropped item is persisted ↓ CombatReward persisted ↓ Angular shows reward summary ↓ refresh page ↓ same reward still shown ↓ no duplicate rewards granted ↓ return to hunt ``` --- # 52. Required verification Before completion, run: ```text API unit tests API integration tests Angular tests API build Angular build migration compilation migration execution seed execution ``` Perform browser walkthroughs for: ```text Aschenratte victory with no item Aschenratte victory with forced deterministic item roll where supported Straßenräuber victory Straßenräuber with Räuberklinge drop reward screen refresh repeated reward request return to hunt ``` --- # 53. Acceptance criteria The slice is accepted when: - only WON combats receive victory rewards - one combat grants rewards at most once - XP is persisted - silver is persisted - silver ranges match the content definitions - loot rolls happen only on the backend - loot probabilities are data-driven - reward randomness is deterministic in tests - dropped items become persistent CharacterItems - reward data survives browser refresh - reward results never reroll on reload - Angular only renders server-provided reward state - item drops are visibly presented - a no-item victory is handled cleanly - CombatEngineService contains no reward logic - no equipment functionality is implemented prematurely - all tests pass - backend and frontend builds succeed --- # 54. Handoff to Playable Slice 0.5 The exact handoff from Slice 0.4 is: ```text WON Combat + persisted CombatReward + persisted CharacterItem ``` For example: ```text Character owns: Räuberklinge ``` but cannot yet equip it. Playable Slice 0.5 will implement: ```text Inventory → inspect Räuberklinge → compare against Abgenutztes Kurzschwert → equip Räuberklinge → effective weapon damage increases → next combat becomes easier ``` --- # 55. Why Slice 0.5 matters Slice 0.4 gives the player a reward. Slice 0.5 makes that reward meaningful. Together they complete the central progression loop: ```text hunt → fight → loot → equip → become stronger ``` That is the first real proof of the Ashen Realms item-driven progression philosophy. --- # 56. Architectural boundary Keep responsibilities separated: ```text CombatEngineService ↓ decides combat result CombatService ↓ persists combat Reward service ↓ decides and grants rewards LootService ↓ rolls configured loot CharacterItem ↓ stores player-owned loot Slice 0.5 ↓ uses that loot as equipment ``` Do not let the combat engine know about: ```text XP silver loot tables inventory equipment item rarity ``` --- # 57. Summary Playable Slice 0.4 proves that defeating an enemy produces real, persistent progression resources. After this slice the player can: ```text travel → hunt → choose enemy → fight → win → receive XP → receive silver → find an item ``` The next slice completes the loop by allowing the player to actually use that item.