openup: doc restructure - template alignment + design sequences (non-PSP)

Align the OpenUP markdown set to the blank templates (process-data repo,
templates/openup/) per BUILD-CHECKLIST 7. Scenario step text unchanged;
the EA model needs no change (brief descriptions already live in the
model's UC/actor notes).

- vision.md: positioning tables, stakeholder summary, user environment,
  needs-and-features table (R1/R2), other product requirements
- use-case-model.md: per-UC specification sections (brief description,
  actor descriptions, named alternative/exception subsections with
  numbered steps, post-conditions, special requirements)
- supplementary-specifications.md: category tables with priority and
  use-case columns + reverse coverage matrix; IDs unchanged
- test/quality-check-procedure.md: migrated from Joplin (single content
  home); stale open items marked resolved (WI-1/WI-2)
- analysis-design/sequences/: 12 PlantUML design sequences (lifelines =
  analysis classes, messages = design elements; per the PlantUML layout
  style guide) + PNG renders; embedded in Architecture Notebook 9.3
- Joplin consolidated: project content notes deleted (all live in
  docs/game + docs/openup); Joplin keeps the status note only
This commit is contained in:
Jan-Peter von Hunnius 2026-10-05 11:22:38 +02:00
parent 93bb034752
commit 193562cbc4
30 changed files with 1200 additions and 124 deletions

View file

@ -213,9 +213,35 @@ associations); renders in this folder, generated by `scripts/openup/export-diagr
### 9.3 Interaction View
Not yet modeled: no sequence diagrams exist for the Skara Brae MVP (the EA model carries scenarios with
per-step actors, not interactions). Candidate first sequences: UC-08 adventure request→combat→loot, and
UC-01/02 auth flows. Adding them is a future work item (PlantUML prototype → EA per the workflow canon).
Design sequences for all twelve use-case realizations. Sources are the version-controlled
PlantUML files under `sequences/` (working copy per the PlantUML→EA workflow canon; the EA
model carries scenarios with per-step actors). Render: `java -jar plantuml.jar -tpng
-charset UTF-8 sequences/*.puml`; lifelines = the realization's analysis classes
(mapping §2), messages = the actual design elements (routes and domain functions).
![UC-01 Register](sequences/uc-01-register.png)
![UC-02 Authenticate](sequences/uc-02-authenticate.png)
![UC-03 Manage Characters](sequences/uc-03-manage-characters.png)
![UC-04 Browse Catalogue](sequences/uc-04-browse-catalogue.png)
![UC-05 Train Stat](sequences/uc-05-train-stat.png)
![UC-06 Manage Equipment](sequences/uc-06-manage-equipment.png)
![UC-07 Trade at Shop](sequences/uc-07-trade-at-shop.png)
![UC-08 Run Adventure](sequences/uc-08-run-adventure.png)
![UC-09 Heal at Temple](sequences/uc-09-heal-at-temple.png)
![UC-10 Practice Bard Song](sequences/uc-10-practice-bard-song.png)
![UC-11 Study Magic](sequences/uc-11-study-magic.png)
![UC-12 Level Up](sequences/uc-12-level-up.png)
### 9.4 Operational View
@ -230,10 +256,11 @@ channel in the MVP (WebSocket notifications are a later-phase feature, vision.md
|---|---|---|
| Use Case Overview | `../requirements/use-case-overview.png` | actors, use cases, associations |
| UC-01…UC-12 Realization | `uc-01-realization.png` … `uc-12-realization.png` | analysis classes + associations per UC |
| UC-01…UC-12 Sequence | `sequences/uc-01-register.png` … `sequences/uc-12-level-up.png` (sources: `sequences/*.puml`) | design interactions per UC |
| Component Architecture | `component-architecture.png` | joint component model, dependency rule |
All three sets are renders of the EA model (`docs/openup/model/skara-brae.eapx`) produced by
`scripts/openup/export-diagram-images.ps1`; regenerate after model changes (read-only, layout-preserving).
EA model renders are produced by `scripts/openup/export-diagram-images.ps1`; sequence
renders by PlantUML from the committed sources. Regenerate after changes (drift rule).
## 10. References

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -0,0 +1,23 @@
@startuml uc-01-register
autonumber
title UC-01 Register Account (design sequence)
actor Guest
participant "AuthBoundary\n«POST /api/auth/register»" as boundary <<boundary>>
participant "AccountRegistry\nsb-api::routes::register" as control <<control>>
participant "UserAccount\nsb-shared::user::User" as entity <<entity>>
== Register ==
Guest -> boundary : name (3-32), password (>= 6)
boundary -> control : register(name, password)
alt name invalid / password too short
control --> boundary : 400
else name taken
control --> boundary : 409 CONFLICT
else valid
control -> entity : hash password (argon2, sb-auth)
control -> entity : assign player role + next id
control -> entity : persist (sb-db)
control --> boundary : JWT HS256 (player role)
boundary --> Guest : {token, user_id, name, role}
end
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1,20 @@
@startuml uc-02-authenticate
autonumber
title UC-02 Authenticate (design sequence)
actor Guest
participant "TokenService\n«POST /api/auth/login»" as boundary <<boundary>>
participant "Authenticator\nsb-api::routes::login" as control <<control>>
== Login ==
Guest -> boundary : name, password
boundary -> control : login(name, password)
alt unknown name or wrong password
control --> boundary : 401 UNAUTHORIZED
else valid
control -> control : verify_password (sb-auth::hash)
control -> control : create_token (sb-auth::jwt, real role)
control --> boundary : {token, user_id, name, role}
boundary --> Guest : {token, user_id, name, role}
end
note over boundary : auth_middleware guards\nUC-03..UC-12
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View file

@ -0,0 +1,31 @@
@startuml uc-03-manage-characters
autonumber
title UC-03 Manage Characters (design sequence)
actor Player
participant "CharacterCatalog\n«/api/users/{id}/characters»" as boundary <<boundary>>
participant "CharacterFactory\nsb-core::character::create_character" as control <<control>>
participant "Character\nsb-shared::character" as entity <<entity>>
participant "ResourceRegenerator\napply_regen" as regen <<entity>>
== Create ==
Player -> boundary : POST name, race, class (user id)
boundary -> control : create_character(rng, user, name, race, class)
alt user missing
control --> boundary : 404
else name invalid
control --> boundary : 400
else valid
control -> entity : roll stats (d6+5 +- race mods)
control -> entity : max HP/energy/nerve from class
control -> entity : gold 50 + level-1 spells (casters)
control -> entity : assign next character id, persist
control --> boundary : character
boundary --> Player : character (full)
end
== List / View ==
Player -> boundary : GET characters (own; admin: any)
boundary -> regen : apply_regen(character)
regen --> boundary : character (regenerated)
boundary --> Player : characters (regen applied)
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -0,0 +1,17 @@
@startuml uc-04-browse-catalogue
autonumber
title UC-04 Browse Game Catalogue (design sequence)
actor Player
participant "CatalogueBrowser\n«/api/{races,classes,items,enemies,adventures}»" as boundary <<boundary>>
participant "GameData\nsb-core::data + items" as entity <<entity>>
== Browse ==
Player -> boundary : GET catalogue (optional type/quality/slot filters)
boundary -> entity : read LazyLock catalogue (sb-common JSON)
alt unknown item id
entity --> boundary : 404
else hit (possibly empty filter result)
entity --> boundary : static data (classes + is_caster)
boundary --> Player : races / classes / items / enemies / zones
end
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View file

@ -0,0 +1,28 @@
@startuml uc-05-train-stat
autonumber
title UC-05 Train Stat (design sequence)
actor Player
participant "GymBoundary\n«/api/characters/{id}/gym»" as boundary <<boundary>>
participant "TrainingService\nsb-core::services::train_stat" as control <<control>>
participant "ResourceRegenerator\napply_regen" as regen <<entity>>
participant "TrainingGrounds\nTRAINING_GROUNDS + training_cost" as entity <<entity>>
== Train ==
Player -> boundary : POST stat name
boundary -> regen : apply_regen(character)
regen --> boundary : character (regenerated)
boundary -> control : train_stat(character, stat)
control -> entity : cost = training_cost (x1.3 per 10 points)
alt not enough energy
control --> boundary : 422 (DomainError)
else not enough gold
control --> boundary : 422 (DomainError)
else unknown stat
control --> boundary : 400 (DomainError)
else ok
control -> entity : stat +1 (energy -1, gold -cost)
control -> entity : constitution: recompute max HP (clamp current)
control --> boundary : {success, message, character}
boundary --> Player : {success, message, character}
end
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -0,0 +1,30 @@
@startuml uc-06-manage-equipment
autonumber
title UC-06 Manage Equipment (design sequence)
actor Player
participant "EquipmentBoundary\n«/api/characters/{id}/{equip,unequip}»" as boundary <<boundary>>
participant "EquipmentService\nsb-core::services::{equip,unequip}" as control <<control>>
participant "Equipment/Inventory\nCharacter.equipment/.inventory" as entity <<entity>>
== Equip ==
Player -> boundary : POST equip item id
boundary -> control : equip_item(character, item)
alt item not in inventory
control --> boundary : 400 (DomainError)
else ok
control -> entity : inventory -> slot\n(previous item returns to inventory)
control --> boundary : character
boundary --> Player : updated character
end
== Unequip ==
Player -> boundary : POST unequip slot
boundary -> control : unequip_item(character, slot)
alt slot empty
control --> boundary : 400 (DomainError)
else ok
control -> entity : slot -> inventory
control --> boundary : character
boundary --> Player : updated character
end
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View file

@ -0,0 +1,37 @@
@startuml uc-07-trade-at-shop
autonumber
title UC-07 Trade at Shop (design sequence)
actor Player
participant "ShopBoundary\n«/api/characters/{id}/shop»" as boundary <<boundary>>
participant "ShopService\nsb-api::routes::{shop_action,buy,sell}" as control <<control>>
participant "ShopStock\nAppState.shop_stock" as entity <<entity>>
== View ==
Player -> boundary : GET shop
boundary --> Player : always-stocked + rotating stock, inventory
== Buy ==
Player -> boundary : POST buy + item id
boundary -> control : buy_item(character, item)
control -> entity : check stock (non-permanent: decrement)
alt stock exhausted
entity --> control : 0 remaining
control --> boundary : 410 GONE
else gold < price
control --> boundary : 422
else ok
control -> entity : stock -1, gold -price, inventory +item
control --> boundary : {success, message, gold}
boundary --> Player : {success, message, gold}
end
== Sell ==
Player -> boundary : POST sell + item id
boundary -> control : sell_item(character, item)
alt item not owned / unknown action
control --> boundary : 400
else ok
control -> entity : inventory -item, gold +value/2, stock +1 (non-permanent)
boundary --> Player : {success, message, gold}
end
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View file

@ -0,0 +1,52 @@
@startuml uc-08-run-adventure
autonumber
title UC-08 Run Adventure (design sequence)
actor Player
participant "AdventureBoundary\n«/api/characters/{id}/adventure»" as boundary <<boundary>>
participant "AdventureService\nsb-core::adventure::run_adventure" as control <<control>>
participant "ResourceRegenerator\napply_regen" as regen <<entity>>
participant "CombatEngine\nsb-core::combat" as combat <<entity>>
participant "Combatant\nCombatStats" as combatant <<entity>>
participant "SpellEffect / BardSong\nsb-core::{spells,songs}" as fx <<entity>>
== Validate ==
Player -> boundary : POST adventure (zone, default skara_brae_streets)
boundary -> control : run_adventure(rng, character, zone)
alt zone unknown
control --> boundary : 404
else HP <= 0
control --> boundary : 400
else nerve < zone cost
control --> boundary : 422
else ok
end
control -> regen : apply_regen(character)
regen --> control : character (regenerated)
control -> control : nerve -= zone cost
== Encounter ==
control -> combatant : pick_enemy(rng, weighted table)
alt unknown enemy id (data error)
control --> boundary : 500 DomainError::UnknownEncounterEnemy
end
== Combat (rounds) ==
loop until one side falls
control -> combat : open_ended_roll(rng, attack)
combat -> combat : OB vs DB, crit table\n(level-gap damage caps)
combat -> fx : bard song effect, active spell dmg/heal
combat --> control : round result
end
== Resolve ==
alt player victory
control -> combatant : XP + gold per enemy def
control -> combatant : HP = last defender HP
control -> combatant : loot rolls (catalogue items only)
else defeat
control -> combatant : HP = 0, no rewards
end
control -> fx : spell-point upkeep (win or lose)
control --> boundary : {zone, enemy, combat_log, loot}
boundary --> Player : {zone, enemy, combat_log, loot}
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View file

@ -0,0 +1,22 @@
@startuml uc-09-heal-at-temple
autonumber
title UC-09 Heal at Temple (design sequence)
actor Player
participant "TempleBoundary\n«/api/characters/{id}/heal»" as boundary <<boundary>>
participant "HealingService\nsb-api::routes::heal_character" as control <<control>>
== Heal ==
Player -> boundary : POST heal (optionally partial)
boundary -> control : heal_character(character, partial)
control -> control : missing HP\nfull: all; partial: half (ceil)\ncost = healed, min 1 gold
alt already at full HP
control --> boundary : 400
else gold < cost
control --> boundary : 422
else ok
control -> control : gold -= cost, HP restored, persist
control --> boundary : {hp, max_hp, gold, healed: true}
boundary --> Player : {hp, max_hp, gold, healed: true}
end
note over control : handler-side logic - recorded debt\n(Architecture Notebook D1, Q3)
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -0,0 +1,29 @@
@startuml uc-10-practice-bard-song
autonumber
title UC-10 Practice Bard Song (design sequence)
actor Player
participant "SongBoundary\n«/api/characters/{id}/songs»" as boundary <<boundary>>
participant "SongService\nsb-api::routes::set_song" as control <<control>>
participant "BardSong\nBARD_SONGS + active_song" as entity <<entity>>
== View ==
Player -> boundary : GET songs
boundary -> entity : available_songs (level-gated)
boundary --> Player : songs + effects + active song
== Set / Clear ==
Player -> boundary : POST song id (or empty to clear)
boundary -> control : set_song(character, song)
alt non-Bard
control --> boundary : 403
else unknown song
control --> boundary : 404
else level too low
control --> boundary : 422
else ok
control -> entity : active_song = song (or None), persist
control --> boundary : {success, message, active_song}
boundary --> Player : {success, message, active_song}
end
note over entity : active song applies in UC-08 combat (step 5)
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View file

@ -0,0 +1,51 @@
@startuml uc-11-study-magic
autonumber
title UC-11 Study Magic (design sequence)
actor Player
participant "MagicBoundary\n«/api/characters/{id}/spells»" as boundary <<boundary>>
participant "MagicService\nsb-api::routes::{get_spells,learn}" as control <<control>>
participant "Spell\nSpellDef + known/active/points" as entity <<entity>>
== View spellbook ==
Player -> boundary : GET spells
boundary -> control : get_spells(character)
control -> entity : caster class, spell level = (level/3)+1
control --> boundary : spells (marked known) + learn costs 2-7
boundary --> Player : spellbook + spell points
== Learn spell level ==
Player -> boundary : POST learn level (1-7)
boundary -> control : learn_spell_level(character, level)
alt non-caster
control --> boundary : 403
else level out of 1-7
control --> boundary : 400
else level above current spell level
control --> boundary : 422
else gold < learn cost
control --> boundary : 422
else no new spells at level
control --> boundary : 400
else ok
control -> entity : gold -= cost, add unknown spell ids
control -> entity : recompute + refill max spell points, persist
control --> boundary : {learned_count, spell_points, max_spell_points, gold}
boundary --> Player : {learned_count, ...}
end
== Activate / Clear active spell ==
Player -> boundary : POST spell id (or empty)
boundary -> control : activate_spell(character, spell)
alt spell not known
control --> boundary : 403
else spell unknown
control --> boundary : 404
else spell points insufficient
control --> boundary : 422
else ok
control -> entity : points -= cost, active_spell = spell, persist
control --> boundary : {active_spell, spell_points}
boundary --> Player : {active_spell, spell_points}
end
note over entity : active spell acts in UC-08 combat\n(dmg/heal per round + upkeep)
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View file

@ -0,0 +1,26 @@
@startuml uc-12-level-up
autonumber
title UC-12 Level Up at Review Board (design sequence)
actor Player
participant "ReviewBoundary\n«/api/characters/{id}/level-up»" as boundary <<boundary>>
participant "ReviewBoard\nsb-core::character::level_up" as control <<control>>
participant "Progression\nxp_for_level + level_up_cost" as entity <<entity>>
== Level up ==
Player -> boundary : POST level-up
boundary -> control : level_up(character)
control -> entity : xp_for_level(next) = 18*100*level^2
control -> entity : cost = 500 * next level
alt at max level 100
control --> boundary : 422 (DomainError)
else XP below threshold
control --> boundary : 422 (DomainError)
else gold < cost
control --> boundary : 422 (DomainError)
else ok
control -> entity : level += 1, gold -= cost
control -> entity : recompute + fully restore\nHP/energy/nerve/max SP, persist
control --> boundary : {success, message, character}
boundary --> Player : {success, message, character}
end
@enduml