Compare commits

..

No commits in common. "c92220b2a4680297e6f472547ade05ce22b6ba2d" and "93bb0347524ed642eb90e90c832153becbf37f51" have entirely different histories.

31 changed files with 125 additions and 1201 deletions

View file

@ -213,35 +213,9 @@ associations); renders in this folder, generated by `scripts/openup/export-diagr
### 9.3 Interaction View ### 9.3 Interaction View
Design sequences for all twelve use-case realizations. Sources are the version-controlled Not yet modeled: no sequence diagrams exist for the Skara Brae MVP (the EA model carries scenarios with
PlantUML files under `sequences/` (working copy per the PlantUML→EA workflow canon; the EA per-step actors, not interactions). Candidate first sequences: UC-08 adventure request→combat→loot, and
model carries scenarios with per-step actors). Render: `java -jar plantuml.jar -tpng UC-01/02 auth flows. Adding them is a future work item (PlantUML prototype → EA per the workflow canon).
-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 ### 9.4 Operational View
@ -256,11 +230,10 @@ 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 | | 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 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 | | Component Architecture | `component-architecture.png` | joint component model, dependency rule |
EA model renders are produced by `scripts/openup/export-diagram-images.ps1`; sequence All three sets are renders of the EA model (`docs/openup/model/skara-brae.eapx`) produced by
renders by PlantUML from the committed sources. Regenerate after changes (drift rule). `scripts/openup/export-diagram-images.ps1`; regenerate after model changes (read-only, layout-preserving).
## 10. References ## 10. References

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View file

@ -1,23 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 46 KiB

View file

@ -1,20 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 70 KiB

View file

@ -1,31 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 42 KiB

View file

@ -1,17 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 69 KiB

View file

@ -1,28 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 62 KiB

View file

@ -1,30 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 75 KiB

View file

@ -1,37 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 119 KiB

View file

@ -1,52 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 52 KiB

View file

@ -1,22 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 63 KiB

View file

@ -1,29 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 103 KiB

View file

@ -1,51 +0,0 @@
@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.

Before

Width:  |  Height:  |  Size: 60 KiB

View file

@ -1,26 +0,0 @@
@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

View file

