openup: EA model built + enriched - baseline, associations, joint design package
All checks were successful
build / build-test (push) Successful in 16s
All checks were successful
build / build-test (push) Successful in 16s
Windows/EA session per BUILD-CHECKLIST 0-6 (the planned human-gate path, executed on the EA machine). Baseline built from EABase.eapx via build-model.ps1 and verified per checklist 4: 3 actors, 12 UCs with scenarios, 13 associations, 0 include connectors, 7 SS requirements, 12 realization packages / 36 elements; UC-08 MSS (9 steps) and UC-11 learn spell level (3 steps) match use-case-model.md verbatim. Script fixes (defects 1-3 of the session log): PS 5.1 hashtable parse error; realization UC lookup now accepts short and full use-case names. Enrichment after a manual layout pass - model changes are surgical from now on, re-running build-model.ps1 is forbidden (checklist 6): - add-analysis-associations.ps1: 24 analysis associations per mapping 2a (boundary-controller-entity; UC-04 boundary-entity) rendered via DiagramLinks without touching manual layout; 4 uses-dependencies to DomainError (UC-05/06/08/12 error paths, model-level) - add-design-package.ps1: Analysis-Design/Design - joint component model (skara-brae-client, sb-api, sb-auth, sb-db, sb-core, sb-shared, sb-common; 10 dependencies, rule inward). Joint-model decision recorded in mapping 1: one model for server+client+common; client- side UC realizations are a future work item. Markdown sources updated first (drift rule): mapping 1 tree + 2a interactions + 3 design component model; checklist 6. Exports regenerated from the model: use-case-model.json new, uc-realizations.json re-exported (useCase full names, notes merged, crossCutting added; element content verified equal to the markdown-to-json generator output). Open: XMI backup - ExportPackageXMI fails via COM (DISP_E_TYPEMISMATCH, PS 5.1 + cscript); manual EA UI export per model/README.md option 2.
This commit is contained in:
parent
201f8f251c
commit
c0226df62d
8 changed files with 1296 additions and 300 deletions
|
|
@ -201,3 +201,42 @@ UC-07:3, UC-08:5, UC-09:2, UC-10:3, UC-11:3, UC-12:3 — total 36.
|
|||
content — it was generated from the mapping markdown).
|
||||
2. Save the model; commit `.eapx` + `exports/` + `scripts/openup/` together as one commit
|
||||
`openup: EA model WI-2 baseline` in **skara-brae-common**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Enrichment (2026-10-03, after manual layout session) — SURGICAL only
|
||||
|
||||
The user laid out diagrams by hand in EA after the WI-2 baseline build. From here on the
|
||||
model may only be touched by the two surgical scripts below — **never** re-run
|
||||
`build-model.ps1` (it deletes and rebuilds the Requirements / Analysis-Design packages and
|
||||
would destroy manual layout). Sources: `uc-realization-mapping.md` §2a (interactions) and
|
||||
§3 (design component model).
|
||||
|
||||
### 6.1 Analysis associations — `scripts/openup/add-analysis-associations.ps1`
|
||||
|
||||
- Inside each `UC-NN Realization` package: Association connectors per mapping §2a
|
||||
(boundary→controller, controller→entity; UC-04 boundary→entity) — **24 associations**
|
||||
(UC-01:2, UC-02:1, UC-03:3, UC-04:1, UC-05:2, UC-06:2, UC-07:2, UC-08:4, UC-09:1,
|
||||
UC-10:2, UC-11:2, UC-12:2).
|
||||
- 4 Dependencies `«uses»` from TrainingService, EquipmentService, AdventureService,
|
||||
ReviewBoard to `DomainError` (Architecture Overview) — the error paths of UC-05/06/08/12.
|
||||
- DiagramLinks added to each package's existing class diagram so the connectors render
|
||||
WITHOUT touching any DiagramObject (manual layout preserved). Idempotent: existing
|
||||
connectors are skipped.
|
||||
|
||||
### 6.2 Design package — `scripts/openup/add-design-package.ps1`
|
||||
|
||||
- Creates `Analysis-Design / Design` (rebuilds only this package — agent-owned, contains
|
||||
no manual edits): 7 components (skara-brae-client, sb-api, sb-auth, sb-db, sb-core,
|
||||
sb-shared, sb-common) with traceability notes, 10 Dependency connectors, one Component
|
||||
diagram `Component Architecture` (layered layout, dependency rule inward).
|
||||
|
||||
### 6.3 Verification
|
||||
|
||||
1. 24 associations across realization packages (per-UC counts above); 4 `«uses»`
|
||||
dependencies to DomainError; still 0 `«include»` connectors.
|
||||
2. `Design` package: 7 components, 10 dependencies, 1 diagram.
|
||||
3. Baseline intact: 3 actors, 12 use cases, 12 realization packages, 13 actor-UC
|
||||
associations; Requirements tree untouched.
|
||||
4. Manual layout preserved: the scripts add no/modify no DiagramObjects in existing
|
||||
diagrams (only the new `Design` diagram carries its own object layout).
|
||||
|
|
|
|||
|
|
@ -1,295 +1,271 @@
|
|||
{
|
||||
"exportedFrom": "docs/openup/analysis-design/uc-realization-mapping.md §2, generated by scripts/openup/markdown-to-json.py — generated file, never hand-edit",
|
||||
"realizations": [
|
||||
{
|
||||
"id": "UC-01 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "AccountRegistry",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-api::routes::{register, seed_users}`",
|
||||
"notes": "argon2 hash via `sb-auth::hash`; JWT via `sb-auth::jwt`"
|
||||
},
|
||||
{
|
||||
"name": "UserAccount",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-shared::user::User`, `Role`",
|
||||
"notes": "persisted via `sb-db::Database`"
|
||||
},
|
||||
{
|
||||
"name": "AuthBoundary",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`POST /api/auth/register`, `AuthResponse`",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Register Account"
|
||||
},
|
||||
{
|
||||
"id": "UC-02 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "Authenticator",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-api::routes::login`, `sb-auth::{hash::verify_password, jwt::create_token}`",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "TokenService",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`POST /api/auth/login`, `AuthResponse`, `middleware::auth::auth_middleware`",
|
||||
"notes": "middleware guards all UC-03…12"
|
||||
}
|
||||
],
|
||||
"useCase": "Authenticate"
|
||||
},
|
||||
{
|
||||
"id": "UC-03 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "CharacterFactory",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-core::character::create_character`",
|
||||
"notes": "takes injected rng (WI-1)"
|
||||
},
|
||||
{
|
||||
"name": "Character",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-shared::character::Character` (+ `Race`, `Class`, `CharacterStats`)",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "CharacterCatalog",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`GET/POST /api/users/{id}/characters`, `GET /api/characters/{id}`",
|
||||
"notes": "store: `AppState.characters`"
|
||||
},
|
||||
{
|
||||
"name": "ResourceRegenerator",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-core::character::apply_regen` (+ regen constants — WI-2 names them)",
|
||||
"notes": "included sub-flow"
|
||||
}
|
||||
],
|
||||
"useCase": "Manage Characters"
|
||||
},
|
||||
{
|
||||
"id": "UC-04 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "CatalogueBrowser",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`GET /api/{races,classes,items,enemies,adventures}`; response types `sb-shared::responses`",
|
||||
"notes": "data from `sb-common` JSON via `sb-core::{character::RACES/CLASSES, data, items}`"
|
||||
},
|
||||
{
|
||||
"name": "GameData",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-core::data::{EnemyDef, ZoneDef, EncounterEntry, LootDrop}`, `items::load_all_items`",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Browse Game Catalogue"
|
||||
},
|
||||
{
|
||||
"id": "UC-05 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "TrainingService",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-core::services::train_stat`",
|
||||
"notes": "error type `DomainError` (WI-2)"
|
||||
},
|
||||
{
|
||||
"name": "TrainingGrounds",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-core::character::TRAINING_GROUNDS`, `training_cost` (+ cost constants WI-2)",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "GymBoundary",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`GET/POST /api/characters/{id}/gym`, `GymResponse`, `TrainStatResponse`",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Train Stat"
|
||||
},
|
||||
{
|
||||
"id": "UC-06 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "EquipmentService",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-core::services::{equip_item, unequip_item}`",
|
||||
"notes": "`DomainError` (WI-2)"
|
||||
},
|
||||
{
|
||||
"name": "Equipment/Inventory",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`Character.equipment` / `.inventory`, `sb-shared::item::Item`",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "EquipmentBoundary",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`POST /api/characters/{id}/{equip,unequip}`",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Manage Equipment"
|
||||
},
|
||||
{
|
||||
"id": "UC-07 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "ShopService",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-api::routes::{shop_action, buy_item, sell_item}` (handler-side; extraction candidate, design note Q3)",
|
||||
"notes": "prices: `sb-core::items::{item_price, item_sell_price}`"
|
||||
},
|
||||
{
|
||||
"name": "ShopStock",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`AppState.shop_stock`",
|
||||
"notes": "persisted via `sb-db`"
|
||||
},
|
||||
{
|
||||
"name": "ShopBoundary",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`GET/POST /api/characters/{id}/shop`, `ShopResponse`, `ShopActionResponse`",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Trade at Shop"
|
||||
},
|
||||
{
|
||||
"id": "UC-08 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "AdventureService",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-core::adventure::run_adventure` (**new, WI-2**) + `services::pick_enemy` → `Result` (WI-2)",
|
||||
"notes": "takes injected rng; `DomainError` (WI-2)"
|
||||
},
|
||||
{
|
||||
"name": "CombatEngine",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-core::combat::{resolve_combat_full, resolve_attack, crit tables, open_ended_roll}` + combat constants (WI-2)",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "Combatant",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-core::combat::CombatStats`",
|
||||
"notes": "built from Character/EnemyDef"
|
||||
},
|
||||
{
|
||||
"name": "SpellEffect / BardSong",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-core::combat::SpellEffect`, `sb-core::spells`, `sb-core::songs`",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "AdventureBoundary",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`GET /api/characters/{id}/adventure`; `AdventureRunResponse` (**new, WI-2**), `sb-shared::responses::CombatLog`",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Run Adventure"
|
||||
},
|
||||
{
|
||||
"id": "UC-09 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "HealingService",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-api::routes::heal_character` (handler-side; extraction candidate, Q3)",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "TempleBoundary",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`POST /api/characters/{id}/heal`; `HealResponse` (**new, WI-2**)",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Heal at Temple"
|
||||
},
|
||||
{
|
||||
"id": "UC-10 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "SongService",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-api::routes::set_song`; `sb-core::songs::available_songs`",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "BardSong",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-core::songs::BARD_SONGS`, `Character.active_song`",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "SongBoundary",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`GET/POST /api/characters/{id}/songs`, `SongsResponse`, `SetSongResponse`",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Practice Bard Song"
|
||||
},
|
||||
{
|
||||
"id": "UC-11 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "MagicService",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-api::routes::{get_spells, learn_spell_level}`; `sb-core::spells::{spells_for_class, spell_learn_cost, max_spell_points, caster_spell_level (WI-2)}`",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "Spell",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-core::spells::SpellDef`, `Character.{known_spells, active_spell, spell_points}`",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "MagicBoundary",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`GET/POST /api/characters/{id}/spells`; `SpellbookResponse`, `LearnSpellResponse` (**new, WI-2**)",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Study Magic"
|
||||
},
|
||||
{
|
||||
"id": "UC-12 Realization",
|
||||
"elements": [
|
||||
{
|
||||
"name": "ReviewBoard",
|
||||
"stereotype": "controller",
|
||||
"designElement": "`sb-core::character::level_up`",
|
||||
"notes": "`DomainError` (WI-2)"
|
||||
},
|
||||
{
|
||||
"name": "Progression",
|
||||
"stereotype": "entity",
|
||||
"designElement": "`sb-core::character::{xp_for_level, level_up_cost, MAX_LEVEL}` (+ curve constants WI-2)",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"name": "ReviewBoundary",
|
||||
"stereotype": "boundary",
|
||||
"designElement": "`POST /api/characters/{id}/level-up`, `LevelUpResponse`",
|
||||
"notes": ""
|
||||
}
|
||||
],
|
||||
"useCase": "Level Up at Review Board"
|
||||
}
|
||||
],
|
||||
"crossCutting": "Cross-cutting design elements (WI-2): `sb-core::error::DomainError` + `sb-api::routes::domain_status` (status mapping table, design note §4.1) realize the error paths of UC-05/06/08/12; `sb-core::constants` realizes NFR \"no magic numbers\"; `sb-api::routes::responses` realizes NFR-19 (wire-format freeze).",
|
||||
"dependencyRule": "Dependency rule check (Clean Architecture): all controllers/entities above live in `sb-core`/`sb-shared` (domain side, no HTTP imports); all boundaries live in `sb-api`; the single current violations (ShopService, HealingService, parts of MagicService in sb-api) are recorded as debt with a follow-up question — the model must not paper over them (stale docs are defects)."
|
||||
}
|
||||
{
|
||||
"exportedFrom": "docs/openup/model/skara-brae.eapx (Analysis-Design realization packages)",
|
||||
"realizations": [
|
||||
{
|
||||
"id": "UC-01 Realization",
|
||||
"useCase": "UC-01 Register Account",
|
||||
"elements": [
|
||||
{
|
||||
"name": "AccountRegistry",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-api::routes::{register, seed_users}`. argon2 hash via `sb-auth::hash`; JWT via `sb-auth::jwt`"
|
||||
},
|
||||
{
|
||||
"name": "AuthBoundary",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `POST /api/auth/register`, `AuthResponse`. "
|
||||
},
|
||||
{
|
||||
"name": "UserAccount",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-shared::user::User`, `Role`. persisted via `sb-db::Database`"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-02 Realization",
|
||||
"useCase": "UC-02 Authenticate",
|
||||
"elements": [
|
||||
{
|
||||
"name": "Authenticator",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-api::routes::login`, `sb-auth::{hash::verify_password, jwt::create_token}`. "
|
||||
},
|
||||
{
|
||||
"name": "TokenService",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `POST /api/auth/login`, `AuthResponse`, `middleware::auth::auth_middleware`. middleware guards all UC-03…12"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-03 Realization",
|
||||
"useCase": "UC-03 Manage Characters",
|
||||
"elements": [
|
||||
{
|
||||
"name": "Character",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-shared::character::Character` (+ `Race`, `Class`, `CharacterStats`). "
|
||||
},
|
||||
{
|
||||
"name": "CharacterCatalog",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `GET/POST /api/users/{id}/characters`, `GET /api/characters/{id}`. store: `AppState.characters`"
|
||||
},
|
||||
{
|
||||
"name": "CharacterFactory",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-core::character::create_character`. takes injected rng (WI-1)"
|
||||
},
|
||||
{
|
||||
"name": "ResourceRegenerator",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-core::character::apply_regen` (+ regen constants — WI-2 names them). included sub-flow"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-04 Realization",
|
||||
"useCase": "UC-04 Browse Game Catalogue",
|
||||
"elements": [
|
||||
{
|
||||
"name": "CatalogueBrowser",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `GET /api/{races,classes,items,enemies,adventures}`; response types `sb-shared::responses`. data from `sb-common` JSON via `sb-core::{character::RACES/CLASSES, data, items}`"
|
||||
},
|
||||
{
|
||||
"name": "GameData",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-core::data::{EnemyDef, ZoneDef, EncounterEntry, LootDrop}`, `items::load_all_items`. "
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-05 Realization",
|
||||
"useCase": "UC-05 Train Stat",
|
||||
"elements": [
|
||||
{
|
||||
"name": "GymBoundary",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `GET/POST /api/characters/{id}/gym`, `GymResponse`, `TrainStatResponse`. "
|
||||
},
|
||||
{
|
||||
"name": "TrainingGrounds",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-core::character::TRAINING_GROUNDS`, `training_cost` (+ cost constants WI-2). "
|
||||
},
|
||||
{
|
||||
"name": "TrainingService",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-core::services::train_stat`. error type `DomainError` (WI-2)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-06 Realization",
|
||||
"useCase": "UC-06 Manage Equipment",
|
||||
"elements": [
|
||||
{
|
||||
"name": "Equipment/Inventory",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `Character.equipment` / `.inventory`, `sb-shared::item::Item`. "
|
||||
},
|
||||
{
|
||||
"name": "EquipmentBoundary",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `POST /api/characters/{id}/{equip,unequip}`. "
|
||||
},
|
||||
{
|
||||
"name": "EquipmentService",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-core::services::{equip_item, unequip_item}`. `DomainError` (WI-2)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-07 Realization",
|
||||
"useCase": "UC-07 Trade at Shop",
|
||||
"elements": [
|
||||
{
|
||||
"name": "ShopBoundary",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `GET/POST /api/characters/{id}/shop`, `ShopResponse`, `ShopActionResponse`. "
|
||||
},
|
||||
{
|
||||
"name": "ShopService",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-api::routes::{shop_action, buy_item, sell_item}` (handler-side; extraction candidate, design note Q3). prices: `sb-core::items::{item_price, item_sell_price}`"
|
||||
},
|
||||
{
|
||||
"name": "ShopStock",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `AppState.shop_stock`. persisted via `sb-db`"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-08 Realization",
|
||||
"useCase": "UC-08 Run Adventure",
|
||||
"elements": [
|
||||
{
|
||||
"name": "AdventureBoundary",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `GET /api/characters/{id}/adventure`; `AdventureRunResponse` (**new, WI-2**), `sb-shared::responses::CombatLog`. "
|
||||
},
|
||||
{
|
||||
"name": "AdventureService",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-core::adventure::run_adventure` (**new, WI-2**) + `services::pick_enemy` → `Result` (WI-2). takes injected rng; `DomainError` (WI-2)"
|
||||
},
|
||||
{
|
||||
"name": "Combatant",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-core::combat::CombatStats`. built from Character/EnemyDef"
|
||||
},
|
||||
{
|
||||
"name": "CombatEngine",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-core::combat::{resolve_combat_full, resolve_attack, crit tables, open_ended_roll}` + combat constants (WI-2). "
|
||||
},
|
||||
{
|
||||
"name": "SpellEffect / BardSong",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-core::combat::SpellEffect`, `sb-core::spells`, `sb-core::songs`. "
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-09 Realization",
|
||||
"useCase": "UC-09 Heal at Temple",
|
||||
"elements": [
|
||||
{
|
||||
"name": "HealingService",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-api::routes::heal_character` (handler-side; extraction candidate, Q3). "
|
||||
},
|
||||
{
|
||||
"name": "TempleBoundary",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `POST /api/characters/{id}/heal`; `HealResponse` (**new, WI-2**). "
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-10 Realization",
|
||||
"useCase": "UC-10 Practice Bard Song",
|
||||
"elements": [
|
||||
{
|
||||
"name": "BardSong",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-core::songs::BARD_SONGS`, `Character.active_song`. "
|
||||
},
|
||||
{
|
||||
"name": "SongBoundary",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `GET/POST /api/characters/{id}/songs`, `SongsResponse`, `SetSongResponse`. "
|
||||
},
|
||||
{
|
||||
"name": "SongService",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-api::routes::set_song`; `sb-core::songs::available_songs`. "
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-11 Realization",
|
||||
"useCase": "UC-11 Study Magic",
|
||||
"elements": [
|
||||
{
|
||||
"name": "MagicBoundary",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `GET/POST /api/characters/{id}/spells`; `SpellbookResponse`, `LearnSpellResponse` (**new, WI-2**). "
|
||||
},
|
||||
{
|
||||
"name": "MagicService",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-api::routes::{get_spells, learn_spell_level}`; `sb-core::spells::{spells_for_class, spell_learn_cost, max_spell_points, caster_spell_level (WI-2)}`. "
|
||||
},
|
||||
{
|
||||
"name": "Spell",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-core::spells::SpellDef`, `Character.{known_spells, active_spell, spell_points}`. "
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "UC-12 Realization",
|
||||
"useCase": "UC-12 Level Up at Review Board",
|
||||
"elements": [
|
||||
{
|
||||
"name": "Progression",
|
||||
"stereotype": "entity",
|
||||
"notes": "Design element: . Design element: `sb-core::character::{xp_for_level, level_up_cost, MAX_LEVEL}` (+ curve constants WI-2). "
|
||||
},
|
||||
{
|
||||
"name": "ReviewBoard",
|
||||
"stereotype": "controller",
|
||||
"notes": "Design element: . Design element: `sb-core::character::level_up`. `DomainError` (WI-2)"
|
||||
},
|
||||
{
|
||||
"name": "ReviewBoundary",
|
||||
"stereotype": "boundary",
|
||||
"notes": "Design element: . Design element: `POST /api/characters/{id}/level-up`, `LevelUpResponse`. "
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"crossCutting": [
|
||||
{
|
||||
"name": "Constants",
|
||||
"notes": "sb-core::constants realizes NFR \"no magic numbers\"."
|
||||
},
|
||||
{
|
||||
"name": "DomainError",
|
||||
"notes": "sb-core::error::DomainError + sb-api::routes::domain_status (status mapping table, design note §4.1) realize the error paths of UC-05/06/08/12."
|
||||
},
|
||||
{
|
||||
"name": "Responses",
|
||||
"notes": "sb-api::routes::responses realizes NFR-19 (wire-format freeze)."
|
||||
}
|
||||
]
|
||||
}
|
||||
546
docs/openup/model/exports/use-case-model.json
Normal file
546
docs/openup/model/exports/use-case-model.json
Normal file
|
|
@ -0,0 +1,546 @@
|
|||
{
|
||||
"exportedFrom": "docs/openup/model/skara-brae.eapx (Requirements/Use Case Model)",
|
||||
"actors": [
|
||||
{
|
||||
"name": "Administrator",
|
||||
"notes": "Player with elevated rights (role = admin); may view any user\u0027s and any character\u0027s resources read-only."
|
||||
},
|
||||
{
|
||||
"name": "Guest",
|
||||
"notes": "Unauthenticated caller; only register/login are open."
|
||||
},
|
||||
{
|
||||
"name": "Player",
|
||||
"notes": "Authenticated user owning one or more characters (JWT role player)."
|
||||
}
|
||||
],
|
||||
"useCases": [
|
||||
{
|
||||
"name": "UC-01 Register Account",
|
||||
"notes": "Open registration. No precondition.",
|
||||
"actors": [
|
||||
"Guest"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario",
|
||||
"type": "Basic Path",
|
||||
"notes": "",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Guest submits name (3-32 chars) and password (\u003e= 6 chars)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System verifies the name is unused -\u003e 409 CONFLICT if taken."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System hashes the password (argon2), assigns the player role and the next id, persists the user."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System issues a JWT (HS256, player role) and returns {token, user_id, name, role}."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "name length invalid -\u003e 400; password too short -\u003e 400; name taken -\u003e 409; hashing/token failure -\u003e 500.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-02 Authenticate",
|
||||
"notes": "Login with name and password; issues a JWT with the user\u0027s real role.",
|
||||
"actors": [
|
||||
"Guest"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario",
|
||||
"type": "Basic Path",
|
||||
"notes": "",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Guest submits name and password."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System finds the user (401 if unknown) and verifies the password hash (401 on mismatch)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System issues a JWT carrying the user\u0027s real role (admin/player) and returns {token, user_id, name, role}."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "unknown name or wrong password -\u003e 401 UNAUTHORIZED; token creation failure -\u003e 500.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-03 Manage Characters",
|
||||
"notes": "Create, list and view characters. Admin may act for any user (read-only view).",
|
||||
"actors": [
|
||||
"Player",
|
||||
"Administrator"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario (create)",
|
||||
"type": "Basic Path",
|
||||
"notes": "Admin may act for any user.",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player submits character name, race, class for their user id."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System validates the target user exists (404) and the trimmed name is 1-32 chars (400)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "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."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System assigns the next character id, stores and persists the character; returns it fully."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "list/view",
|
||||
"type": "Alternate",
|
||||
"notes": "Player lists their characters (Admin: any user\u0027s); the system applies Regenerate Resources to each returned character and returns them. Viewing one character applies regen then returns it.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "foreign user/character without admin role -\u003e 403 FORBIDDEN; user/character not found -\u003e 404; invalid name -\u003e 400.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-04 Browse Game Catalogue",
|
||||
"notes": "Races, classes, items (type/quality/slot filters), enemies, adventure zones from static game data.",
|
||||
"actors": [
|
||||
"Player"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario",
|
||||
"type": "Basic Path",
|
||||
"notes": "",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player requests the catalogue: races, classes, items (optional type/quality/slot filters), enemies, or adventure zones."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System returns static game data loaded from skara-brae-common JSON (class info is enriched with is_caster)."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "single item by unknown id -\u003e 404; filters may return an empty list (not an error).",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-05 Train Stat",
|
||||
"notes": "Train a stat at a training ground; costs 1 energy + gold. Includes Regenerate Resources.",
|
||||
"actors": [
|
||||
"Player"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario",
|
||||
"type": "Basic Path",
|
||||
"notes": "Precondition: character exists and caller may act on it.",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player selects a training ground (strength / dexterity / constitution / intelligence / luck) and submits the stat name."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System applies Regenerate Resources."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System checks energy \u003e= 1 and gold \u003e= training cost (cost grows ~1.3x per 10 stat points)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System deducts 1 energy and the gold cost, increments the stat; constitution training recomputes max HP (current HP clamped)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System persists and returns {success, message, character}."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "foreign character -\u003e 403; character not found -\u003e 404; not enough energy -\u003e 422; not enough gold -\u003e 422; unknown stat name -\u003e 400.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-06 Manage Equipment",
|
||||
"notes": "Equip from inventory / unequip back to inventory.",
|
||||
"actors": [
|
||||
"Player"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario (equip)",
|
||||
"type": "Basic Path",
|
||||
"notes": "",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player submits an item id from their inventory."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System moves the item from inventory to its slot; a previously equipped item in that slot returns to inventory."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System persists and returns the updated character."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "unequip",
|
||||
"type": "Alternate",
|
||||
"notes": "Player submits a slot name; the system moves the equipped item back to inventory.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "foreign character -\u003e 403; character/item not found -\u003e 404; item not in inventory -\u003e 400; nothing equipped in slot -\u003e 400.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-07 Trade at Shop",
|
||||
"notes": "Buy from (always-stocked + rotating) shop stock or sell inventory items.",
|
||||
"actors": [
|
||||
"Player"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario (buy)",
|
||||
"type": "Basic Path",
|
||||
"notes": "",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player views the shop: always-stocked items plus rotating stock (with quantities) alongside their inventory."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player submits buy + item id."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System checks gold \u003e= price (422), and for non-always-in-stock items decrements the limited stock (410 GONE if 0)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System deducts gold, adds the item to inventory, persists character and stock; returns {success, message, gold}."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sell",
|
||||
"type": "Alternate",
|
||||
"notes": "Player submits sell + item id; system removes the first matching inventory item, pays half the item value, increments shop stock for non-permanent items.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "unknown action or item not owned (sell) -\u003e 400; item not found -\u003e 404; insufficient gold -\u003e 422; stock exhausted -\u003e 410.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-08 Run Adventure",
|
||||
"notes": "Zone adventure with weighted enemy pick and turn-based combat. Includes Regenerate Resources, Resolve Combat. Exception: encounter references an unknown enemy -\u003e 500 (post WI-2; previously a panic). WI-2 design driver (sb_core::adventure extraction).",
|
||||
"actors": [
|
||||
"Player"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario",
|
||||
"type": "Basic Path",
|
||||
"notes": "Precondition: character exists and caller may act on it.",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player requests an adventure for a zone (default skara_brae_streets)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System validates the zone (404) and applies Regenerate Resources."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System refuses incapacitated characters (HP \u003c= 0 -\u003e 400) and characters with nerve below the zone cost (422); otherwise deducts the nerve cost."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System draws one enemy from the zone\u0027s weighted encounter table."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System resolves turn-based combat: open-ended d100 attack rolls, OB vs DB, RoleMaster crit tables with level-gap damage caps, active bard song effects, active spell damage/heal per round."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "On player victory: XP and gold per the enemy definition, character HP set to the last round\u0027s defender HP, and loot dropped per-item chance rolls (only items existing in the catalogue)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "On defeat: character HP set to 0; no rewards."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Spell-point upkeep is charged whenever a spell was active, win or lose."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System persists the character and returns {zone, enemy, combat_log, loot} (loot = item display names)."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "foreign character -\u003e 403; character/zone not found -\u003e 404; incapacitated -\u003e 400; insufficient nerve -\u003e 422.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Exception",
|
||||
"type": "Exception",
|
||||
"notes": "encounter references an unknown enemy (data error) -\u003e 500 (post WI-2; previously a panic).",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-09 Heal at Temple",
|
||||
"notes": "Full or partial healing, cost = amount healed, minimum 1 gold.",
|
||||
"actors": [
|
||||
"Player"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario",
|
||||
"type": "Basic Path",
|
||||
"notes": "",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player requests healing, optionally partial."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System computes missing HP; full heals all, partial heals half (rounded up); cost = amount healed, minimum 1 gold."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System checks gold (422), deducts it, restores HP, persists; returns {hp, max_hp, gold, healed: true}."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "foreign character -\u003e 403; not found -\u003e 404; already at full HP -\u003e 400; insufficient gold -\u003e 422.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-10 Practice Bard Song",
|
||||
"notes": "Set or clear the active bard song (Bard only, level-gated).",
|
||||
"actors": [
|
||||
"Player"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario",
|
||||
"type": "Basic Path",
|
||||
"notes": "Precondition: character class is Bard.",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player views available songs (level-gated, with combat effect descriptions) and the active song."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player submits a song id; system validates level requirement (422) and sets it active; returns {success, message, active_song}."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "clear",
|
||||
"type": "Alternate",
|
||||
"notes": "submitting no song id clears the active song.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "non-Bard -\u003e 403; unknown song -\u003e 404; level too low -\u003e 422; foreign character -\u003e 403; not found -\u003e 404.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-11 Study Magic",
|
||||
"notes": "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.",
|
||||
"actors": [
|
||||
"Player"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario (view spellbook)",
|
||||
"type": "Basic Path",
|
||||
"notes": "Precondition: character belongs to a caster class (Mage-\u003emagician, Wizard, Conjurer, Sorcerer). Spell level = (character level / 3) + 1 - uncapped by code (level 100 -\u003e spell level 34); learn requests are validated to 1-7.",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "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."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "learn spell level",
|
||||
"type": "Alternate",
|
||||
"notes": "",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player submits a spell level (1-7)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System validates caster class (403), level range (400), level not above the character\u0027s current spell level (422), gold \u003e= learn cost (422), and that new spells exist (400)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "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}."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "activate/clear active spell",
|
||||
"type": "Alternate",
|
||||
"notes": "",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player submits a spell id (or empty to clear)."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System validates the spell is known (403), exists (404), and spell points suffice (422); deducts the spell\u0027s points and sets it active; returns {active_spell, spell_points}."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UC-12 Level Up at Review Board",
|
||||
"notes": "Manual level-up: XP threshold + gold cost (500 per next level), full resource restore on level-up.",
|
||||
"actors": [
|
||||
"Player"
|
||||
],
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Main Success Scenario",
|
||||
"type": "Basic Path",
|
||||
"notes": "",
|
||||
"steps": [
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "Player requests a level-up."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System validates: below max level 100, XP \u003e= 18*100*(level)^2 for the next level (i.e. xp_for_level(next)), gold \u003e= 500*next level."
|
||||
},
|
||||
{
|
||||
"actor": 0,
|
||||
"text": "System deducts the gold cost, increments the level, recomputes and fully restores HP/energy/nerve and max spell points; persists; returns {success, message, character}."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alternate flows",
|
||||
"type": "Alternate",
|
||||
"notes": "already at max level; not enough XP; not enough gold -\u003e all 422. Foreign character -\u003e 403; not found -\u003e 404.",
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
docs/openup/model/skara-brae.eapx
Normal file
BIN
docs/openup/model/skara-brae.eapx
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue