Skara Brae — Tech Stack & Architecture

# Skara Brae — Rust Backend Architecture

## Why This Stack in 2026

Torn launched in 2004 as a PHP/HTML text game. It received an engine overhaul in 2014 and a major Crimes 2.0 update in 2023. Modern Torn has real-time features, mobile apps, and a rich API.

For Skara Brae, we build for **2026+ expectations**:
- Instant real-time feedback (WebSocket everywhere)
- Mobile-first (PWA, not just responsive)
- API-first design (community tools from day one)
- Type-safe full stack
- Idle/offline progression (server-side tick engine)

---

## Multi-Repo Structure

The project is split into three repositories. See [Project Plan](joplin://088737f31c514f1da21cc42c0ab6acc1) for full rationale.

```
github.com/{org}/
├── skara-brae-server/          # This repo — Rust backend
├── skara-brae-client/          # Vue 3 frontend
└── skara-brae-common/          # Shared game data, API types, constants
```

---

## Server Project Structure

```
skara-brae-server/
├── Cargo.toml                  # Workspace root
├── crates/
│   ├── sb-api/                 # Axum HTTP API server
│   │   ├── src/
│   │   │   ├── main.rs
│   │   │   ├── config.rs
│   │   │   ├── routes/
│   │   │   │   ├── mod.rs
│   │   │   │   ├── auth.rs
│   │   │   │   ├── character.rs
│   │   │   │   ├── adventure.rs
│   │   │   │   ├── combat.rs
│   │   │   │   ├── market.rs
│   │   │   │   ├── guild.rs
│   │   │   │   ├── property.rs
│   │   │   │   ├── education.rs
│   │   │   │   ├── crafting.rs
│   │   │   │   ├── pets.rs
│   │   │   │   ├── blessings.rs
│   │   │   │   ├── inn.rs
│   │   │   │   └── slayer.rs
│   │   │   ├── middleware/
│   │   │   │   ├── auth.rs
│   │   │   │   └── rate_limit.rs
│   │   │   └── ws/
│   │   │       ├── handler.rs
│   │   │       └── channels.rs
│   │   └── Cargo.toml
│   │
│   ├── sb-core/                # Game engine core
│   │   ├── src/
│   │   │   ├── lib.rs
│   │   │   ├── character/
│   │   │   │   ├── mod.rs
│   │   │   │   ├── stats.rs         # BIGINT stat system, tiered display
│   │   │   │   ├── skills.rs
│   │   │   │   └── races.rs
│   │   │   ├── combat/
│   │   │   │   ├── mod.rs
│   │   │   │   ├── engine.rs
│   │   │   │   ├── actions.rs
│   │   │   │   ├── formulas.rs
│   │   │   │   └── crit_tables.rs   # RoleMaster-inspired open-ended d100
│   │   │   ├── adventure/
│   │   │   │   ├── mod.rs
│   │   │   │   ├── templates.rs
│   │   │   │   ├── rewards.rs
│   │   │   │   └── offline.rs       # Offline session reward calculation
│   │   │   ├── economy/
│   │   │   │   ├── mod.rs
│   │   │   │   ├── market.rs
│   │   │   │   └── bank.rs
│   │   │   ├── guild/
│   │   │   │   ├── mod.rs
│   │   │   │   ├── wars.rs
│   │   │   │   └── ranks.rs          # 10-rank progression system
│   │   │   ├── crafting/
│   │   │   │   ├── mod.rs
│   │   │   │   ├── professions.rs    # 12 professions (5 gathering + 7 crafting)
│   │   │   │   ├── recipes.rs
│   │   │   │   └── quality.rs        # Quality roll system (Common→Legendary)
│   │   │   ├── pets/
│   │   │   │   ├── mod.rs
│   │   │   │   ├── breeding.rs
│   │   │   │   └── bonuses.rs
│   │   │   ├── blessings/
│   │   │   │   ├── mod.rs
│   │   │   │   └── effects.rs
│   │   │   ├── workers/
│   │   │   │   ├── mod.rs             # Inn workers (NPC automation)
│   │   │   │   └── tasks.rs
│   │   │   ├── slayer/
│   │   │   │   ├── mod.rs
│   │   │   │   └── tasks.rs
│   │   │   ├── world/
│   │   │   │   ├── mod.rs
│   │   │   │   ├── time.rs         # Day/night cycle
│   │   │   │   ├── weather.rs
│   │   │   │   └── events.rs       # Dynamic world events
│   │   │   └── rules/
│   │   │       ├── mod.rs
│   │   │       └── formulas.rs      # All game formulas in one place
│   │   └── Cargo.toml
│   │
│   ├── sb-db/                  # Database layer
│   │   ├── src/
│   │   │   ├── lib.rs
│   │   │   ├── models/
│   │   │   │   ├── user.rs
│   │   │   │   ├── character.rs
│   │   │   │   ├── item.rs
│   │   │   │   ├── guild.rs
│   │   │   │   ├── property.rs
│   │   │   │   ├── pet.rs
│   │   │   │   ├── crafting.rs
│   │   │   │   └── blessing.rs
│   │   │   ├── queries/
│   │   │   └── migrations/
│   │   └── Cargo.toml
│   │
│   ├── sb-auth/                # Authentication & authorization
│   │   ├── src/
│   │   │   ├── lib.rs
│   │   │   ├── jwt.rs
│   │   │   ├── hash.rs           # argon2 password hashing
│   │   │   └── session.rs
│   │   └── Cargo.toml
│   │
│   └── sb-shared/              # Shared types, errors, constants
│       ├── src/
│       │   ├── lib.rs
│       │   ├── types.rs
│       │   ├── errors.rs
│       │   └── constants.rs
│       └── Cargo.toml
│
├── migrations/                 # SQL migrations
│   ├── 001_initial.sql
│   ├── 002_character.sql
│   ├── 003_guilds.sql
│   ├── 004_pets.sql
│   ├── 005_crafting.sql
│   └── 006_blessings.sql
│
├── docker-compose.yml
├── Dockerfile
└── README.md
```

---

## Key Crate Dependencies

### sb-api (Axum server)
```toml
[dependencies]
axum = { version = "0.8", features = ["ws", "macros"] }
tokio = { version = "1", features = ["full"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sb-core = { path = "../sb-core" }
sb-db = { path = "../sb-db" }
sb-auth = { path = "../sb-auth" }
sb-shared = { path = "../sb-shared" }
tracing = "0.1"
tracing-subscriber = "0.3"
```

### sb-core (Game engine)
```toml
[dependencies]
serde = { version = "1", features = ["derive"] }
rand = "0.9"
chrono = { version = "0.4", features = ["serde"] }
thiserror = "2"
sb-shared = { path = "../sb-shared" }
```

### sb-db (Database)
```toml
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "chrono", "uuid", "migrate"] }
sb-shared = { path = "../sb-shared" }
serde = { version = "1", features = ["derive"] }
```

### sb-auth (Auth)
```toml
[dependencies]
jsonwebtoken = "9"
argon2 = "0.5"
serde = { version = "1", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
sb-shared = { path = "../sb-shared" }
```

---

## Database Schema (Core Tables)

```sql
-- Users
CREATE TABLE users (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username    VARCHAR(32) UNIQUE NOT NULL,
    email       VARCHAR(255) UNIQUE NOT NULL,
    password    VARCHAR(255) NOT NULL,  -- argon2 hash
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_login  TIMESTAMPTZ,
    is_banned   BOOLEAN NOT NULL DEFAULT FALSE,
    is_patron   BOOLEAN NOT NULL DEFAULT FALSE
);

-- Characters (one per user, or multiple for patrons)
CREATE TABLE characters (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id     UUID NOT NULL REFERENCES users(id),
    name        VARCHAR(32) UNIQUE NOT NULL,
    race        VARCHAR(16) NOT NULL,
    class       VARCHAR(16) NOT NULL,
    level       INT NOT NULL DEFAULT 1,
    experience  BIGINT NOT NULL DEFAULT 0,
    gold        BIGINT NOT NULL DEFAULT 100,
    energy      INT NOT NULL DEFAULT 100,
    max_energy  INT NOT NULL DEFAULT 100,
    hp          BIGINT NOT NULL DEFAULT 100,
    max_hp      BIGINT NOT NULL DEFAULT 100,
    mana        BIGINT NOT NULL DEFAULT 50,
    max_mana    BIGINT NOT NULL DEFAULT 50,
    -- Base stats (BIGINT — exponential growth, display tiered)
    strength    BIGINT NOT NULL DEFAULT 10,
    agility     BIGINT NOT NULL DEFAULT 10,
    constitution BIGINT NOT NULL DEFAULT 10,
    intelligence BIGINT NOT NULL DEFAULT 10,
    wisdom      BIGINT NOT NULL DEFAULT 10,
    charisma    BIGINT NOT NULL DEFAULT 10,
    -- Location
    location    VARCHAR(32) NOT NULL DEFAULT 'tavern',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Skills
CREATE TABLE character_skills (
    character_id  UUID NOT NULL REFERENCES characters(id),
    skill_name    VARCHAR(32) NOT NULL,
    level         INT NOT NULL DEFAULT 0,
    experience    BIGINT NOT NULL DEFAULT 0,
    PRIMARY KEY (character_id, skill_name)
);

-- Inventory
CREATE TABLE inventory (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    character_id  UUID NOT NULL REFERENCES characters(id),
    item_id       UUID NOT NULL,
    quantity      INT NOT NULL DEFAULT 1,
    equipped_slot VARCHAR(16),
    quality       VARCHAR(16) NOT NULL DEFAULT 'common',  -- common, fine, superior, exceptional, masterwork, legendary
    acquired_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Guilds
CREATE TABLE guilds (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name        VARCHAR(64) UNIQUE NOT NULL,
    description TEXT,
    leader_id   UUID NOT NULL REFERENCES characters(id),
    treasury    BIGINT NOT NULL DEFAULT 0,
    tax_rate    SMALLINT NOT NULL DEFAULT 10 CHECK (tax_rate BETWEEN 0 AND 25),
    level       INT NOT NULL DEFAULT 1,
    reputation  BIGINT NOT NULL DEFAULT 0,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE guild_members (
    guild_id     UUID NOT NULL REFERENCES guilds(id),
    character_id UUID NOT NULL REFERENCES characters(id),
    rank         VARCHAR(16) NOT NULL DEFAULT 'initiate',
    reputation   BIGINT NOT NULL DEFAULT 0,
    joined_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (guild_id, character_id)
);

-- Properties
CREATE TABLE properties (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    property_type VARCHAR(32) NOT NULL,
    owner_id      UUID REFERENCES characters(id),
    guild_id      UUID REFERENCES guilds(id),
    level         INT NOT NULL DEFAULT 1,
    rent_price    BIGINT,
    sale_price    BIGINT,
    is_listed     BOOLEAN NOT NULL DEFAULT FALSE
);

-- Market listings
CREATE TABLE market_listings (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    seller_id     UUID NOT NULL REFERENCES characters(id),
    item_id       UUID NOT NULL,
    quantity      INT NOT NULL DEFAULT 1,
    price         BIGINT NOT NULL,
    listed_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at    TIMESTAMPTZ NOT NULL
);

-- Adventures log
CREATE TABLE adventure_log (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    character_id  UUID NOT NULL REFERENCES characters(id),
    adventure_id  VARCHAR(64) NOT NULL,
    result        VARCHAR(16) NOT NULL,
    rewards_gold  BIGINT NOT NULL DEFAULT 0,
    rewards_xp    BIGINT NOT NULL DEFAULT 0,
    started_at    TIMESTAMPTZ NOT NULL,
    completed_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Education enrollment
CREATE TABLE education_enrollments (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    character_id  UUID NOT NULL REFERENCES characters(id),
    course_id     VARCHAR(64) NOT NULL,
    enrolled_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    completes_at  TIMESTAMPTZ NOT NULL,
    completed     BOOLEAN NOT NULL DEFAULT FALSE
);

-- Pets
CREATE TABLE pets (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    character_id  UUID NOT NULL REFERENCES characters(id),
    pet_type      VARCHAR(32) NOT NULL,
    name          VARCHAR(32),
    rarity        VARCHAR(16) NOT NULL DEFAULT 'common',
    level         INT NOT NULL DEFAULT 1,
    experience    BIGINT NOT NULL DEFAULT 0,
    bonus_type    VARCHAR(32) NOT NULL,
    bonus_value   DECIMAL(5,2) NOT NULL DEFAULT 1.00,
    is_active     BOOLEAN NOT NULL DEFAULT FALSE,
    age_days      INT NOT NULL DEFAULT 0,
    is_dead       BOOLEAN NOT NULL DEFAULT FALSE,
    acquired_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE pet_equipment (
    id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    pet_id     UUID NOT NULL REFERENCES pets(id) ON DELETE CASCADE,
    item_id    UUID NOT NULL,
    slot       VARCHAR(16) NOT NULL  -- collar, armor, trinket
);

-- Crafting professions
CREATE TABLE character_professions (
    character_id  UUID NOT NULL REFERENCES characters(id),
    profession    VARCHAR(32) NOT NULL,
    mastery_level VARCHAR(16) NOT NULL DEFAULT 'novice',
    experience    BIGINT NOT NULL DEFAULT 0,
    PRIMARY KEY (character_id, profession)
);

-- Crafting recipes (discovered)
CREATE TABLE character_recipes (
    character_id  UUID NOT NULL REFERENCES characters(id),
    recipe_id     VARCHAR(64) NOT NULL,
    discovered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (character_id, recipe_id)
);

-- Blessings (active)
CREATE TABLE active_blessings (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    character_id  UUID NOT NULL REFERENCES characters(id),
    blessing_type VARCHAR(32) NOT NULL,
    effect_value  DECIMAL(5,2) NOT NULL,
    activated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at    TIMESTAMPTZ NOT NULL
);

-- Inn workers (hired NPCs)
CREATE TABLE hired_workers (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    character_id  UUID NOT NULL REFERENCES characters(id),
    worker_type   VARCHAR(32) NOT NULL,  -- mercenary, apprentice, gatherer
    quality       VARCHAR(16) NOT NULL DEFAULT 'novice',
    task          VARCHAR(64),
    hired_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at    TIMESTAMPTZ NOT NULL,
    cost_per_hour BIGINT NOT NULL
);

-- Slayer tasks
CREATE TABLE slayer_tasks (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    character_id  UUID NOT NULL REFERENCES characters(id),
    enemy_type    VARCHAR(64) NOT NULL,
    target_count  INT NOT NULL,
    current_count INT NOT NULL DEFAULT 0,
    assigned_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    completed     BOOLEAN NOT NULL DEFAULT FALSE
);

-- Offline session state
CREATE TABLE offline_sessions (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    character_id  UUID NOT NULL REFERENCES characters(id),
    session_type  VARCHAR(32) NOT NULL,  -- adventure, skill_training, gathering
    started_at    TIMESTAMPTZ NOT NULL,
    max_duration  INTERVAL NOT NULL DEFAULT INTERVAL '1 hour',
    claimed       BOOLEAN NOT NULL DEFAULT FALSE
);
```

---

## Why Rust for a Game Like This

1. **Performance**: Torn serves millions of requests. Rust handles this with minimal resources — single server can serve thousands of concurrent WebSocket connections.

2. **Type safety**: Game rules are complex (combat formulas, skill interactions, economy, crit tables). Rust's type system catches bugs at compile time. The `sb-core` crate becomes a verified game engine.

3. **Fearless concurrency**: Game tick engine processes timers for thousands of players simultaneously. Offline reward calculation runs alongside active gameplay. Tokio + Rust's ownership model makes this correct by construction.

4. **No GC pauses**: Critical for real-time combat. Rust provides deterministic performance.

5. **Workspace architecture**: Clean separation between game logic (`sb-core`), persistence (`sb-db`), API (`sb-api`), and auth (`sb-auth`). Each crate compiles independently — fast iteration.

6. **Ecosystem**: Axum, SQLx, Tokio, Serde are all mature, production-grade crates in 2026.

---

## Why Vue.js (Not React/Svelte)

- **Composition API** maps well to game UI patterns (reactive stat bars, inventory grids, combat logs)
- **Pinia** stores model game state naturally (character store, market store, combat store)
- **Single-file components** keep template + logic + styles co-located — good for complex game UIs
- User preference (stated)
- **PWA support** via Vite plugin for mobile-like experience without a separate app

---

## Related Notes

- [Game Concept Overview](joplin://fcd381c235694f29abf73665317a40f5) — Full game design with crafting, pets, inn, guilds
- [Combat System & Stat Scaling Design](joplin://5ff1fed180fa4b39b4cdb925f34c1008) — Crit tables, BIGINT stats, combat formula
- [Idle Fantasy Inspirations](joplin://fea2d81f0414484fa4edf126f8ca17ed) — Offline sessions, workers, pets, blessings
- [Lore & World Building](joplin://2c8e2a844da0497e8a93c820ec8901a1) — Districts, NPCs, storyline
- [Project Plan](joplin://088737f31c514f1da21cc42c0ab6acc1) — Roadmap, MVP scope, repo structure

id: 980c3eb587294e4383474b94988f2f88
parent_id: d1892c7c531848f5a5a3ac5e1749f7cf
created_time: 2026-06-11T13:32:48.037Z
updated_time: 2026-06-11T16:20:26.516Z
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: 1781184768037
user_created_time: 2026-06-11T13:32:48.037Z
user_updated_time: 2026-06-11T16:20:26.516Z
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
type_: 1