Files
ashen-realms/docs/playable-slices/Ashen Realms – Playable Slice 0.5_ First Upgrade.md
Bastian Wagner 5b662aad4c docs
2026-08-19 11:14:17 +02:00

25 KiB
Raw Blame History

Ashen Realms Playable Slice 0.5: First Upgrade

Status: Ready for implementation
Prerequisite: Playable Slice 0.4 First Loot
Scope: First usable inventory and equipment upgrade
Primary Upgrade: Abgenutztes Kurzschwert → Räuberklinge
Primary Goal: A looted item can be equipped and measurably changes the character's effective combat stats.


1. Goal

Playable Slice 0.5 completes the first full Ashen Realms progression loop.

The existing playable flow:

World
→ Travel
→ Hunt
→ Encounter
→ Combat
→ Victory
→ Reward

becomes:

World
→ Travel
→ Hunt
→ Combat
→ Loot
→ Inventory
→ Equip upgrade
→ Character becomes stronger
→ next combat becomes easier

This slice must prove that:

  • player-owned items are persistent
  • inventory is server-authoritative
  • equipment is server-authoritative
  • equipment slots are enforced
  • item ownership is validated
  • required level is validated
  • effective character stats are derived from character base stats plus equipment
  • equipping an upgrade changes combat-relevant stats
  • the frontend does not calculate authoritative character power
  • a newly obtained weapon produces a visibly meaningful improvement

The primary demonstration is:

Abgenutztes Kurzschwert
8 Waffenschaden

becomes:

Räuberklinge
11 Waffenschaden
+1 Angriff

and the next combat deals more damage.


2. Core player flow

The intended flow is:

Straßenräuber besiegen
↓
Räuberklinge erhalten
↓
Reward screen
↓
Inventar öffnen
↓
Räuberklinge auswählen
↓
current item and new item compared
↓
Ausrüsten
↓
server validates item
↓
server equips Räuberklinge in Waffe slot
↓
effective stats recalculated
↓
Topbar / character UI updates
↓
return to hunt
↓
fight another enemy
↓
player deals more damage

This moment is the first real proof of item-driven progression.


3. Scope

Implement:

Inventory API

Equipment API

CharacterEquipment persistence

CharacterStatsService

effective character stats

weapon equipment slot

basic armor equipment slots where already supported

inventory page

item selection

item detail presentation

simple equipped-item comparison

equip action

unequip/replace behavior as required

equipped state display

effective stat refresh

At minimum, the system must support:

Abgenutztes Kurzschwert
Räuberklinge
Räuberhaube

where those items already exist in content.


4. Explicit non-goals

Do not implement:

item selling
merchants
item durability
repairs
salvaging
crafting
socketing
random affixes
item upgrades +1 to +10
transmog
set bonuses
complex tooltips
drag-and-drop inventory
inventory sorting systems
filters
search
bank/storage
stash
equipment presets
multiple builds
comparison against every possible slot

Do not expand the slice into a complete MMO inventory system.

The goal is only:

obtain item → inspect item → equip item → become stronger.


5. Equipment philosophy

Ashen Realms uses equipment as the primary source of character power.

The Vertical Slice targets roughly:

20 % strength from level/base stats
80 % strength from equipment

Therefore equipment changes must be noticeable.

The player should immediately understand:

old weapon
↓
new weapon
↓
more damage

Do not hide the effect behind tiny percentage changes.


6. Equipment slots

The final Vertical Slice uses:

WEAPON
HEAD
CHEST
HANDS
LEGS
FEET
AMULET

Slice 0.5 should support this enum/data model cleanly.

However, the actual playable demonstration only requires:

WEAPON

and optionally:

HEAD

if Räuberhaube is already part of the first loot implementation.

Do not build seven complex UI workflows if only one or two slots currently have content.


7. ItemDefinition

Reuse the existing ItemDefinition.

Do not create a second item model.