@ -249,34 +249,3 @@ Read-only on the model: renders 14 diagrams to PNG for the markdown documents
image methods live on **ProjectInterface** (not Repository); `PutDiagramImageToFile(id, 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 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. 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).

View file

@ -3,7 +3,7 @@
| # | Issue | Work Item | Iteration | PSP level | Est. N (LOC) | Act. N (LOC) | Est. time (min) | Act. time (min) | Status | | # | Issue | Work Item | Iteration | PSP level | Est. N (LOC) | Act. N (LOC) | Est. time (min) | Act. time (min) | Status |
|---|-------|-----------|-----------|-----------|--------------|--------------|-----------------|-----------------|--------| |---|-------|-----------|-----------|-----------|--------------|--------------|-----------------|-----------------|--------|
| 1 | skara-brae-server#1 | Injectable RNG (plan item #8) | I1 | PSP1.0 | 35 (pred. 42, PI 37–73) | **41** | 105 (PI 75–135) | **90** | **Done** — both actuals inside 70% PI (size −2.4%, time −14%) | | 1 | skara-brae-server#1 | Injectable RNG (plan item #8) | I1 | PSP1.0 | 35 (pred. 42, PI 37–73) | **41** | 105 (PI 75–135) | **90** | **Done** — both actuals inside 70% PI (size −2.4%, time −14%) |
| 2 | skara-brae-server#2 | Architecture cleanup + OpenUP work set (plan item #7) | I1 | PSP1.0 | 660 (pred. 951, PI 607–1295) | **1833** | 620 (PI 455–785) | **201** | **Done, merged & closed 2026-10-05** — size +42% OVER UPI, time UNDER LPI (both PIs missed, opposite directions — calibration: agent-class data); 24 defects, 100% pre-compile; gates green, 71/71 tests; EA model built incl. XMI backup; EA session time deliberately unbooked | | 2 | skara-brae-server#2 | Architecture cleanup + OpenUP work set (plan item #7) | I1 | PSP1.0 | 660 (pred. 951, PI 607–1295) | **1833** | 620 (PI 455–785) | **~171** | **Done** (pending human gates) — size +42% OVER UPI, time 72% UNDER LPI; 22 defects, 100% pre-compile; gates green, 71/71 tests |
Notes: Notes:
- Estimates filled during PLN (PROBE); actuals during PM postmortem. - Estimates filled during PLN (PROBE); actuals during PM postmortem.

View file

@ -1,93 +1,51 @@
# Skara Brae — Supplementary Specifications # Skara Brae — Supplementary Specifications
`Skara Brae` — OpenUP Requirements · version 2.0 (2026-10-05, restructured to the OpenUP Version 1.0 (WI-2, 2026-10-03) · OpenUP artifact: `requirements/supplementary-specifications.md`
supplementary-specifications template: category tables with priority and use-case trace + Non-functional requirements and constraints for the server MVP. Facts cite code where they are implemented; assumptions are marked.
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.
# Performance / Scalability ## 1. Performance / Scalability
| **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.
| --- | --- | --- | --- | - **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-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-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-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-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.
| 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 |
# Reliability / Correctness ## 2. Reliability / Correctness
| **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()`.
| --- | --- | --- | --- | - **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-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-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).
| 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 |
# Quality Gates (per-commit acceptance) ## 3. Quality Gates (per-commit acceptance)
| **ID** | **Requirement** | **Priority** | **Use Cases** | - **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-08 | `cargo fmt --check` clean (fmt baseline established by WI-2). | High | all | - **NFR-10:** `cargo test --workspace` fully green (45 sb-core tests at WI-2 start; suite only grows).
| NFR-09 | `cargo clippy --all-targets -- -D warnings` clean — zero-warning policy. | High | all | - **NFR-11 (WI-2-specific):** behavior-identical refactor — JSON field names/order, HTTP status codes, game semantics, and RNG draw order unchanged.
| NFR-10 | `cargo test --workspace` fully green (45 sb-core tests at WI-2 start; suite only grows). | 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).
| 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 |
# Data ## 4. Data
| **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.
| --- | --- | --- | --- | - **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-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-15 (numeric headroom):** stats are `i64` (BIGINT) — “exponential growth, tiered display” is a product commitment; no narrowing conversions to smaller integers in new 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. | 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 |
# Security ## 5. Security
| **ID** | **Requirement** | **Priority** | **Use Cases** | - **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-16 | Passwords hashed with argon2; verification constant-time via the crate. | High | UC-01, UC-02 | - **NFR-18:** no secrets in code; JWT secret and admin/player seed passwords come from environment/configuration.
| 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 |
# Interfaces ## 6. Constraints
| **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).
| --- | --- | --- | --- | - **CON-02:** HTTP framework **Axum 0.8** (ws + macros features), tokio, tower stack (CORS/trace/compression-gzip).
| 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 | - **CON-03:** `rand 0.9`; domain functions take `rng: &mut impl Rng`; handlers obtain RNG only from `request_rng()` (WI-1 rule).
| 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 | - **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.
# Design Constraints ## 7. Usability / API
| **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.
| --- | --- | --- | --- | - **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.
| 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 |

View file

