diff --git a/docs/openup/analysis-design/architecture-notebook.md b/docs/openup/analysis-design/architecture-notebook.md index 1f13268..af60f59 100644 --- a/docs/openup/analysis-design/architecture-notebook.md +++ b/docs/openup/analysis-design/architecture-notebook.md @@ -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 diff --git a/docs/openup/analysis-design/sequences/uc-01-register.png b/docs/openup/analysis-design/sequences/uc-01-register.png new file mode 100644 index 0000000..9c9aefe Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-01-register.png differ diff --git a/docs/openup/analysis-design/sequences/uc-01-register.puml b/docs/openup/analysis-design/sequences/uc-01-register.puml new file mode 100644 index 0000000..ac8e6ce --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-01-register.puml @@ -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 <> +participant "AccountRegistry\nsb-api::routes::register" as control <> +participant "UserAccount\nsb-shared::user::User" as 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 diff --git a/docs/openup/analysis-design/sequences/uc-02-authenticate.png b/docs/openup/analysis-design/sequences/uc-02-authenticate.png new file mode 100644 index 0000000..f26ff4f Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-02-authenticate.png differ diff --git a/docs/openup/analysis-design/sequences/uc-02-authenticate.puml b/docs/openup/analysis-design/sequences/uc-02-authenticate.puml new file mode 100644 index 0000000..8d1cc5f --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-02-authenticate.puml @@ -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 <> +participant "Authenticator\nsb-api::routes::login" as 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 diff --git a/docs/openup/analysis-design/sequences/uc-03-manage-characters.png b/docs/openup/analysis-design/sequences/uc-03-manage-characters.png new file mode 100644 index 0000000..aa93e16 Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-03-manage-characters.png differ diff --git a/docs/openup/analysis-design/sequences/uc-03-manage-characters.puml b/docs/openup/analysis-design/sequences/uc-03-manage-characters.puml new file mode 100644 index 0000000..58f4d53 --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-03-manage-characters.puml @@ -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 <> +participant "CharacterFactory\nsb-core::character::create_character" as control <> +participant "Character\nsb-shared::character" as entity <> +participant "ResourceRegenerator\napply_regen" as regen <> + +== 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 diff --git a/docs/openup/analysis-design/sequences/uc-04-browse-catalogue.png b/docs/openup/analysis-design/sequences/uc-04-browse-catalogue.png new file mode 100644 index 0000000..17da6ea Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-04-browse-catalogue.png differ diff --git a/docs/openup/analysis-design/sequences/uc-04-browse-catalogue.puml b/docs/openup/analysis-design/sequences/uc-04-browse-catalogue.puml new file mode 100644 index 0000000..fd10dc9 --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-04-browse-catalogue.puml @@ -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 <> +participant "GameData\nsb-core::data + items" as 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 diff --git a/docs/openup/analysis-design/sequences/uc-05-train-stat.png b/docs/openup/analysis-design/sequences/uc-05-train-stat.png new file mode 100644 index 0000000..9457c46 Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-05-train-stat.png differ diff --git a/docs/openup/analysis-design/sequences/uc-05-train-stat.puml b/docs/openup/analysis-design/sequences/uc-05-train-stat.puml new file mode 100644 index 0000000..8bda62e --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-05-train-stat.puml @@ -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 <> +participant "TrainingService\nsb-core::services::train_stat" as control <> +participant "ResourceRegenerator\napply_regen" as regen <> +participant "TrainingGrounds\nTRAINING_GROUNDS + training_cost" as 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 diff --git a/docs/openup/analysis-design/sequences/uc-06-manage-equipment.png b/docs/openup/analysis-design/sequences/uc-06-manage-equipment.png new file mode 100644 index 0000000..7c9a9f9 Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-06-manage-equipment.png differ diff --git a/docs/openup/analysis-design/sequences/uc-06-manage-equipment.puml b/docs/openup/analysis-design/sequences/uc-06-manage-equipment.puml new file mode 100644 index 0000000..6969d53 --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-06-manage-equipment.puml @@ -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 <> +participant "EquipmentService\nsb-core::services::{equip,unequip}" as control <> +participant "Equipment/Inventory\nCharacter.equipment/.inventory" as 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 diff --git a/docs/openup/analysis-design/sequences/uc-07-trade-at-shop.png b/docs/openup/analysis-design/sequences/uc-07-trade-at-shop.png new file mode 100644 index 0000000..bd9ce45 Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-07-trade-at-shop.png differ diff --git a/docs/openup/analysis-design/sequences/uc-07-trade-at-shop.puml b/docs/openup/analysis-design/sequences/uc-07-trade-at-shop.puml new file mode 100644 index 0000000..0cecf5b --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-07-trade-at-shop.puml @@ -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 <> +participant "ShopService\nsb-api::routes::{shop_action,buy,sell}" as control <> +participant "ShopStock\nAppState.shop_stock" as 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 diff --git a/docs/openup/analysis-design/sequences/uc-08-run-adventure.png b/docs/openup/analysis-design/sequences/uc-08-run-adventure.png new file mode 100644 index 0000000..68946ed Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-08-run-adventure.png differ diff --git a/docs/openup/analysis-design/sequences/uc-08-run-adventure.puml b/docs/openup/analysis-design/sequences/uc-08-run-adventure.puml new file mode 100644 index 0000000..fb089ca --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-08-run-adventure.puml @@ -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 <> +participant "AdventureService\nsb-core::adventure::run_adventure" as control <> +participant "ResourceRegenerator\napply_regen" as regen <> +participant "CombatEngine\nsb-core::combat" as combat <> +participant "Combatant\nCombatStats" as combatant <> +participant "SpellEffect / BardSong\nsb-core::{spells,songs}" as fx <> + +== 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 diff --git a/docs/openup/analysis-design/sequences/uc-09-heal-at-temple.png b/docs/openup/analysis-design/sequences/uc-09-heal-at-temple.png new file mode 100644 index 0000000..bddf694 Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-09-heal-at-temple.png differ diff --git a/docs/openup/analysis-design/sequences/uc-09-heal-at-temple.puml b/docs/openup/analysis-design/sequences/uc-09-heal-at-temple.puml new file mode 100644 index 0000000..e3e9624 --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-09-heal-at-temple.puml @@ -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 <> +participant "HealingService\nsb-api::routes::heal_character" as 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 diff --git a/docs/openup/analysis-design/sequences/uc-10-practice-bard-song.png b/docs/openup/analysis-design/sequences/uc-10-practice-bard-song.png new file mode 100644 index 0000000..7bfbc83 Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-10-practice-bard-song.png differ diff --git a/docs/openup/analysis-design/sequences/uc-10-practice-bard-song.puml b/docs/openup/analysis-design/sequences/uc-10-practice-bard-song.puml new file mode 100644 index 0000000..14de433 --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-10-practice-bard-song.puml @@ -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 <> +participant "SongService\nsb-api::routes::set_song" as control <> +participant "BardSong\nBARD_SONGS + active_song" as 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 diff --git a/docs/openup/analysis-design/sequences/uc-11-study-magic.png b/docs/openup/analysis-design/sequences/uc-11-study-magic.png new file mode 100644 index 0000000..0074f38 Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-11-study-magic.png differ diff --git a/docs/openup/analysis-design/sequences/uc-11-study-magic.puml b/docs/openup/analysis-design/sequences/uc-11-study-magic.puml new file mode 100644 index 0000000..5b3622f --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-11-study-magic.puml @@ -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 <> +participant "MagicService\nsb-api::routes::{get_spells,learn}" as control <> +participant "Spell\nSpellDef + known/active/points" as 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 diff --git a/docs/openup/analysis-design/sequences/uc-12-level-up.png b/docs/openup/analysis-design/sequences/uc-12-level-up.png new file mode 100644 index 0000000..4bf7b14 Binary files /dev/null and b/docs/openup/analysis-design/sequences/uc-12-level-up.png differ diff --git a/docs/openup/analysis-design/sequences/uc-12-level-up.puml b/docs/openup/analysis-design/sequences/uc-12-level-up.puml new file mode 100644 index 0000000..eddd9b9 --- /dev/null +++ b/docs/openup/analysis-design/sequences/uc-12-level-up.puml @@ -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 <> +participant "ReviewBoard\nsb-core::character::level_up" as control <> +participant "Progression\nxp_for_level + level_up_cost" as 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 diff --git a/docs/openup/model/BUILD-CHECKLIST.md b/docs/openup/model/BUILD-CHECKLIST.md index 98307ab..efdfd17 100644 --- a/docs/openup/model/BUILD-CHECKLIST.md +++ b/docs/openup/model/BUILD-CHECKLIST.md @@ -249,3 +249,34 @@ Read-only on the model: renders 14 diagrams to PNG for the markdown documents image methods live on **ProjectInterface** (not Repository); `PutDiagramImageToFile(id, path, 0)` = PNG; the diagram must be `OpenDiagram`-ed first or a blank 241×151 stub is written. Regenerate after model changes; renders reflect the manual layout. + +--- + +## 7. Documentation restructure (2026-10-05, non-PSP effort) + +The OpenUP markdown set was aligned to the blank templates (process-data repo, +`templates/openup/`) — no EA model change required: scenario content is unchanged, +reformatted only; the model's UC notes/actors already carry the brief descriptions the +template sections now expose. XMI backup added 2026-10-05 via manual EA export +(`exports/xmi-backup.xml`); the COM export path is broken in this EA 15 build +(`ExportPackageXMI` filename param declared as Object in the typelib — verified across +PowerShell/JScript/VBScript, arities 2–6; `ExportProjectXML` returns false silently). + +Restructured to template form: +- `requirements/vision.md` — positioning tables, stakeholder summary, user environment, + needs-and-features table (R1/R2), other product requirements. +- `requirements/use-case-model.md` — per-UC specification sections (Brief Description, + Actor Brief Descriptions, Preconditions, Basic Flow, named Alternative/Exception + subsections with numbered steps, Post-conditions, Special Requirements). Flow step + text unchanged from version 1.0. +- `requirements/supplementary-specifications.md` — category tables with Priority and + Use-Case columns + reverse Use Case Coverage Matrix; IDs unchanged; project-specific + categories (Quality Gates, Data) kept, DOC-01 added. +- `test/quality-check-procedure.md` — migrated from Joplin (single content home); + stale open items marked resolved (WI-1/WI-2). + +New design content: +- `analysis-design/sequences/uc-01..uc-12.puml` + PNG renders — design sequences per + realization (lifelines = analysis classes, messages = design elements; PlantUML per + the layout style guide). Embedded in Architecture Notebook §9.3, indexed §9.5. + EA import via PlantUML `-txmi` remains optional future work (workflow canon). diff --git a/docs/openup/requirements/supplementary-specifications.md b/docs/openup/requirements/supplementary-specifications.md index f876da5..79472b6 100644 --- a/docs/openup/requirements/supplementary-specifications.md +++ b/docs/openup/requirements/supplementary-specifications.md @@ -1,51 +1,93 @@ # Skara Brae — Supplementary Specifications -Version 1.0 (WI-2, 2026-10-03) · OpenUP artifact: `requirements/supplementary-specifications.md` -Non-functional requirements and constraints for the server MVP. Facts cite code where they are implemented; assumptions are marked. +`Skara Brae` — OpenUP Requirements · version 2.0 (2026-10-05, restructured to the OpenUP +supplementary-specifications template: category tables with priority and use-case trace + +coverage matrix; requirement IDs unchanged from version 1.0). +Non-functional requirements and constraints for the server MVP. Facts cite code where they +are implemented; assumptions are marked. Category tailoring vs stock OpenUP: this project +adds **Quality Gates** and **Data** categories (project-specific acceptance and +game-data rules); a Standards category is not applicable at R1. -## 1. Performance / Scalability +# Performance / Scalability -- **NFR-01 (in-memory state):** all game state (users, characters, shop stock) is held in process memory behind `tokio::sync::RwLock` (`crates/sb-api/src/routes/mod.rs:33–42`). Single-process deployment; persistence is write-through to SQLite, reads never hit the DB. -- **NFR-02 (lazy regeneration):** resource regeneration is computed on every character access from elapsed wall-clock time (`apply_regen`, character.rs:105–122) — no timers, no background ticks. Intervals: energy 180 s, nerve 300 s, HP 300 s (5% max HP per tick). -- **NFR-03 (client polling):** clients poll character state to observe regeneration; the assumed UI refresh cadence is ~15 s *(assumption carried from project planning; server-side behavior is poll-frequency-independent by design NFR-02)*. -- **NFR-04 (static catalogue):** items, enemies, zones, spells, and crit tables are compiled in via `include_str!` from `skara-brae-common` JSON and parsed once behind `LazyLock`; catalogue reads are lock-free after first access. +| **ID** | **Requirement** | **Priority** | **Use Cases** | +| --- | --- | --- | --- | +| NFR-01 | In-memory state: all game state (users, characters, shop stock) is held in process memory behind `tokio::sync::RwLock` (`crates/sb-api/src/routes/mod.rs:33–42`). Single-process deployment; persistence is write-through to SQLite, reads never hit the DB. | High | UC-01..UC-12 | +| NFR-02 | Lazy regeneration: resource regeneration is computed on every character access from elapsed wall-clock time (`apply_regen`, character.rs:105–122) — no timers, no background ticks. Intervals: energy 180 s, nerve 300 s, HP 300 s (5% max HP per tick). | High | UC-03, UC-05, UC-08 | +| NFR-03 | Client polling: clients poll character state to observe regeneration; the assumed UI refresh cadence is ~15 s *(assumption carried from project planning; server-side behavior is poll-frequency-independent by design NFR-02)*. | Medium | UC-03, UC-05, UC-08 | +| NFR-04 | Static catalogue: items, enemies, zones, spells, and crit tables are compiled in via `include_str!` from `skara-brae-common` JSON and parsed once behind `LazyLock`; catalogue reads are lock-free after first access. | High | UC-04 | -## 2. Reliability / Correctness +# Reliability / Correctness -- **NFR-05 (deterministic domain logic):** all domain randomness is injected `StdRng` (WI-1); identical seeds produce identical outcomes. Request paths construct RNG only at `request_rng()`. -- **NFR-06 (persistence best-effort):** persistence failures are logged (`tracing::error`) and do not fail the request; in-memory state remains authoritative for the process lifetime. -- **NFR-07 (referential data integrity):** zone encounter tables must reference existing enemy ids and loot drops must reference existing item ids; violations are detected at first use (WI-2: `DomainError::UnknownEncounterEnemy` → 500 instead of a panic). +| **ID** | **Requirement** | **Priority** | **Use Cases** | +| --- | --- | --- | --- | +| NFR-05 | Deterministic domain logic: all domain randomness is injected `StdRng` (WI-1); identical seeds produce identical outcomes. Request paths construct RNG only at `request_rng()`. | High | UC-03, UC-07, UC-08 | +| NFR-06 | Persistence best-effort: persistence failures are logged (`tracing::error`) and do not fail the request; in-memory state remains authoritative for the process lifetime. | Medium | UC-01, UC-03, UC-05..UC-12 | +| NFR-07 | Referential data integrity: zone encounter tables must reference existing enemy ids and loot drops must reference existing item ids; violations are detected at first use (WI-2: `DomainError::UnknownEncounterEnemy` → 500 instead of a panic). | High | UC-08 | -## 3. Quality Gates (per-commit acceptance) +# Quality Gates (per-commit acceptance) -- **NFR-08:** `cargo fmt --check` clean (fmt baseline established by WI-2). -- **NFR-09:** `cargo clippy --all-targets -- -D warnings` clean — zero-warning policy. -- **NFR-10:** `cargo test --workspace` fully green (45 sb-core tests at WI-2 start; suite only grows). -- **NFR-11 (WI-2-specific):** behavior-identical refactor — JSON field names/order, HTTP status codes, game semantics, and RNG draw order unchanged. -- **NFR-12:** no `unwrap()`/`expect()` on request paths outside the documented keep-with-reason invariants in the design-note §6 ledger (covers sb-api, sb-core, sb-db mutex poisoning, sb-auth time/entropy invariants). +| **ID** | **Requirement** | **Priority** | **Use Cases** | +| --- | --- | --- | --- | +| NFR-08 | `cargo fmt --check` clean (fmt baseline established by WI-2). | High | all | +| NFR-09 | `cargo clippy --all-targets -- -D warnings` clean — zero-warning policy. | High | all | +| NFR-10 | `cargo test --workspace` fully green (45 sb-core tests at WI-2 start; suite only grows). | High | all | +| NFR-11 | Behavior-identical refactor — JSON field names/order, HTTP status codes, game semantics, and RNG draw order unchanged (WI-2-specific; standing interpretation for future refactors). | High | all | +| NFR-12 | No `unwrap()`/`expect()` on request paths outside the documented keep-with-reason invariants in the design-note §6 ledger (covers sb-api, sb-core, sb-db mutex poisoning, sb-auth time/entropy invariants). | High | all | -## 4. Data +# Data -- **NFR-13 (shared data source):** all game content lives in the `skara-brae-common` repo as language-neutral JSON under `data/` — single source of truth consumed at build time by server (`include_str!` + serde) and client. Server must not duplicate or transform these definitions in code. -- **NFR-14 (persistence format):** optional SQLite via `SB_DB_PATH` (rusqlite; users, characters, shop stock with `next_character_id`). Absent the variable the server runs purely in-memory with seeded `admin`/`player` accounts. -- **NFR-15 (numeric headroom):** stats are `i64` (BIGINT) — “exponential growth, tiered display” is a product commitment; no narrowing conversions to smaller integers in new code. +| **ID** | **Requirement** | **Priority** | **Use Cases** | +| --- | --- | --- | --- | +| NFR-13 | Shared data source: all game content lives in the `skara-brae-common` repo as language-neutral JSON under `data/` — single source of truth consumed at build time by server (`include_str!` + serde) and client. Server must not duplicate or transform these definitions in code. | High | UC-04 | +| NFR-14 | Persistence format: optional SQLite via `SB_DB_PATH` (rusqlite; users, characters, shop stock with `next_character_id`). Absent the variable the server runs purely in-memory with seeded `admin`/`player` accounts. | Medium | UC-01, UC-03, UC-07 | +| NFR-15 | Numeric headroom: stats are `i64` (BIGINT) — "exponential growth, tiered display" is a product commitment; no narrowing conversions to smaller integers in new code. | High | UC-05, UC-08, UC-12 | -## 5. Security +# Security -- **NFR-16:** passwords hashed with argon2; verification constant-time via the crate. -- **NFR-17:** JWT (HS256, `jsonwebtoken`), 24 h expiry; all game routes behind auth middleware; cross-user access requires the `admin` role (403 otherwise). -- **NFR-18:** no secrets in code; JWT secret and admin/player seed passwords come from environment/configuration. +| **ID** | **Requirement** | **Priority** | **Use Cases** | +| --- | --- | --- | --- | +| NFR-16 | Passwords hashed with argon2; verification constant-time via the crate. | High | UC-01, UC-02 | +| NFR-17 | JWT (HS256, `jsonwebtoken`), 24 h expiry; all game routes behind auth middleware; cross-user access requires the `admin` role (403 otherwise). | High | UC-01..UC-12 | +| NFR-18 | No secrets in code; JWT secret and admin/player seed passwords come from environment/configuration. | High | UC-01, UC-02 | -## 6. Constraints +# Interfaces -- **CON-01:** Rust, edition **2024**; workspace crates `sb-api` / `sb-core` / `sb-db` / `sb-auth` / `sb-shared` + external sibling `sb-common` (absolute path dependency; submodule migration planned). -- **CON-02:** HTTP framework **Axum 0.8** (ws + macros features), tokio, tower stack (CORS/trace/compression-gzip). -- **CON-03:** `rand 0.9`; domain functions take `rng: &mut impl Rng`; handlers obtain RNG only from `request_rng()` (WI-1 rule). -- **CON-04:** Error handling: `thiserror 2` in-domain; **no new error/framework dependencies**; one domain error enum (`sb_core::error::DomainError`) with a single central HTTP status mapping in sb-api. -- **CON-05:** Clean Architecture dependency rule: sb-core has zero knowledge of HTTP/axum/serde_json/tokio/tracing/sb-db; ports (domain traits) would live in sb-core, adapters outward. -- **CON-06:** OpenUP work products are versioned in the **skara-brae-common repo** under `docs/openup/` — one canonical project-docs home (decision reversed 2026-10-03 from the earlier skara-brae-server `docs/openup/` plan). The drafted WI-2 artifacts move there verbatim (coder, session 2); WI-1 artifacts at the old project-top folder migrate there as well. `roadmap.md` stays at the project-top folder per the earlier PM decision. +| **ID** | **Requirement** | **Priority** | **Use Cases** | +| --- | --- | --- | --- | +| NFR-19 | REST JSON API; field names and order are a wire contract — refactors must preserve them byte-for-byte. The current API emits JSON object keys in lexicographic order (serde_json resolves without `preserve_order`; `json!` uses BTreeMap-backed maps), and the typed-response structs of WI-2 declare fields in that order to stay byte-identical. | High | UC-01..UC-12 | +| NFR-20 | Meaningful HTTP status codes: 400 malformed input, 401 auth failure, 403 foreign/non-permitted resource, 404 unknown resource, 409 name conflict, 410 shop stock exhausted, 422 game-rule violation (resources/requirements), 500 internal. | High | UC-01..UC-12 | -## 7. Usability / API +# Design Constraints -- **NFR-19:** REST JSON API; field names and order are a wire contract — refactors must preserve them byte-for-byte. The current API emits JSON object keys in **lexicographic order** (serde_json resolves without `preserve_order`; `json!` uses BTreeMap-backed maps), and the typed-response structs of WI-2 declare fields in that order to stay byte-identical. -- **NFR-20:** meaningful HTTP status codes: 400 malformed input, 401 auth failure, 403 foreign/non-permitted resource, 404 unknown resource, 409 name conflict, 410 shop stock exhausted, 422 game-rule violation (resources/requirements), 500 internal. +| **ID** | **Requirement** | **Priority** | **Use Cases** | +| --- | --- | --- | --- | +| CON-01 | Rust, edition **2024**; workspace crates `sb-api` / `sb-core` / `sb-db` / `sb-auth` / `sb-shared` + external sibling `sb-common` (absolute path dependency; submodule migration planned). | High | all | +| CON-02 | HTTP framework **Axum 0.8** (ws + macros features), tokio, tower stack (CORS/trace/compression-gzip). | High | all | +| CON-03 | `rand 0.9`; domain functions take `rng: &mut impl Rng`; handlers obtain RNG only from `request_rng()` (WI-1 rule). | High | UC-03, UC-07, UC-08 | +| CON-04 | Error handling: `thiserror 2` in-domain; **no new error/framework dependencies**; one domain error enum (`sb_core::error::DomainError`) with a single central HTTP status mapping in sb-api. | High | UC-05, UC-06, UC-08, UC-12 | +| CON-05 | Clean Architecture dependency rule: sb-core has zero knowledge of HTTP/axum/serde_json/tokio/tracing/sb-db; ports (domain traits) would live in sb-core, adapters outward. | High | all | +| CON-06 | OpenUP work products are versioned in the **skara-brae-common repo** under `docs/openup/` — one canonical project-docs home (decision reversed 2026-10-03 from the earlier skara-brae-server `docs/openup/` plan). `roadmap.md` stays at the project-top folder per the earlier PM decision. | Medium | — | + +# Documentation + +| **ID** | **Requirement** | **Priority** | **Use Cases** | +| --- | --- | --- | --- | +| DOC-01 | The OpenUP artifact set (Vision, Use-Case Specifications, Supplementary Specifications, Architecture Notebook, test artifacts) and the game-design canon (`docs/game/`) live in the skara-brae-common repo and are maintained there; the EA model and its exports render them. Joplin keeps project status only. | Medium | — | + +# Use Case Coverage Matrix + +| **ID** | **Use Case** | **Traced Requirements** | +| --- | --- | --- | +| UC-01 | Register Account | NFR-01, NFR-06, NFR-16, NFR-17, NFR-18, NFR-19, NFR-20 | +| UC-02 | Authenticate | NFR-16, NFR-17, NFR-18, NFR-19, NFR-20 | +| UC-03 | Manage Characters | NFR-01, NFR-02, NFR-03, NFR-05, NFR-06, NFR-14, NFR-17, NFR-19, NFR-20 | +| UC-04 | Browse Game Catalogue | NFR-04, NFR-13, NFR-19, NFR-20 | +| UC-05 | Train Stat | NFR-02, NFR-03, NFR-06, NFR-11, NFR-12, NFR-15, CON-04 | +| UC-06 | Manage Equipment | NFR-06, NFR-11, NFR-12, CON-04 | +| UC-07 | Trade at Shop | NFR-05, NFR-06, NFR-14, CON-03 | +| UC-08 | Run Adventure | NFR-02, NFR-03, NFR-05, NFR-07, NFR-11, NFR-15, CON-03, CON-04 | +| UC-09 | Heal at Temple | NFR-06, NFR-11, NFR-12 | +| UC-10 | Practice Bard Song | NFR-06, NFR-11, NFR-12 | +| UC-11 | Study Magic | NFR-06, NFR-11, NFR-12, NFR-15 | +| UC-12 | Level Up at Review Board | NFR-06, NFR-11, NFR-12, NFR-15, CON-04 | diff --git a/docs/openup/requirements/use-case-model.md b/docs/openup/requirements/use-case-model.md index 267adc1..bbee09b 100644 --- a/docs/openup/requirements/use-case-model.md +++ b/docs/openup/requirements/use-case-model.md @@ -1,23 +1,20 @@ -# Skara Brae — Use Case Model +# Skara Brae — Use-Case Specifications -Version 1.0 (WI-2, 2026-10-03) · OpenUP artifact: `requirements/use-case-model.md` -Scope: the **existing implemented system** (server MVP as of `wi-2-architecture-cleanup` @ 899fde8). Every flow below is derived from actual handler code (`crates/sb-api/src/routes/mod.rs`) — status codes cited are the implemented behavior and are normative. +`Skara Brae` — OpenUP Requirements · version 2.0 (2026-10-05, restructured to the OpenUP +use-case-specification template; scenario content unchanged from version 1.0). +Scope: the **existing implemented system** (server MVP as of `main`, formerly +`wi-2-architecture-cleanup` @ 899fde8). Every flow below is derived from actual handler +code (`crates/sb-api/src/routes/mod.rs`) — status codes cited are the implemented +behavior and are normative. The EA model (`docs/openup/model/skara-brae.eapx`) carries +the same scenarios with per-step actors; markdown and model must agree (drift rule). -## Actors +# Use Case Overview -| Actor | Description | -|---|---| -| **Player** | Authenticated user owning one or more characters (JWT role `player`). | -| **Administrator** | Player with elevated rights (`role = admin`); may view any user's and any character's resources read-only. | -| **Guest** | Unauthenticated caller; only register/login are open. | - -## Use Case Overview - -| ID | Use Case | Primary Actor | Includes / Extends | +| ID | Use Case | Actors | Includes / Extends | |---|---|---|---| | UC-01 | Register Account | Guest | — | | UC-02 | Authenticate | Guest | — | -| UC-03 | Manage Characters | Player (Admin read) | — | +| UC-03 | Manage Characters | Player, Administrator | — | | UC-04 | Browse Game Catalogue | Player | — | | UC-05 | Train Stat | Player | includes Regenerate Resources | | UC-06 | Manage Equipment | Player | — | @@ -30,88 +27,372 @@ Scope: the **existing implemented system** (server MVP as of `wi-2-architecture- ![Skara Brae Use Case Overview](use-case-overview.png) -*Figure 1: Use case overview (rendered from the EA model, `docs/openup/model/skara-brae.eapx`, via `scripts/openup/export-diagram-images.ps1`).* +*Figure 1: Use case overview (rendered from the EA model via `scripts/openup/export-diagram-images.ps1`).* -Shared sub-flow **Regenerate Resources**: the system recomputes time-based regeneration **at specific call sites only** — list characters, view character, run adventure, and train stat (routes/mod.rs:264–266, 331, 442, 642). Energy +1 per 180 s, nerve +1 per 300 s, HP +5% of max per 300 s (only if alive and injured), capped at maxima; `last_regen` advances to now. It is **not** applied by equip/unequip, gym view, shop, heal, songs, spells, or level-up. +Shared sub-flow **Regenerate Resources**: the system recomputes time-based regeneration +**at specific call sites only** — list characters, view character, run adventure, and +train stat (routes/mod.rs:264–266, 331, 442, 642). Energy +1 per 180 s, nerve +1 per +300 s, HP +5% of max per 300 s (only if alive and injured), capped at maxima; +`last_regen` advances to now. It is **not** applied by equip/unequip, gym view, shop, +heal, songs, spells, or level-up. --- -## UC-01 Register Account +# UC-01 Register Account + +## Brief Description + +Open registration: a guest submits name and password and receives a JWT with the `player` +role. No precondition. + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Guest | Primary | Unauthenticated caller; only register/login are open. | + +## Preconditions and Context + +Precondition: none. + +## Basic Flow of Events -**Precondition:** none. **Main success scenario:** 1. Guest submits name (3–32 chars) and password (≥ 6 chars). 2. System verifies the name is unused → 409 CONFLICT if taken. 3. System hashes the password (argon2), assigns the `player` role and the next id, persists the user. 4. System issues a JWT (HS256, `player` role) and returns `{token, user_id, name, role}`. -**Alternate flows:** name length invalid → 400; password too short → 400; name taken → 409; hashing/token failure → 500. +## Alternative and Exception Flows -## UC-02 Authenticate +### Invalid input (Alternate) + +1. System rejects a name outside 3–32 chars or a password under 6 chars with 400. + +### Hashing or token failure (Exception) + +1. System fails during password hashing or JWT creation → 500; no user is persisted. + +## Post-conditions + +- User persisted with `player` role; JWT issued and returned. + +## Special Requirements + +- Passwords hashed with argon2, verification constant-time (NFR-16). +- JWT HS256, 24 h expiry (NFR-17); no secrets in code (NFR-18). + +# UC-02 Authenticate + +## Brief Description + +Login with name and password; issues a JWT carrying the user's real role. + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Guest | Primary | Unauthenticated caller; only register/login are open. | + +## Preconditions and Context + +Precondition: none. + +## Basic Flow of Events -**Main success scenario:** 1. Guest submits name and password. 2. System finds the user (401 if unknown) and verifies the password hash (401 on mismatch). 3. System issues a JWT carrying the user's real role (`admin`/`player`) and returns `{token, user_id, name, role}`. -**Alternate flows:** unknown name or wrong password → 401 UNAUTHORIZED; token creation failure → 500. +## Alternative and Exception Flows -## UC-03 Manage Characters +### Credentials rejected (Alternate) -**Main success scenario (create):** -1. Player submits character name, race, class for their user id (Admin may act for any user). +1. Unknown name or wrong password → 401 UNAUTHORIZED. + +### Token creation failure (Exception) + +1. JWT creation fails → 500. + +## Post-conditions + +- JWT issued with the user's real role; no persistent state change. + +## Special Requirements + +- The middleware guards all UC-03…12 routes with this token (NFR-17). + +# UC-03 Manage Characters + +## Brief Description + +Create, list and view characters. Admin may act for any user (read-only view). + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | +| Administrator | Supporting | Player with elevated rights (`role = admin`); may view any user's and any character's resources read-only. | + +## Preconditions and Context + +Precondition (create): target user exists. Admin may act for any user. + +## Basic Flow of Events (create) + +1. Player submits character name, race, class for their user id. 2. System validates the target user exists (404) and the trimmed name is 1–32 chars (400). 3. System rolls stats (d6+5 base 6–16 per stat ± race modifiers), computes max HP/energy/nerve from class, grants starting gold (50) and level-1 class spells to casters. 4. System assigns the next character id, stores and persists the character; returns it fully. -**Scenario (list/view):** Player lists their characters (Admin: any user's); the system applies Regenerate Resources to each returned character and returns them. Viewing one character applies regen then returns it. +## Alternative and Exception Flows -**Alternate flows:** foreign user/character without admin role → 403 FORBIDDEN; user/character not found → 404; invalid name → 400. +### List / view (Alternate) -## UC-04 Browse Game Catalogue +1. Player lists their characters (Admin: any user's); the system applies Regenerate Resources to each returned character and returns them. +2. Player views one character; the system applies regen then returns it. + +### Access denied (Alternate) + +1. Foreign user/character without admin role → 403 FORBIDDEN. + +### Not found (Alternate) + +1. User or character not found → 404. + +### Invalid name (Alternate) + +1. Trimmed name outside 1–32 chars → 400. + +## Post-conditions + +- (create) Character persisted with rolled stats, class resources and starting gold. +- (view/list) Regeneration applied to returned characters. + +## Special Requirements + +- Stat rolls use the injected rng — deterministic under seed (NFR-05, WI-1). + +# UC-04 Browse Game Catalogue + +## Brief Description + +Races, classes, items (type/quality/slot filters), enemies, adventure zones from static +game data. + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | + +## Preconditions and Context + +Precondition: authenticated. + +## Basic Flow of Events -**Main success scenario:** 1. Player requests the catalogue: races, classes, items (optional type/quality/slot filters), enemies, or adventure zones. 2. System returns static game data loaded from `skara-brae-common` JSON (class info is enriched with `is_caster`). -**Alternate flows:** single item by unknown id → 404; filters may return an empty list (not an error). +## Alternative and Exception Flows -## UC-05 Train Stat +### Unknown item (Alternate) + +1. Single item by unknown id → 404. + +### Empty filter result (Alternate) + +1. Filters may return an empty list (not an error). + +## Post-conditions + +- None (read-only). + +## Special Requirements + +- Catalogue is compiled-in and parsed once behind `LazyLock`; lock-free reads (NFR-04). +- Content source is `skara-brae-common` JSON, never duplicated in code (NFR-13). + +# UC-05 Train Stat + +## Brief Description + +Train a stat at a training ground; costs 1 energy + gold. Includes Regenerate Resources. + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | + +## Preconditions and Context + +Precondition: character exists and caller may act on it. + +## Basic Flow of Events -**Precondition:** character exists and caller may act on it. **Main success scenario:** 1. Player selects a training ground (strength / dexterity / constitution / intelligence / luck) and submits the stat name. 2. System applies Regenerate Resources. 3. System checks energy ≥ 1 and gold ≥ training cost (cost grows ~1.3× per 10 stat points). 4. System deducts 1 energy and the gold cost, increments the stat; constitution training recomputes max HP (current HP clamped). 5. System persists and returns `{success, message, character}`. -**Alternate flows:** foreign character → 403; character not found → 404; not enough energy → 422; not enough gold → 422; unknown stat name → 400. +## Alternative and Exception Flows -## UC-06 Manage Equipment +### Foreign character (Alternate) + +1. Character owned by another user without admin role → 403. + +### Not found (Alternate) + +1. Character not found → 404. + +### Not enough energy (Alternate) + +1. Energy < 1 → 422. + +### Not enough gold (Alternate) + +1. Gold below training cost → 422. + +### Unknown stat (Alternate) + +1. Unknown stat name → 400. + +## Post-conditions + +- Stat incremented; energy −1 and gold −cost deducted; max HP recomputed on constitution training; character persisted. + +## Special Requirements + +- Rule failures flow through `DomainError` → central status mapping (CON-04). + +# UC-06 Manage Equipment + +## Brief Description + +Equip from inventory / unequip back to inventory. + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | + +## Preconditions and Context + +Precondition: character exists and caller may act on it. + +## Basic Flow of Events (equip) -**Main success scenario (equip):** 1. Player submits an item id from their inventory. 2. System moves the item from inventory to its slot; a previously equipped item in that slot returns to inventory. 3. System persists and returns the updated character. -**Scenario (unequip):** Player submits a slot name; the system moves the equipped item back to inventory. +## Alternative and Exception Flows -**Alternate flows:** foreign character → 403; character/item not found → 404; item not in inventory → 400; nothing equipped in slot → 400. +### Unequip (Alternate) -## UC-07 Trade at Shop +1. Player submits a slot name; the system moves the equipped item back to inventory. + +### Foreign character (Alternate) + +1. Foreign character without admin role → 403. + +### Not found (Alternate) + +1. Character or item not found → 404. + +### Item not in inventory (Alternate) + +1. Equip target not in inventory → 400. + +### Empty slot (Alternate) + +1. Unequip on a slot with nothing equipped → 400. + +## Post-conditions + +- Item moved between inventory and equipment slot; character persisted. + +## Special Requirements + +- Rule failures flow through `DomainError` → central status mapping (CON-04). + +# UC-07 Trade at Shop + +## Brief Description + +Buy from (always-stocked + rotating) shop stock or sell inventory items. + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | + +## Preconditions and Context + +Precondition: character exists and caller may act on it. + +## Basic Flow of Events (buy) -**Main success scenario (buy):** 1. Player views the shop: always-stocked items plus rotating stock (with quantities) alongside their inventory. 2. Player submits `buy` + item id. 3. System checks gold ≥ price (422), and for non-always-in-stock items decrements the limited stock (410 GONE if 0). 4. System deducts gold, adds the item to inventory, persists character and stock; returns `{success, message, gold}`. -**Scenario (sell):** Player submits `sell` + item id; system removes the first matching inventory item, pays half the item value, increments shop stock for non-permanent items. +## Alternative and Exception Flows -**Alternate flows:** unknown action or item not owned (sell) → 400; item not found → 404; insufficient gold → 422; stock exhausted → 410. +### Sell (Alternate) -## UC-08 Run Adventure +1. Player submits `sell` + item id; system removes the first matching inventory item, pays half the item value, increments shop stock for non-permanent items. + +### Bad request (Alternate) + +1. Unknown action, or sell of an item not owned → 400. + +### Not found (Alternate) + +1. Item not found → 404. + +### Insufficient gold (Alternate) + +1. Gold below price → 422. + +### Stock exhausted (Alternate) + +1. Limited stock at 0 → 410. + +## Post-conditions + +- Buy: gold deducted, item in inventory, stock decremented; Sell: item removed, half value paid, stock incremented; persisted. + +## Special Requirements + +- Shop business logic is handler-side recorded debt (Architecture Notebook D1, Q3). + +# UC-08 Run Adventure + +## Brief Description + +Zone adventure with weighted enemy pick and turn-based combat. Includes Regenerate +Resources, Resolve Combat. Exception: encounter references an unknown enemy → 500 (post +WI-2; previously a panic). WI-2 design driver (`sb_core::adventure` extraction). + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | + +## Preconditions and Context + +Precondition: character exists and caller may act on it. + +## Basic Flow of Events -**Precondition:** character exists and caller may act on it. **Main success scenario:** 1. Player requests an adventure for a zone (default `skara_brae_streets`). 2. System validates the zone (404) and applies Regenerate Resources. 3. System refuses incapacitated characters (HP ≤ 0 → 400) and characters with nerve below the zone cost (422); otherwise deducts the nerve cost. @@ -122,53 +403,232 @@ Shared sub-flow **Regenerate Resources**: the system recomputes time-based regen 8. Spell-point upkeep is charged whenever a spell was active, win or lose. 9. System persists the character and returns `{zone, enemy, combat_log, loot}` (loot = item display names). -**Alternate flows:** foreign character → 403; character/zone not found → 404; incapacitated → 400; insufficient nerve → 422. **Exception:** encounter references an unknown enemy (data error) → 500 (post WI-2; previously a panic). +## Alternative and Exception Flows -## UC-09 Heal at Temple +### Foreign character (Alternate) + +1. Foreign character without admin role → 403. + +### Not found (Alternate) + +1. Character or zone not found → 404. + +### Incapacitated (Alternate) + +1. HP ≤ 0 → 400. + +### Insufficient nerve (Alternate) + +1. Nerve below zone cost → 422. + +### Unknown encounter enemy (Exception) + +1. Encounter references an enemy id missing from the catalogue (data error) → 500 (post WI-2; previously a panic). + +## Post-conditions + +- Victory: rewards granted, loot added, HP set to last defender HP; defeat: HP 0, no rewards; spell upkeep charged if a spell was active; character persisted. + +## Special Requirements + +- Enemy draw and combat rolls use the injected rng (NFR-05, CON-03). +- Referential integrity violations surface as `DomainError::UnknownEncounterEnemy` → 500 (NFR-07). +- Crit damage caps by level gap; open-ended d100 (combat canon, `docs/game/`). + +# UC-09 Heal at Temple + +## Brief Description + +Full or partial healing, cost = amount healed, minimum 1 gold. + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | + +## Preconditions and Context + +Precondition: character exists, is injured, and caller may act on it. + +## Basic Flow of Events -**Main success scenario:** 1. Player requests healing, optionally `partial`. 2. System computes missing HP; full heals all, partial heals half (rounded up); cost = amount healed, minimum 1 gold. 3. System checks gold (422), deducts it, restores HP, persists; returns `{hp, max_hp, gold, healed: true}`. -**Alternate flows:** foreign character → 403; not found → 404; already at full HP → 400; insufficient gold → 422. +## Alternative and Exception Flows -## UC-10 Practice Bard Song +### Foreign character (Alternate) + +1. Foreign character without admin role → 403. + +### Not found (Alternate) + +1. Character not found → 404. + +### Already at full HP (Alternate) + +1. No missing HP → 400. + +### Insufficient gold (Alternate) + +1. Gold below heal cost → 422. + +## Post-conditions + +- HP restored, gold deducted, character persisted. + +## Special Requirements + +- Healing logic is handler-side recorded debt (Architecture Notebook D1, Q3). + +# UC-10 Practice Bard Song + +## Brief Description + +Set or clear the active bard song (Bard only, level-gated). + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | + +## Preconditions and Context + +Precondition: character class is Bard. + +## Basic Flow of Events -**Precondition:** character class is Bard. **Main success scenario:** 1. Player views available songs (level-gated, with combat effect descriptions) and the active song. 2. Player submits a song id; system validates level requirement (422) and sets it active; returns `{success, message, active_song}`. -**Scenario (clear):** submitting no song id clears the active song. **Alternate flows:** non-Bard → 403; unknown song → 404; level too low → 422; foreign character → 403; not found → 404. +## Alternative and Exception Flows -## UC-11 Study Magic +### Clear (Alternate) -**Precondition:** character belongs to a caster class (Mage→magician, Wizard, Conjurer, Sorcerer). Spell level = (character level / 3) + 1 — **uncapped by code** (level 100 → spell level 34); learn *requests* are validated to 1–7, and the practical ceiling is emergent from the data (spells are defined only through level 7, so higher computed levels surface no new spells). +1. Submitting no song id clears the active song. + +### Non-Bard (Alternate) + +1. Character class is not Bard → 403. + +### Unknown song (Alternate) + +1. Unknown song id → 404. + +### Level too low (Alternate) + +1. Character level below the song requirement → 422. + +### Foreign character / not found (Alternate) + +1. Foreign character → 403; character not found → 404. + +## Post-conditions + +- Active song set or cleared; character persisted. + +## Special Requirements + +- Song effects apply in UC-08 combat (step 5). + +# UC-11 Study Magic + +## Brief Description + +View spellbook, learn spell levels (1–7, gold cost), activate/clear the active spell. +Caster classes only; spell level = (character level / 3) + 1, uncapped by code. + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | + +## Preconditions and Context + +Precondition: character belongs to a caster class (Mage→magician, Wizard, Conjurer, +Sorcerer). Spell level = (character level / 3) + 1 — uncapped by code (level 100 → spell +level 34); learn requests are validated to 1–7. + +## Basic Flow of Events (view spellbook) -**Scenario (view spellbook):** 1. Player requests their spellbook: caster class, current spell level, spell points, spells up to the achievable level (each marked known), and the learn-cost table for levels 2–7. -**Scenario (learn spell level):** +## Alternative and Exception Flows + +### Learn spell level (Alternate) + 1. Player submits a spell level (1–7). 2. System validates caster class (403), level range (400), level not above the character's current spell level (422), gold ≥ learn cost (422), and that new spells exist (400). 3. System deducts gold, adds all unknown spell ids of that level, recomputes and refills max spell points; returns `{learned_count, spell_points, max_spell_points, gold}`. -**Scenario (activate/clear active spell):** +### Activate / clear active spell (Alternate) + 1. Player submits a spell id (or empty to clear). 2. System validates the spell is known (403), exists (404), and spell points suffice (422); deducts the spell's points and sets it active; returns `{active_spell, spell_points}`. -## UC-12 Level Up at Review Board +## Post-conditions + +- (learn) spells of the level known, gold deducted, max spell points recomputed and refilled; (activate) active spell set, spell points deducted; persisted. + +## Special Requirements + +- Active spell deals damage/heals per round and charges upkeep in UC-08 combat (steps 5, 8). +- Parts of spell logic are handler-side recorded debt (Architecture Notebook D1, Q3). + +# UC-12 Level Up at Review Board + +## Brief Description + +Manual level-up: XP threshold + gold cost (500 per next level), full resource restore on +level-up. + +## Actor Brief Descriptions + +| **Actor** | **Type** | **Description** | +| --- | --- | --- | +| Player | Primary | Authenticated user owning one or more characters (JWT role `player`). | + +## Preconditions and Context + +Precondition: character exists and caller may act on it. + +## Basic Flow of Events -**Main success scenario:** 1. Player requests a level-up. 2. System validates: below max level 100, XP ≥ 18·100·(level)² for the next level (i.e. `xp_for_level(next)`), gold ≥ 500·next level. 3. System deducts the gold cost, increments the level, recomputes and fully restores HP/energy/nerve and max spell points; persists; returns `{success, message, character}`. -**Alternate flows (all 422):** already at max level; not enough XP; not enough gold. Foreign character → 403; not found → 404. +## Alternative and Exception Flows + +### Level-up rejected (Alternate) + +1. Already at max level, not enough XP, or not enough gold → 422. + +### Foreign character (Alternate) + +1. Foreign character without admin role → 403. + +### Not found (Alternate) + +1. Character not found → 404. + +## Post-conditions + +- Level +1, gold deducted, all resources fully restored and maxima recomputed; character persisted. + +## Special Requirements + +- Level-up rule failures flow through `DomainError` → central status mapping (CON-04). --- -## Traceability Notes +# Traceability Notes - UC-08 is the design driver for WI-2's service extraction (`sb_core::adventure`); its status mapping feeds `DomainError` (see `analysis-design/design-wi-2-architecture-cleanup.md` §4.1–4.2). - Catalogue data backing UC-04 lives in `skara-brae-common/data/` (items, enemies, zones, spells, crit tables). +- Analysis realizations for all twelve use cases: `analysis-design/uc-realization-mapping.md` §2 + EA model; design sequences: `analysis-design/sequences/`. +- Requirement traceability: `supplementary-specifications.md` coverage matrix. diff --git a/docs/openup/requirements/vision.md b/docs/openup/requirements/vision.md index 080cc11..9f9628f 100644 --- a/docs/openup/requirements/vision.md +++ b/docs/openup/requirements/vision.md @@ -1,41 +1,84 @@ # Skara Brae — Vision -Version 1.0 (WI-2, 2026-10-03) · OpenUP artifact: `requirements/vision.md` · Canonical home: **skara-brae-common repo**, `docs/openup/` (decision reversed 2026-10-03; drafts move verbatim from skara-brae-server/docs/) +`Skara Brae` — OpenUP Requirements · Vision · version 2.0 (2026-10-05, restructured to the +OpenUP vision template; content unchanged in substance from version 1.0, WI-2). +Canonical home: **skara-brae-common repo**, `docs/openup/` (decision 2026-10-03). -## Problem / Opportunity +## 1. Introduction -Text-based browser MMORPGs have proven retention and monetization for two decades (Torn City, since 2004), but incumbents carry 2000s-era engines. There is room for a modern, API-first, type-safe implementation of the formula: deep numeric progression, player-driven economy, idle-friendly pacing, real-time feedback. +This Vision describes Skara Brae, a text-based browser MMORPG built as a modern +re-implementation of a proven formula. The document is maintained alongside an +implemented, growing system: the server MVP (accounts, characters, training, adventures, +economy, magic, progression) is live at `main` with the OpenUP artifact set generated from +it; later-phase features are committed in intent but not built. -## Product Vision +The product comprises three repositories: `skara-brae-server` (Rust/Axum backend), +`skara-brae-client` (Vue 3 SPA) and `skara-brae-common` (bilingual game-data package and +OpenUP artifact home). In the tables below, R1 denotes the delivered server MVP and R2 the +later-phase features (not yet committed to dates). -**Skara Brae** is a fantasy text-based/idle MMORPG set in an ancient, labyrinthine city of stone and sorcery (The Bard's Tale I inspiration, dark-fantasy tone). It transplants **Torn City's proven loop** — energy-gated activities, training, economy, social systems — into that setting, adds **idle-RPG elements** (offline progression, hireable workers, pets; Idle Fantasy inspiration), and grounds combat in a **RoleMaster-inspired critical-hit system** (open-ended d100, crit tables with bleed/stun effects, level-gap damage caps). +References: game design canon in `docs/game/`, OpenUP artifact set in `docs/openup/`, +EA model in `docs/openup/model/skara-brae.eapx`. -**Tagline:** *Set your hero to work. Close the app. Come back to loot.* +## 2. Positioning -## Stakeholders +### 2.1 Problem Statement -- **Owner/developer (solo)** — builds and operates the game on self-hosted infrastructure (Forgejo/Jenkins pipeline). -- **Players** — web/PWA users; the API-first contract also serves future community tools. +| | | +| --- | --- | +| The problem of | 2000s-era engines under proven text-based browser MMORPGs | +| affects | players of the genre (retention-heavy games with dated UX and closed stacks) and a solo developer wanting a type-safe, API-first base for community tooling | +| the impact of which is | incumbents carry legacy engines; no modern reference implementation exists for the formula with real-time feedback, idle-friendly pacing and an open API | +| a successful solution would be | a 2026-era, type-safe full-stack implementation: instant feedback, mobile-first PWA, API-first design, offline-friendly idle progression, self-hostable by one operator | -## Key Features (committed MVP scope, Phase 1) +### 2.2 Product Position Statement -1. Accounts (register/login, JWT) and character creation (5 races, 10 classes). -2. Stat training with escalating gold costs; energy/nerve/HP resources with lazy timed regeneration. -3. Adventures (energy/nerve-gated zone runs) with turn-based combat, crit tables, XP/gold/loot. -4. Economy: shop (buy/sell, stock), equipment slots, inventory. -5. Magic (class-gated spell levels, spell points) and bard songs (combat buffs). -6. Level-up review board; temple healing. -7. REST API (Rust/Axum) + Vue 3 client; WebSocket notifications. +| For | players of deep, numeric-progression browser games and the solo operator building for them | +| --- | --- | +| Who | need a modern, maintainable instance of the proven Torn-style loop | +| Skara Brae | is a fantasy text-based/idle MMORPG set in an ancient city of stone and sorcery (The Bard's Tale I inspiration, dark-fantasy tone) | +| That | transplants the proven loop — energy-gated activities, training, economy — into that setting, adds idle-RPG elements (offline progression, hireable workers, pets) and grounds combat in a RoleMaster-inspired crit system | +| Unlike | incumbent PHP-era engines with closed APIs | +| Our product | ships a Rust/Axum REST API, a Vue 3 PWA client, language-neutral game data, and a self-hosted Forgejo/Jenkins pipeline — with the tagline: *Set your hero to work. Close the app. Come back to loot.* | -Later phases (not committed): PvP, guilds, properties, crafting, pets, blessings, offline workers, tower endgame. +## 3. Stakeholder Descriptions -## Constraints & Quality Bars +### 3.1 Stakeholder Summary -- Behavior continuity: refactors must be wire-format and game-semantics identical (WI-2 governs). -- Quality gates: `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, full test suite — zero-warning policy. -- Game data lives in the shared `skara-brae-common` repo as language-neutral JSON (single source of truth for server, client, and a future generated wiki). -- BIGINT (`i64`) stat growth; stats only ever go up. +| **Name** | **Description** | **Responsibilities** | +| --- | --- | --- | +| Owner/developer (solo) | Builds and operates the game on self-hosted infrastructure | Scope, releases, quality gate, external hosting (Forgejo/Jenkins pipeline) | +| Players | Web/PWA users; the API-first contract also serves future community tools | Play; consume the public API within its contract | -## Success Criteria (current iteration) +### 3.2 User Environment -WI-1 delivered injectable RNG (deterministic domain tests). This iteration (WI-2) delivers an architecture cleanup that makes the codebase safe to grow on: typed responses, domain-extracted adventure service, named game-balance constants, one domain error taxonomy, fmt baseline — with all 45 existing tests green and zero behavior change. Longer term: a playable MVP per the feature list above. +Players use a browser or installed PWA against the hosted Vue 3 SPA; the client polls the +REST API (assumed refresh cadence ~15 s; server behavior is poll-frequency-independent by +design). The operator runs a single Rust process per deployment (systemd), with optional +write-through SQLite persistence, behind Caddy TLS; game state otherwise lives in memory. +All game content ships compiled-in from `skara-brae-common` JSON — no external data +services for R1. + +## 4. Product Overview + +### 4.1 Needs and Features + +| **Need** | **Priority** | **Features** | **Planned Release** | +| --- | --- | --- | --- | +| Progress a character | High | Accounts (register/login, JWT), character creation (5 races, 10 classes), stat training with escalating gold costs, level-up review board | R1 | +| Adventure and fight | High | Energy/nerve-gated zone adventures, turn-based combat with open-ended d100 rolls and RoleMaster crit tables, XP/gold/loot | R1 | +| Run an economy | High | Shop (buy/sell, rotating stock), equipment slots, inventory | R1 | +| Wield magic and song | High | Class-gated spell levels with spell points, bard songs with combat effects | R1 | +| Regenerate over time | High | Energy/nerve/HP resources with lazy timed regeneration; temple healing | R1 | +| Consume the game programmatically | High | REST JSON API (Rust/Axum), Vue 3 client; WebSocket notifications | R1 (WS: later R1+) | +| Play idly | Medium | Offline progression, hireable workers, pets; further endgame (tower, guilds, properties, crafting, blessings, PvP) | R2 | + +## 5. Other Product Requirements + +| **Requirement** | **Priority** | **Planned Release** | +| --- | --- | --- | +| Behavior continuity: refactors must be wire-format and game-semantics identical | High | R1 (standing) | +| Quality gates: `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, full test suite — zero-warning policy | High | R1 (standing) | +| Game data lives in the shared `skara-brae-common` repo as language-neutral JSON (single source of truth for server, client and future generated wiki) | High | R1 | +| BIGINT (`i64`) stat growth; stats only ever go up | High | R1 | +| Self-hosted pipeline: Forgejo (git), Jenkins CI, systemd + Caddy deployment | Medium | R1 | diff --git a/docs/openup/test/quality-check-procedure.md b/docs/openup/test/quality-check-procedure.md new file mode 100644 index 0000000..be2c61b --- /dev/null +++ b/docs/openup/test/quality-check-procedure.md @@ -0,0 +1,107 @@ +# Quality Check Procedure + +`Skara Brae` — OpenUP Test · migrated 2026-10-05 from the Joplin note of the same name +(markdown is the single content home; Joplin keeps status only). Post-migration update: +the three unchecked items below were resolved — `rand::rng()` direct use by WI-1 +(single construction point `request_rng()`), handler-side business logic by WI-2 +(`run_adventure` extraction) and constants by WI-2 (`sb-core::constants`). + +## Purpose + +Systematic verification that the implementation matches design notes, passes all code +checks, and follows Clean Code / Clean Architecture principles. + +## Procedure + +### Step 1: Code Checks + +```bash +# Rust build + clippy +cd ~/Development/skara-brae/skara-brae-server && cargo clippy -- -W clippy::all 2>&1 + +# Rust tests +cd ~/Development/skara-brae/skara-brae-server && cargo test 2>&1 + +# Vue typecheck +cd ~/Development/skara-brae/skara-brae-client && npx vue-tsc --noEmit 2>&1 +``` + +### Step 2: Implementation vs Design Notes + +For each design note, verify the implementation matches: + +| Note (docs/) | What to Check | +|---|---| +| `docs/game/game-concept-overview.md` | Character system (races, classes, stats), economy model, adventure structure | +| `docs/game/combat-system-stat-scaling-design.md` | BIGINT stats, open-ended d100, crit tables, crit caps by level diff | +| `docs/game/mechanics-source-mapping.md` | BT1/RM/IF/Torn mapping consistency | +| `docs/game/tech-stack-architecture.md` | Crate structure, dependency direction, deployment | +| `docs/game/skill-magic-system.md` | Spell system design vs implementation | +| `docs/game/medals-honours-system.md` | Medal system (not yet implemented) | +| `docs/game/idle-fantasy-inspirations.md` | Features adopted from IF | +| `docs/openup/analysis-design/architecture-notebook.md` | Layers, dependency rule, key abstractions | + +Check items: +- [x] Stats are i64 (BIGINT) — not i32 +- [x] 5 races match design (Human, Elf, Dwarf, Hobbit, HalfElf) +- [x] 10 classes match design (Warrior through Sorcerer, no Cleric) +- [x] Crit damage cap by level difference implemented +- [x] Open-ended d100 combat rolls +- [x] BT1 items (127), enemies (125), spells (79), songs (6) all present +- [x] Zones match BT1 dungeon structure (8 zones) +- [x] Shop economy uses BT1 gold values +- [x] Bard songs with combat integration +- [x] Spell system with SP costs, learning, combat casting +- [x] Training costs gold (not just energy) +- [x] Review Board level-up: XP threshold polynomial (18×100×(level-1)²), gold cost, max level 100 + +### Step 3: Clean Architecture Audit + +Check dependency direction: +- [x] `sb-shared` → no dependencies on other game crates (domain types only) +- [x] `sb-core` → depends only on `sb-shared` + `sb-common` (domain services, no HTTP) +- [x] `sb-api` → depends on `sb-core`, `sb-shared`, `sb-auth`, `sb-db` (HTTP adapters) + +Check layer violations: +- [x] No `serde_json` in domain logic (only in LazyLock data loaders) +- [x] No `include_str!` called during business logic (only module-level const) +- [x] No `rand::rng()` in domain functions — resolved by WI-1: single construction point + `request_rng()`, domain takes injected rng +- [x] Route handlers delegate to domain services — resolved by WI-2 for adventure; + remaining handler-side logic (shop, heal, parts of spells) is recorded debt + (Architecture Notebook D1, follow-up question Q3) + +Check code quality: +- [x] No magic numbers — resolved by WI-2: `sb-core::constants` is the audit home +- [x] No abbreviations in variable names +- [x] Functions do one thing, are short +- [x] No duplicated logic across modules +- [x] No comments that restate code +- [x] Fail fast, return early — happy path flat +- [x] No mixed concerns (HTTP + business logic in same function) + +### Step 4: Bug Scan + +- [x] All gold/energy/nerve/SP costs are both displayed AND enforced +- [x] All state mutations happen inside write locks +- [x] No unwrap() on fallible operations in request paths — WI-2 §6 ledger classifies + the remaining ones as documented keep-with-reason invariants +- [x] All enum match arms exhaustive +- [x] No integer overflow paths (xp_for_level uses saturating arithmetic) + +## Audit History + +| Date | Clippy | Tests | vue-tsc | Criticals Found | Criticals Fixed | +|---|---|---|---|---|---| +| 2026-08-03 #1 | 18 warnings | 0 tests | clean | 3 | 3 | +| 2026-08-03 #2 | 0 | 42 pass | clean | 4 | 4 | +| 2026-08-03 #3 | 0 | 44 pass | clean | 1 | 1 | + +## Quality Gate + +A check passes when: +- Zero clippy warnings (excluding allowed lints) +- Zero typecheck errors +- All tests pass +- Zero critical architecture violations +- All tracked inconsistencies have a ticket or accepted rationale