Relevant conceptual fields:

id: uuid;

key: string;
name: string;
description: string;

type: ItemType;
equipmentSlot?: EquipmentSlot;

rarity: ItemRarity;
tier: number;

requiredLevel: number;

weaponDamage: number;
bonusHp: number;
bonusAttack: number;
bonusArmor: number;

sellPrice: number;
iconPath: string;

Only fields already supported by the existing architecture should be introduced.

Do not add speculative attributes.


8. Initial item content

Abgenutztes Kurzschwert

Stable key:

worn-short-sword

Stats:

equipmentSlot: WEAPON
requiredLevel: 1

weaponDamage: 8
bonusAttack: 0
bonusHp: 0
bonusArmor: 0

This is the starting weapon.


9. Räuberklinge

Stable key:

bandit-blade

Stats:

equipmentSlot: WEAPON
requiredLevel: 1

weaponDamage: 11
bonusAttack: 1

This item must be a clear upgrade from the starting weapon.

If finalized content in the repository differs, preserve the established project values.


10. Räuberhaube

Stable key:

bandit-hood

Stats:

equipmentSlot: HEAD

bonusArmor: 3
bonusHp: 5

Only include this in the playable inventory if it already exists in Slice 0.4 content.

The weapon upgrade remains the required core test.


11. CharacterItem

Reuse the persistent player-owned item entity introduced in Slice 0.4.

Conceptually:

id: uuid;

characterId: uuid;
itemDefinitionId: uuid;

quantity: number;

createdAt: timestamptz;
updatedAt: timestamptz;

Equipment ownership must originate from CharacterItem.

The frontend must not be able to equip arbitrary ItemDefinition.id values.


12. CharacterEquipment

Create or reuse:

apps/api/src/equipment/entities/character-equipment.entity.ts

Conceptual fields:

id: uuid;

characterId: uuid;

slot: EquipmentSlot;

characterItemId: uuid;

createdAt: timestamptz;
updatedAt: timestamptz;

Important invariants:

one equipped item per character per slot

and:

one CharacterItem cannot occupy multiple equipment slots simultaneously

Protect these through service validation and appropriate database constraints.


13. Ownership rule

The server must validate:

CharacterItem belongs to current character

before equipment changes.

Never accept:

ItemDefinition.id

alone as sufficient proof of ownership.

Equip requests should identify the owned item:

CharacterItem.id

14. Equip validation

Before equipping:

CharacterItem exists
↓
belongs to current character
↓
ItemDefinition is equippable
↓
equipmentSlot exists
↓
requiredLevel <= character level
↓
item is valid for target slot

The client must not choose an arbitrary target slot if the item already defines its slot.

Prefer:

POST /api/equipment

with:

{
  "characterItemId": "uuid"
}

The server derives:

slot

from the item definition.


15. Equip replacement behavior

If the slot already contains an item:

current item
↓
new item equipped
↓
old item becomes unequipped
↓
old item remains in inventory

Do not destroy or delete the old CharacterItem.

Example:

WEAPON:
Abgenutztes Kurzschwert

then equip:

Räuberklinge

result:

WEAPON:
Räuberklinge

Inventory:
Abgenutztes Kurzschwert still owned

16. Unequip behavior

For Slice 0.5, an explicit unequip endpoint is optional.

If implemented:

DELETE /api/equipment/:slot

It must remove only the equipped relation.

The item remains owned.

Do not make explicit unequip necessary for replacing an item.


17. Starting equipment

The demo character must begin with:

Abgenutztes Kurzschwert

actually represented through the same inventory/equipment model used for future loot.

Do not keep the starting weapon as an unrelated hardcoded combat constant once equipment is implemented.

The goal is to remove temporary Slice 0.3 combat-stat shortcuts where possible.


18. CharacterStatsService

Introduce or complete:

CharacterStatsService

This becomes the single authoritative source for effective character stats.