@ -1,20 +1,23 @@
# Skara Brae — Use-Case Specifications # Skara Brae — Use Case Model
`Skara Brae` — OpenUP Requirements · version 2.0 (2026-10-05, restructured to the OpenUP Version 1.0 (WI-2, 2026-10-03) · OpenUP artifact: `requirements/use-case-model.md`
use-case-specification template; scenario content unchanged from version 1.0). 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.
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).
# Use Case Overview ## Actors
| ID | Use Case | Actors | Includes / Extends | | 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 |
|---|---|---|---| |---|---|---|---|
| UC-01 | Register Account | Guest | — | | UC-01 | Register Account | Guest | — |
| UC-02 | Authenticate | Guest | — | | UC-02 | Authenticate | Guest | — |
| UC-03 | Manage Characters | Player, Administrator | — | | UC-03 | Manage Characters | Player (Admin read) | — |
| UC-04 | Browse Game Catalogue | Player | — | | UC-04 | Browse Game Catalogue | Player | — |
| UC-05 | Train Stat | Player | includes Regenerate Resources | | UC-05 | Train Stat | Player | includes Regenerate Resources |
| UC-06 | Manage Equipment | Player | — | | UC-06 | Manage Equipment | Player | — |
@ -27,372 +30,88 @@ the same scenarios with per-step actors; markdown and model must agree (drift ru
![Skara Brae Use Case Overview](use-case-overview.png) ![Skara Brae Use Case Overview](use-case-overview.png)
*Figure 1: Use case overview (rendered from the EA model via `scripts/openup/export-diagram-images.ps1`).* *Figure 1: Use case overview (rendered from the EA model, `docs/openup/model/skara-brae.eapx`, via `scripts/openup/export-diagram-images.ps1`).*
Shared sub-flow **Regenerate Resources**: the system recomputes time-based regeneration 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.
**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). 1. Guest submits name (3–32 chars) and password (≥ 6 chars).
2. System verifies the name is unused → 409 CONFLICT if taken. 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. 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}`. 4. System issues a JWT (HS256, `player` role) and returns `{token, user_id, name, role}`.
## Alternative and Exception Flows **Alternate flows:** name length invalid → 400; password too short → 400; name taken → 409; hashing/token failure → 500.
### Invalid input (Alternate) ## UC-02 Authenticate
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. 1. Guest submits name and password.
2. System finds the user (401 if unknown) and verifies the password hash (401 on mismatch). 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}`. 3. System issues a JWT carrying the user's real role (`admin`/`player`) and returns `{token, user_id, name, role}`.
## Alternative and Exception Flows **Alternate flows:** unknown name or wrong password → 401 UNAUTHORIZED; token creation failure → 500.
### Credentials rejected (Alternate) ## UC-03 Manage Characters
1. Unknown name or wrong password → 401 UNAUTHORIZED. **Main success scenario (create):**
1. Player submits character name, race, class for their user id (Admin may act for any user).
### 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). 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. 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. 4. System assigns the next character id, stores and persists the character; returns it fully.
## Alternative and Exception Flows **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.
### List / view (Alternate) **Alternate flows:** foreign user/character without admin role → 403 FORBIDDEN; user/character not found → 404; invalid name → 400.
1. Player lists their characters (Admin: any user's); the system applies Regenerate Resources to each returned character and returns them. ## UC-04 Browse Game Catalogue
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. 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`). 2. System returns static game data loaded from `skara-brae-common` JSON (class info is enriched with `is_caster`).
## Alternative and Exception Flows **Alternate flows:** single item by unknown id → 404; filters may return an empty list (not an error).
### Unknown item (Alternate) ## UC-05 Train Stat
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. 1. Player selects a training ground (strength / dexterity / constitution / intelligence / luck) and submits the stat name.
2. System applies Regenerate Resources. 2. System applies Regenerate Resources.
3. System checks energy ≥ 1 and gold ≥ training cost (cost grows ~1.3× per 10 stat points). 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). 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}`. 5. System persists and returns `{success, message, character}`.
## Alternative and Exception Flows **Alternate flows:** foreign character → 403; character not found → 404; not enough energy → 422; not enough gold → 422; unknown stat name → 400.
### Foreign character (Alternate) ## UC-06 Manage Equipment
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. 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. 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. 3. System persists and returns the updated character.
## Alternative and Exception Flows **Scenario (unequip):** Player submits a slot name; the system moves the equipped item back to inventory.
### Unequip (Alternate) **Alternate flows:** foreign character → 403; character/item not found → 404; item not in inventory → 400; nothing equipped in slot → 400.
1. Player submits a slot name; the system moves the equipped item back to inventory. ## UC-07 Trade at Shop
### 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. 1. Player views the shop: always-stocked items plus rotating stock (with quantities) alongside their inventory.
2. Player submits `buy` + item id. 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). 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}`. 4. System deducts gold, adds the item to inventory, persists character and stock; returns `{success, message, gold}`.
## Alternative and Exception Flows **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.
### Sell (Alternate) **Alternate flows:** unknown action or item not owned (sell) → 400; item not found → 404; insufficient gold → 422; stock exhausted → 410.
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. ## UC-08 Run Adventure
### 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`). 1. Player requests an adventure for a zone (default `skara_brae_streets`).
2. System validates the zone (404) and applies Regenerate Resources. 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. 3. System refuses incapacitated characters (HP ≤ 0 → 400) and characters with nerve below the zone cost (422); otherwise deducts the nerve cost.
@ -403,232 +122,53 @@ Precondition: character exists and caller may act on it.
8. Spell-point upkeep is charged whenever a spell was active, win or lose. 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). 9. System persists the character and returns `{zone, enemy, combat_log, loot}` (loot = item display names).
## Alternative and Exception Flows **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).
### Foreign character (Alternate) ## UC-09 Heal at Temple
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`. 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. 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}`. 3. System checks gold (422), deducts it, restores HP, persists; returns `{hp, max_hp, gold, healed: true}`.
## Alternative and Exception Flows **Alternate flows:** foreign character → 403; not found → 404; already at full HP → 400; insufficient gold → 422.
### Foreign character (Alternate) ## UC-10 Practice Bard Song
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. 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}`. 2. Player submits a song id; system validates level requirement (422) and sets it active; returns `{success, message, active_song}`.
## Alternative and Exception Flows **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.
### Clear (Alternate) ## UC-11 Study Magic
1. Submitting no song id clears the active song. **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).
### 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. 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.
## Alternative and Exception Flows **Scenario (learn spell level):**
### Learn spell level (Alternate)
1. Player submits a spell level (1–7). 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). 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}`. 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}`.
### Activate / clear active spell (Alternate) **Scenario (activate/clear active spell):**
1. Player submits a spell id (or empty to clear). 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}`. 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}`.
## Post-conditions ## UC-12 Level Up at Review Board
- (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. 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. 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}`. 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}`.
## Alternative and Exception Flows **Alternate flows (all 422):** already at max level; not enough XP; not enough gold. Foreign character → 403; not found → 404.
### 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). - 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). - 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.

View file

@ -1,84 +1,41 @@
# Skara Brae — Vision # Skara Brae — Vision
`Skara Brae` — OpenUP Requirements · Vision · version 2.0 (2026-10-05, restructured to the 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/)
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).
## 1. Introduction ## Problem / Opportunity
This Vision describes Skara Brae, a text-based browser MMORPG built as a modern 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.
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.
The product comprises three repositories: `skara-brae-server` (Rust/Axum backend), ## Product Vision
`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).
References: game design canon in `docs/game/`, OpenUP artifact set in `docs/openup/`, **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).
EA model in `docs/openup/model/skara-brae.eapx`.
## 2. Positioning **Tagline:** *Set your hero to work. Close the app. Come back to loot.*
### 2.1 Problem Statement ## Stakeholders
| | | - **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 |
### 2.2 Product Position Statement ## Key Features (committed MVP scope, Phase 1)
| For | players of deep, numeric-progression browser games and the solo operator building for them | 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.
| Who | need a modern, maintainable instance of the proven Torn-style loop | 3. Adventures (energy/nerve-gated zone runs) with turn-based combat, crit tables, XP/gold/loot.
| 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) | 4. Economy: shop (buy/sell, stock), equipment slots, inventory.
| 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 | 5. Magic (class-gated spell levels, spell points) and bard songs (combat buffs).
| Unlike | incumbent PHP-era engines with closed APIs | 6. Level-up review board; temple healing.
| 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.* | 7. REST API (Rust/Axum) + Vue 3 client; WebSocket notifications.
## 3. Stakeholder Descriptions Later phases (not committed): PvP, guilds, properties, crafting, pets, blessings, offline workers, tower endgame.
### 3.1 Stakeholder Summary ## Constraints & Quality Bars
| **Name** | **Description** | **Responsibilities** | - 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.
| Owner/developer (solo) | Builds and operates the game on self-hosted infrastructure | Scope, releases, quality gate, external hosting (Forgejo/Jenkins pipeline) | - 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).
| Players | Web/PWA users; the API-first contract also serves future community tools | Play; consume the public API within its contract | - BIGINT (`i64`) stat growth; stats only ever go up.
### 3.2 User Environment ## Success Criteria (current iteration)
Players use a browser or installed PWA against the hosted Vue 3 SPA; the client polls the 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.
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 |

View file

@ -1,107 +0,0 @@
# 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