Skara Brae — Project Plan

# Skara Brae — Project Plan

## Status: Active Development (Phase 1 — Core Mechanics + Quality)

## Summary

A fantasy idle RPG inspired by **The Bard's Tale 1** with **Torn City** UI/economy patterns and a **RoleMaster-inspired** combat system. Functional prototype with in-memory state. Three quality audits passed.

---

## What's Built (2026-08-03)

### Backend (Rust + Axum)
- **127 authentic BT1 items** (weapons, armor, accessories) with original gold values
- **125 enemies** (BT1 bestiary — all tiers from Kobold to Mangar)
- **79 spells** (Conjurer/Magician/Sorcerer/Wizard × 7 levels) with spell point costs
- **6 bard songs** with combat effects
- **10 classes**: Warrior, Paladin, Rogue, Bard, Hunter, Monk, Mage, Wizard, Conjurer, Sorcerer
- **5 races**: Human, Elf, Dwarf, Hobbit, HalfElf
- **11 equipment slots**: head, body, off_hand, main_hand, hands, feet, back, neck, ring, trinket, instrument
- **BIGINT stats (i64)**: CharacterStats, EnemyStats, CombatStats all use i64
- **RoleMaster combat engine**: open-ended d100, OB/DB from stats+equipment, crit tables (slash/crush/puncture A-D)
- **Crit damage cap by level difference**: +10 = 60% HP cap, +20 = 40%, +50 = 15%, +100 = 5%
- **Spell system**: auto-learn level 1 at creation, learn higher at Review Board (gold cost), activate one for combat, SP deducted per fight. Mage↔Magician mapping via `class_for_spells()` / `is_caster_class()`
- **Song combat integration**: bard song buffs/debuffs
- **Shop economy**: 15 basic items always in stock, sell adds to stock, buy checks gold before stock
- **Resource system**: Energy (gym), Nerve (adventures), HP — regen via 15s client polling
- **Training Yard**: 5 themed grounds, costs 1 energy + gold per train
- **Temple**: Heal costs 1 gold per HP
- **8 adventure zones** matching BT1 dungeon progression
- **Map data**: Skara Brae overworld (32 locations) + 16 dungeon levels as JSON
- **Domain services**: `services.rs` — pick_enemy(), equipped_attack_type/damage(), total_defense(), equip/unequip_item(), train_stat() with injectable RNG
- **Review Board level-up**: POST `/api/characters/{id}/level-up`, manual level-up at guild, XP + gold cost, fully restores HP/energy/nerve/SP. MAX_LEVEL = 100. XP curve: `18 * 100 * (level-1)^2` (Lv2=1,800, Lv10=513K, Lv50=72.8M, Lv100=591M total)
- **44 unit tests**: character (19), combat (8), services (17) — all passing
- **Seeded users**: admin (id=1) + player (id=2)
- **0 clippy warnings** with `-W clippy::all`

### Client (Vue 3 + Vite)
- **Torn-style layout**: top bar, left sidebar, main content
- **Lucide icons** (Sparkles, WandSparkles for spells)
- **Pages**: Home, Training Yard, Garth's, Temple, Inventory, Adventure, Songs (Bard-only), Spells (caster classes only), Item Catalogue (admin-only)
- **Review Board**: Level-up panel on Home dashboard when XP threshold met, shows progress bar + gold cost
- **Spells page**: Review Board (learn new spell levels for gold), known spells grid, activate/deactivate
- **Songs page**: Card list with Play/Stop, level gates
- **SP bar**: Gold-colored, visible for caster classes
- **Active spell indicator**: Shown on adventure page
- **Regen polling**: 15s interval refreshes selected character
- **MAX_LEVEL = 100** in client (matches server)

### Quality (3 audits passed)
- **Audit 1**: Fixed 3 critical bugs (mage spell mismatch, race condition in run_adventure, buy_item stock ordering) + 18 clippy warnings
- **Audit 2**: Fixed 3 critical bugs (xp_for_level overflow, crit_damage_cap dead branch, default zone_id mismatch)
- **Audit 3**: Fixed MAX_LEVEL 50→100, crit_damage_cap param rename. Zero new criticals.

---

## Design Doc Inconsistencies (all resolved)

| Area | Design | Implementation | Status |
|---|---|---|---|
| Stats type | BIGINT (i64) | i64 for all stat fields | Done |
| Stats count | 6 (STR/AGI/CON/INT/WIS/CHA) | 5 (STR/INT/DEX/CON/LUK) | Acceptable |
| Races | 6 (incl. Half-Orc) | 5 | Half-Orc deferred |
| Classes | 8 (incl. Cleric) | 10 (no Cleric, 4 casters) | Matches BT1 |
| Crit cap by level diff | Designed | Implemented | Done |
| Spells data | Designed | 79 spells + combat | Done |
| Zones | Abstract | 8 zones BT1 dungeons | Done |

---

## Server BGP
- `bgp_fc60e7d420013nBqe0Uu5Bnkt5` on `:8080`
- Client: `bgp_fc61a2927001eAzZARzgLYl4TK` on `:5173`

---

## Tech Stack
- Backend: Rust (Axum 0.8, edition 2024) on :8080
- Client: Vue 3 + Vite on :5173 (proxied to :8080)
- Data: `skara-brae-common/data/` (items, enemies, spells, crit-tables, maps, zones)

---

## Remaining Known Warnings (deferred)
- `create_character()` and `resolve_combat_full()` use `rand::rng()` directly (should accept injectable RNG)
- ~140 lines of business logic in `run_adventure` route handler
- 25+ magic numbers across combat/character code
- 16 handlers return untyped `Json<serde_json::Value>` instead of typed structs
- 11 low-risk `unwrap()` calls in route handlers
- SP deducted on combat loss (may be intentional — you cast, you pay)
- `caster_classes()` returns "magician" alongside `is_caster_class()` (latent naming)

---

## Next Steps (priority order)

1. **Persistent storage** — swap in-memory state for a database (sb-db crate exists)
2. **PvP combat** — energy-costed, Torn-style screen
3. **Idle auto-combat** — offline progress
4. **Dungeon exploration mode** — using map data
5. **Medals & Honours** — achievement system
6. **Auth** — JWT or session-based
7. **Architecture cleanup** — typed responses, extract business logic from routes, named constants
8. **Injectable RNG** — for create_character and resolve_combat_full

id: 088737f31c514f1da21cc42c0ab6acc1
parent_id: d1892c7c531848f5a5a3ac5e1749f7cf
created_time: 2026-06-11T13:40:16.157Z
updated_time: 2026-08-03T10:20:26.101Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author: 
source_url: 
is_todo: 0
todo_due: 0
todo_completed: 0
source: joplin-desktop
source_application: net.cozic.joplin-desktop
application_data: 
order: 1781185216157
user_created_time: 2026-06-11T13:40:16.157Z
user_updated_time: 2026-08-03T10:20:26.101Z
encryption_cipher_text: 
encryption_applied: 0
markup_language: 1
is_shared: 0
share_id: 
conflict_original_id: 
master_key_id: 
user_data: 
deleted_time: 0
is_locked: 0
extracted_resource_ids: 
type_: 1