Conceptual return type:

interface EffectiveCharacterStats {
  maxHp: number;
  currentHp: number;

  attack: number;
  weaponDamage: number;
  armor: number;

  combatPower: number;
}

The service calculates values from:

Character base stats
+
equipped ItemDefinitions

Do not calculate effective stats in:

Angular
CombatController
InventoryController

19. Base stats

The starting level-1 character uses:

baseHp = 100
baseAttack = 6

Equipment supplies:

weaponDamage
armor
bonusHp
bonusAttack

Starting equipment target:

100 HP
6 base attack
8 weapon damage
6 armor

if the corresponding starting armor pieces are already modeled.

If not all armor items currently exist, do not fabricate an entire starter gear set merely for Slice 0.5.

Preserve the existing demo balance as closely as the implemented content allows.


20. Effective stat calculation

Conceptually:

maxHp =
baseHp
+ sum equipped bonusHp

attack =
baseAttack
+ sum equipped bonusAttack

weaponDamage =
equipped weapon weaponDamage

armor =
sum equipped bonusArmor

If no weapon is equipped:

weaponDamage = 0

or use another existing explicit project rule.

Do not silently inject a fake weapon value.


21. Combat Power

Calculate internal Combat Power using the established formula:

Combat Power =
HP / 10
+ Attack × 2
+ Weapon Damage × 2
+ Armor × 1.5

This value remains internal for balancing unless the existing UI already exposes it.

Do not add Combat Power to the player-facing UI just because the service now calculates it.


22. Combat integration

Slice 0.3 combat snapshots player stats when combat begins.

After Slice 0.5:

Combat creation
↓
CharacterStatsService.calculate(...)
↓
snapshot current effective equipment-derived stats

Therefore equipping Räuberklinge affects:

future combats

but must not retroactively alter:

already-active combat

This snapshot behavior is important.


23. Example power change

Before upgrade:

Base attack: 6
Weapon damage: 8

Raw attack damage:

14

After equipping Räuberklinge:

Attack:
6 + 1 = 7

Weapon damage:
11

Raw attack damage:

18

Therefore the upgrade should be clearly noticeable.


24. Inventory API

Implement:

GET /api/inventory

The server resolves the current character.

Do not send:

characterId

from Angular.

Example response:

{
  "items": [
    {
      "id": "character-item-uuid",
      "quantity": 1,
      "equipped": false,
      "item": {
        "key": "bandit-blade",
        "name": "Räuberklinge",
        "rarity": "COMMON",
        "equipmentSlot": "WEAPON",
        "requiredLevel": 1,
        "weaponDamage": 11,
        "bonusAttack": 1,
        "bonusHp": 0,
        "bonusArmor": 0,
        "iconPath": "/assets/items/bandit-blade.webp"
      }
    }
  ]
}

Do not expose unnecessary persistence details.


25. Equipment API

Implement:

GET /api/equipment

Response conceptually:

{
  "slots": {
    "WEAPON": {
      "characterItemId": "uuid",
      "item": {
        "key": "worn-short-sword",
        "name": "Abgenutztes Kurzschwert"
      }
    },
    "HEAD": null
  },
  "stats": {
    "maxHp": 100,
    "attack": 6,
    "weaponDamage": 8,
    "armor": 6
  }
}

Only expose slots supported by the current shared enum.


26. Equip API

Implement:

POST /api/equipment

Request:

{
  "characterItemId": "uuid"
}

Server determines:

character
item
equipment slot
validity

Response should return authoritative updated equipment and effective stats.

Do not require the frontend to manually reload multiple unrelated resources if one coherent response can provide the changed state.


27. Domain errors

Use stable errors.

Examples:

CHARACTER_ITEM_NOT_FOUND

ITEM_NOT_OWNED

ITEM_NOT_EQUIPPABLE

ITEM_LEVEL_REQUIREMENT_NOT_MET

INVALID_EQUIPMENT_SLOT

If another item occupies the slot, replacement is valid and should not be an error.


28. Transaction

Equip operations must be transactional.

Conceptually:

load/lock CharacterItem
↓
validate ownership
↓
load ItemDefinition
↓
validate level/slot
↓
remove/replace current CharacterEquipment relation
↓
create/update equipment relation
↓
commit

Concurrent equip requests must not create duplicate slot occupancy.


29. Inventory page route

Activate or create:

/inventory

Enable:

Inventar

in the left navigation.

The screen must remain inside the existing AppShell.


30. Inventory page layout

The inventory page should preserve the Ashen Realms visual language.

Recommended desktop composition:

left / center:
inventory item slots/grid

right:
selected item details / comparison

Avoid:

generic ecommerce product grid
admin table
SaaS data table

The inventory should feel like RPG equipment management.


31. Inventory grid

The full Vertical Slice concept allows approximately:

24 slots

However Slice 0.5 does not require enforcing capacity yet unless this already exists.

The UI may visually establish a slot-based inventory.

At minimum display:

item icon
quantity
rarity
equipped indicator

Empty slots may be displayed for visual structure.


32. Item selection

Clicking an owned item selects it.

Selected item details show:

name
icon
rarity
equipment slot
required level
stats
equipped state

Example:

Räuberklinge

Waffe
Stufe 1

11 Waffenschaden
+1 Angriff

33. Item comparison

When selecting an equippable item, compare it to the currently equipped item in the same slot.

Example:

Räuberklinge

11 Waffenschaden    +3
+1 Angriff          +1

compared to:

Abgenutztes Kurzschwert

8 Waffenschaden

The comparison exists for player clarity only.

The backend remains authoritative over actual stat calculations.


34. Comparison rules

Use simple deterministic numeric comparison.

For Slice 0.5, show only direct fixed stats:

weaponDamage
bonusAttack
bonusHp
bonusArmor

Do not invent an opaque overall item score.

Do not use Combat Power as the item comparison metric.


35. Equip action

Selected equippable, owned, level-valid items show:

Ausrüsten

Clicking it calls:

POST /api/equipment

After success:

inventory state refreshes
equipment state refreshes
effective stats refresh
equipped indicator updates

36. Already equipped state

If the selected item is already equipped:

Ausgerüstet

should be clearly visible.

Do not show another active:

Ausrüsten

button for the same item.


37. Required-level state

If the player's level is too low:

Display:

Benötigt Stufe X

and disable the equip action.

Backend must still enforce the same validation.


38. Equipment overview

The inventory or right context area should show current equipped slots.

At minimum:

Waffe
Kopf
Brust
Handschuhe
Beine
Stiefel
Amulett

Slots without items should clearly show:

Leer

Do not add fake starter gear to fill every visual slot.


39. Effective stats presentation

Display a small character stat summary:

Leben
Angriff
Waffenschaden
Rüstung

These values must come from the backend.

Example before:

Angriff: 6
Waffenschaden: 8

after:

Angriff: 7
Waffenschaden: 11

The visual update is important to make the upgrade obvious.


40. Topbar integration

If the TopBar displays HP or other affected values, refresh its authoritative character state after equipment changes.

Do not create a second conflicting stat representation.

Reuse the existing character/world store architecture where appropriate.


41. Reward screen integration

Slice 0.4 currently presents dropped items.

Add a useful navigation path:

Inventar öffnen

when equipment loot is obtained.

Example:

Räuberklinge erhalten

[ Inventar öffnen ]
[ Zur Jagd ]

Do not equip loot directly from the reward screen in this slice.

The player should consciously inspect and equip the item.


42. CharacterStatsService tests

At minimum test:

Starting weapon

Given:

baseAttack = 6
baseHp = 100

worn-short-sword:
weaponDamage = 8

verify:

attack = 6
weaponDamage = 8

Räuberklinge

Given:

bandit-blade:
weaponDamage = 11
bonusAttack = 1

verify:

attack = 7
weaponDamage = 11

Armor aggregation

Given multiple armor items:

sum bonusArmor

must be correct.


HP aggregation

Given equipment with:

bonusHp

verify:

maxHp = baseHp + bonuses

Combat Power

Verify formula:

HP / 10
+ attack × 2
+ weaponDamage × 2
+ armor × 1.5

43. Equipment service tests

At minimum test:

Equip owned weapon

owned Räuberklinge
↓
equip
↓
WEAPON = Räuberklinge

Replace weapon

Given:

WEAPON = Abgenutztes Kurzschwert

equip Räuberklinge.

Verify:

WEAPON = Räuberklinge
old sword still owned

Item not owned

Attempt to equip another character's item.

Expected:

ITEM_NOT_OWNED

Required level

Attempt to equip an item above character level.

Expected rejection.


Not equippable

Attempt to equip material/trade good.

Expected rejection.


Duplicate slot protection

Concurrent or repeated operations must not produce two equipped weapons.


44. Inventory API tests

Verify:

only current character items returned

and each item correctly reports:

definition data
quantity
equipped state

45. Combat integration tests

This is a critical Slice 0.5 proof.

Test:

combat with Abgenutztes Kurzschwert
↓
record player damage

then:

equip Räuberklinge
↓
start new combat
↓
record player damage

Expected:

new combat deals more damage

Do not assert only that a stat number changed.

Prove the upgrade actually affects gameplay.


46. Existing combat snapshots

Also verify:

start combat with old weapon
↓
equip change occurs outside combat if technically possible

must not alter the already-created combat snapshot.

Prefer preventing equipment changes while an active combat exists.

This is simpler and fits the game model.

Recommended rule:

Equipment cannot be changed during an active combat.

Return:

CHARACTER_IN_COMBAT

if attempted.


47. Travel restriction

Prefer also preventing equipment changes while travelling if the current product design considers travelling an unavailable interaction state.

However, do not introduce this rule solely by assumption if existing UI already permits inventory management during travel.

Follow the established behavior of the application.


48. Frontend tests

At minimum cover:

Inventory load

Opening:

/inventory

loads server inventory and equipment.


Item rendering

Räuberklinge displays:

icon
name
weapon damage
bonus attack

Comparison

Given equipped:

Abgenutztes Kurzschwert

and selected:

Räuberklinge

show the direct stat improvements.


Equip request

Click:

Ausrüsten

verify request contains only:

{
  "characterItemId": "uuid"
}

Updated state

After success:

Räuberklinge marked equipped
Abgenutztes Kurzschwert marked unequipped
effective attack updated
weapon damage updated

Invalid item

Server validation errors display in-shell.

No browser alerts.


49. Visual requirements

Follow the established Ashen Realms UI:

dark metal
stone
leather
bronze borders
high-quality item icons
clear rarity styling
large selected-item detail

The inventory must feel like an RPG screen.

Avoid:

spreadsheet table
white cards
generic admin UI
Material default list
Bootstrap grid

50. Item rarity

Use the established rarity levels:

COMMON / Basis
RARE / Selten
EPIC / Besonders

or the actual enum names already present in the repository.

Do not add more rarity tiers.

Rarity may affect:

border treatment
name emphasis
small accent

but must not override readability.


51. Item icons

Item icons are an important progression signal.

Use the existing generated Ashen Realms item assets.

Do not replace them with generic icon libraries if appropriate game-specific icons already exist.

A weapon upgrade should visually feel valuable before reading every number.


52. Accessibility

Required:

keyboard-accessible item selection
real buttons
visible focus states
textual equipped indicator
textual rarity
readable stat changes
disabled action explanation

Do not encode improvements only through green/red color.

Example:

Waffenschaden 11 (+3)

is preferable to color alone.


53. Server-authority checklist

Server decides:

which items character owns
which item is equipped
which equipment slot an item uses
whether level requirement is met
effective HP
effective attack
effective weapon damage
effective armor
Combat Power
combat snapshot stats

Client decides only:

which owned item the player selects
whether the player requests to equip it

54. Database changes

Create or extend migrations for:

character_equipment

and any missing indexes/constraints for character_item.

Required constraints include conceptually:

unique(characterId, slot)

and appropriate ownership relations.

Do not drop or recreate existing loot/combat/hunt/world data.


55. Seed changes

Ensure the demo character starts with:

Abgenutztes Kurzschwert

as a real CharacterItem and equipped WEAPON item.

The seed must remain idempotent.

Re-running must not:

duplicate starting sword
duplicate equipment row
delete earned Räuberklinge
reset player inventory
reset rewards
reset XP
reset silver

Be especially careful not to turn the seed into a player-state reset.


56. Definition of Done

Playable Slice 0.5 is complete when this complete flow works:

start with Abgenutztes Kurzschwert
↓
travel to Verbrannte Straße
↓
hunt Straßenräuber
↓
win combat
↓
Räuberklinge drops
↓
reward persists
↓
open Inventar
↓
see Abgenutztes Kurzschwert equipped
↓
select Räuberklinge
↓
see stat comparison
↓
click Ausrüsten
↓
server replaces weapon
↓
Räuberklinge becomes equipped
↓
effective stats increase
↓
return to hunt
↓
start another combat
↓
player deals more damage than before

57. Required verification

Before completion run:

API unit tests
API integration tests
Angular tests
API build
Angular build
migration compilation
migration execution
seed execution

Perform manual browser walkthroughs for:

inventory with starting weapon

inventory after item drop

select Räuberklinge

comparison against starting sword

equip Räuberklinge

refresh inventory page

equipped state survives refresh

start new combat

verify increased player damage

Also verify error cases:

equip item not owned
equip invalid item
equip level-locked item
equip while active combat exists

where applicable.


58. Acceptance criteria

The slice is accepted when:

  • inventory is backed by persistent CharacterItem data
  • starting weapon uses the same item/equipment model as loot
  • equipment state is persisted
  • only owned items can be equipped
  • equipment slots are validated server-side
  • required level is validated server-side
  • replacing an item does not delete the previous item
  • CharacterStatsService is the authoritative effective-stat source
  • combat creation uses CharacterStatsService
  • active combat keeps its stat snapshot
  • Räuberklinge increases weapon damage
  • Räuberklinge increases attack
  • next combat demonstrably deals more damage
  • Angular does not calculate authoritative stats
  • inventory screen matches the existing Ashen Realms visual language
  • item comparison is understandable
  • refresh preserves equipment state
  • all tests pass
  • frontend and backend builds succeed

59. Handoff after Slice 0.5

At the end of Slice 0.5, Ashen Realms has proven its first complete gameplay loop:

Travel
↓
Hunt
↓
Choose enemy
↓
Combat
↓
Victory
↓
Loot
↓
Inventory
↓
Equip upgrade
↓
Become stronger
↓
Fight again

This is the first major Vertical Slice milestone.

Before dramatically expanding the content, the next development step should build on this verified loop rather than introducing unrelated systems.

Possible next slices include:

0.6  Full First Combat Actions

with:

Schwerer Hieb
Schildstoß
Verteidigen
Trank

or:

0.6  Complete Verbrannte Straße

with additional enemies, content and first progression pacing.

The decision should be based on which aspect of the core loop should be validated next.


60. Key gameplay proof

The most important acceptance test is not simply:

Räuberklinge is equipped.

It is:

Before:
the enemy takes X damage.

Player equips Räuberklinge.

After:
the same type of enemy takes visibly more damage.

If that works correctly, the first central Ashen Realms progression promise has been proven:

A good item makes the player meaningfully stronger.