id: 6d3a597fcf48454b9fb0f6aaff042b88
parent_id: 
item_type: 1
item_id: 980c3eb587294e4383474b94988f2f88
item_updated_time: 1781194826516
title_diff: "[{\"diffs\":[[1,\"Skara Brae — Tech Stack & Architecture\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":38}]"
body_diff: "[{\"diffs\":[[1,\"# Skara Brae — Rust Backend Architecture\\\n\\\n## Why This Stack in 2026\\\n\\\nTorn 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.\\\n\\\nFor Skara Brae, we build for **2026+ expectations**:\\\n- Instant real-time feedback (WebSocket everywhere)\\\n- Mobile-first (PWA, not just responsive)\\\n- API-first design (community tools from day one)\\\n- Type-safe full stack\\\n- Idle/offline progression (server-side tick engine)\\\n\\\n---\\\n\\\n## Multi-Repo Structure\\\n\\\nThe project is split into three repositories. See [Project Plan](joplin://088737f31c514f1da21cc42c0ab6acc1) for full rationale.\\\n\\\n```\\\ngithub.com/{org}/\\\n├── skara-brae-server/          # This repo — Rust backend\\\n├── skara-brae-client/          # Vue 3 frontend\\\n└── skara-brae-common/          # Shared game data, API types, constants\\\n```\\\n\\\n---\\\n\\\n## Server Project Structure\\\n\\\n```\\\nskara-brae-server/\\\n├── Cargo.toml                  # Workspace root\\\n├── crates/\\\n│   ├── sb-api/                 # Axum HTTP API server\\\n│   │   ├── src/\\\n│   │   │   ├── main.rs\\\n│   │   │   ├── config.rs\\\n│   │   │   ├── routes/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   ├── auth.rs\\\n│   │   │   │   ├── character.rs\\\n│   │   │   │   ├── adventure.rs\\\n│   │   │   │   ├── combat.rs\\\n│   │   │   │   ├── market.rs\\\n│   │   │   │   ├── guild.rs\\\n│   │   │   │   ├── property.rs\\\n│   │   │   │   ├── education.rs\\\n│   │   │   │   ├── crafting.rs\\\n│   │   │   │   ├── pets.rs\\\n│   │   │   │   ├── blessings.rs\\\n│   │   │   │   ├── inn.rs\\\n│   │   │   │   └── slayer.rs\\\n│   │   │   ├── middleware/\\\n│   │   │   │   ├── auth.rs\\\n│   │   │   │   └── rate_limit.rs\\\n│   │   │   └── ws/\\\n│   │   │       ├── handler.rs\\\n│   │   │       └── channels.rs\\\n│   │   └── Cargo.toml\\\n│   │\\\n│   ├── sb-core/                # Game engine core\\\n│   │   ├── src/\\\n│   │   │   ├── lib.rs\\\n│   │   │   ├── character/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   ├── stats.rs         # BIGINT stat system, tiered display\\\n│   │   │   │   ├── skills.rs\\\n│   │   │   │   └── races.rs\\\n│   │   │   ├── combat/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   ├── engine.rs\\\n│   │   │   │   ├── actions.rs\\\n│   │   │   │   ├── formulas.rs\\\n│   │   │   │   └── crit_tables.rs   # RoleMaster-inspired open-ended d100\\\n│   │   │   ├── adventure/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   ├── templates.rs\\\n│   │   │   │   ├── rewards.rs\\\n│   │   │   │   └── offline.rs       # Offline session reward calculation\\\n│   │   │   ├── economy/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   ├── market.rs\\\n│   │   │   │   └── bank.rs\\\n│   │   │   ├── guild/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   ├── wars.rs\\\n│   │   │   │   └── ranks.rs          # 10-rank progression system\\\n│   │   │   ├── crafting/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   ├── professions.rs    # 12 professions (5 gathering + 7 crafting)\\\n│   │   │   │   ├── recipes.rs\\\n│   │   │   │   └── quality.rs        # Quality roll system (Common→Legendary)\\\n│   │   │   ├── pets/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   ├── breeding.rs\\\n│   │   │   │   └── bonuses.rs\\\n│   │   │   ├── blessings/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   └── effects.rs\\\n│   │   │   ├── workers/\\\n│   │   │   │   ├── mod.rs             # Inn workers (NPC automation)\\\n│   │   │   │   └── tasks.rs\\\n│   │   │   ├── slayer/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   └── tasks.rs\\\n│   │   │   ├── world/\\\n│   │   │   │   ├── mod.rs\\\n│   │   │   │   ├── time.rs         # Day/night cycle\\\n│   │   │   │   ├── weather.rs\\\n│   │   │   │   └── events.rs       # Dynamic world events\\\n│   │   │   └── rules/\\\n│   │   │       ├── mod.rs\\\n│   │   │       └── formulas.rs      # All game formulas in one place\\\n│   │   └── Cargo.toml\\\n│   │\\\n│   ├── sb-db/                  # Database layer\\\n│   │   ├── src/\\\n│   │   │   ├── lib.rs\\\n│   │   │   ├── models/\\\n│   │   │   │   ├── user.rs\\\n│   │   │   │   ├── character.rs\\\n│   │   │   │   ├── item.rs\\\n│   │   │   │   ├── guild.rs\\\n│   │   │   │   ├── property.rs\\\n│   │   │   │   ├── pet.rs\\\n│   │   │   │   ├── crafting.rs\\\n│   │   │   │   └── blessing.rs\\\n│   │   │   ├── queries/\\\n│   │   │   └── migrations/\\\n│   │   └── Cargo.toml\\\n│   │\\\n│   ├── sb-auth/                # Authentication & authorization\\\n│   │   ├── src/\\\n│   │   │   ├── lib.rs\\\n│   │   │   ├── jwt.rs\\\n│   │   │   ├── hash.rs           # argon2 password hashing\\\n│   │   │   └── session.rs\\\n│   │   └── Cargo.toml\\\n│   │\\\n│   └── sb-shared/              # Shared types, errors, constants\\\n│       ├── src/\\\n│       │   ├── lib.rs\\\n│       │   ├── types.rs\\\n│       │   ├── errors.rs\\\n│       │   └── constants.rs\\\n│       └── Cargo.toml\\\n│\\\n├── migrations/                 # SQL migrations\\\n│   ├── 001_initial.sql\\\n│   ├── 002_character.sql\\\n│   ├── 003_guilds.sql\\\n│   ├── 004_pets.sql\\\n│   ├── 005_crafting.sql\\\n│   └── 006_blessings.sql\\\n│\\\n├── docker-compose.yml\\\n├── Dockerfile\\\n└── README.md\\\n```\\\n\\\n---\\\n\\\n## Key Crate Dependencies\\\n\\\n### sb-api (Axum server)\\\n```toml\\\n[dependencies]\\\naxum = { version = \\\"0.8\\\", features = [\\\"ws\\\", \\\"macros\\\"] }\\\ntokio = { version = \\\"1\\\", features = [\\\"full\\\"] }\\\ntower = \\\"0.5\\\"\\\ntower-http = { version = \\\"0.6\\\", features = [\\\"cors\\\", \\\"trace\\\", \\\"compression-gzip\\\"] }\\\nserde = { version = \\\"1\\\", features = [\\\"derive\\\"] }\\\nserde_json = \\\"1\\\"\\\nsb-core = { path = \\\"../sb-core\\\" }\\\nsb-db = { path = \\\"../sb-db\\\" }\\\nsb-auth = { path = \\\"../sb-auth\\\" }\\\nsb-shared = { path = \\\"../sb-shared\\\" }\\\ntracing = \\\"0.1\\\"\\\ntracing-subscriber = \\\"0.3\\\"\\\n```\\\n\\\n### sb-core (Game engine)\\\n```toml\\\n[dependencies]\\\nserde = { version = \\\"1\\\", features = [\\\"derive\\\"] }\\\nrand = \\\"0.9\\\"\\\nchrono = { version = \\\"0.4\\\", features = [\\\"serde\\\"] }\\\nthiserror = \\\"2\\\"\\\nsb-shared = { path = \\\"../sb-shared\\\" }\\\n```\\\n\\\n### sb-db (Database)\\\n```toml\\\n[dependencies]\\\nsqlx = { version = \\\"0.8\\\", features = [\\\"runtime-tokio\\\", \\\"postgres\\\", \\\"chrono\\\", \\\"uuid\\\", \\\"migrate\\\"] }\\\nsb-shared = { path = \\\"../sb-shared\\\" }\\\nserde = { version = \\\"1\\\", features = [\\\"derive\\\"] }\\\n```\\\n\\\n### sb-auth (Auth)\\\n```toml\\\n[dependencies]\\\njsonwebtoken = \\\"9\\\"\\\nargon2 = \\\"0.5\\\"\\\nserde = { version = \\\"1\\\", features = [\\\"derive\\\"] }\\\nchrono = { version = \\\"0.4\\\", features = [\\\"serde\\\"] }\\\nsb-shared = { path = \\\"../sb-shared\\\" }\\\n```\\\n\\\n---\\\n\\\n## Database Schema (Core Tables)\\\n\\\n```sql\\\n-- Users\\\nCREATE TABLE users (\\\n    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    username    VARCHAR(32) UNIQUE NOT NULL,\\\n    email       VARCHAR(255) UNIQUE NOT NULL,\\\n    password    VARCHAR(255) NOT NULL,  -- argon2 hash\\\n    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),\\\n    last_login  TIMESTAMPTZ,\\\n    is_banned   BOOLEAN NOT NULL DEFAULT FALSE,\\\n    is_patron   BOOLEAN NOT NULL DEFAULT FALSE\\\n);\\\n\\\n-- Characters (one per user, or multiple for patrons)\\\nCREATE TABLE characters (\\\n    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    user_id     UUID NOT NULL REFERENCES users(id),\\\n    name        VARCHAR(32) UNIQUE NOT NULL,\\\n    race        VARCHAR(16) NOT NULL,\\\n    class       VARCHAR(16) NOT NULL,\\\n    level       INT NOT NULL DEFAULT 1,\\\n    experience  BIGINT NOT NULL DEFAULT 0,\\\n    gold        BIGINT NOT NULL DEFAULT 100,\\\n    energy      INT NOT NULL DEFAULT 100,\\\n    max_energy  INT NOT NULL DEFAULT 100,\\\n    hp          BIGINT NOT NULL DEFAULT 100,\\\n    max_hp      BIGINT NOT NULL DEFAULT 100,\\\n    mana        BIGINT NOT NULL DEFAULT 50,\\\n    max_mana    BIGINT NOT NULL DEFAULT 50,\\\n    -- Base stats (BIGINT — exponential growth, display tiered)\\\n    strength    BIGINT NOT NULL DEFAULT 10,\\\n    agility     BIGINT NOT NULL DEFAULT 10,\\\n    constitution BIGINT NOT NULL DEFAULT 10,\\\n    intelligence BIGINT NOT NULL DEFAULT 10,\\\n    wisdom      BIGINT NOT NULL DEFAULT 10,\\\n    charisma    BIGINT NOT NULL DEFAULT 10,\\\n    -- Location\\\n    location    VARCHAR(32) NOT NULL DEFAULT 'tavern',\\\n    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()\\\n);\\\n\\\n-- Skills\\\nCREATE TABLE character_skills (\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    skill_name    VARCHAR(32) NOT NULL,\\\n    level         INT NOT NULL DEFAULT 0,\\\n    experience    BIGINT NOT NULL DEFAULT 0,\\\n    PRIMARY KEY (character_id, skill_name)\\\n);\\\n\\\n-- Inventory\\\nCREATE TABLE inventory (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    item_id       UUID NOT NULL,\\\n    quantity      INT NOT NULL DEFAULT 1,\\\n    equipped_slot VARCHAR(16),\\\n    quality       VARCHAR(16) NOT NULL DEFAULT 'common',  -- common, fine, superior, exceptional, masterwork, legendary\\\n    acquired_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()\\\n);\\\n\\\n-- Guilds\\\nCREATE TABLE guilds (\\\n    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    name        VARCHAR(64) UNIQUE NOT NULL,\\\n    description TEXT,\\\n    leader_id   UUID NOT NULL REFERENCES characters(id),\\\n    treasury    BIGINT NOT NULL DEFAULT 0,\\\n    tax_rate    SMALLINT NOT NULL DEFAULT 10 CHECK (tax_rate BETWEEN 0 AND 25),\\\n    level       INT NOT NULL DEFAULT 1,\\\n    reputation  BIGINT NOT NULL DEFAULT 0,\\\n    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()\\\n);\\\n\\\nCREATE TABLE guild_members (\\\n    guild_id     UUID NOT NULL REFERENCES guilds(id),\\\n    character_id UUID NOT NULL REFERENCES characters(id),\\\n    rank         VARCHAR(16) NOT NULL DEFAULT 'initiate',\\\n    reputation   BIGINT NOT NULL DEFAULT 0,\\\n    joined_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),\\\n    PRIMARY KEY (guild_id, character_id)\\\n);\\\n\\\n-- Properties\\\nCREATE TABLE properties (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    property_type VARCHAR(32) NOT NULL,\\\n    owner_id      UUID REFERENCES characters(id),\\\n    guild_id      UUID REFERENCES guilds(id),\\\n    level         INT NOT NULL DEFAULT 1,\\\n    rent_price    BIGINT,\\\n    sale_price    BIGINT,\\\n    is_listed     BOOLEAN NOT NULL DEFAULT FALSE\\\n);\\\n\\\n-- Market listings\\\nCREATE TABLE market_listings (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    seller_id     UUID NOT NULL REFERENCES characters(id),\\\n    item_id       UUID NOT NULL,\\\n    quantity      INT NOT NULL DEFAULT 1,\\\n    price         BIGINT NOT NULL,\\\n    listed_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),\\\n    expires_at    TIMESTAMPTZ NOT NULL\\\n);\\\n\\\n-- Adventures log\\\nCREATE TABLE adventure_log (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    adventure_id  VARCHAR(64) NOT NULL,\\\n    result        VARCHAR(16) NOT NULL,\\\n    rewards_gold  BIGINT NOT NULL DEFAULT 0,\\\n    rewards_xp    BIGINT NOT NULL DEFAULT 0,\\\n    started_at    TIMESTAMPTZ NOT NULL,\\\n    completed_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()\\\n);\\\n\\\n-- Education enrollment\\\nCREATE TABLE education_enrollments (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    course_id     VARCHAR(64) NOT NULL,\\\n    enrolled_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),\\\n    completes_at  TIMESTAMPTZ NOT NULL,\\\n    completed     BOOLEAN NOT NULL DEFAULT FALSE\\\n);\\\n\\\n-- Pets\\\nCREATE TABLE pets (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    pet_type      VARCHAR(32) NOT NULL,\\\n    name          VARCHAR(32),\\\n    rarity        VARCHAR(16) NOT NULL DEFAULT 'common',\\\n    level         INT NOT NULL DEFAULT 1,\\\n    experience    BIGINT NOT NULL DEFAULT 0,\\\n    bonus_type    VARCHAR(32) NOT NULL,\\\n    bonus_value   DECIMAL(5,2) NOT NULL DEFAULT 1.00,\\\n    is_active     BOOLEAN NOT NULL DEFAULT FALSE,\\\n    age_days      INT NOT NULL DEFAULT 0,\\\n    is_dead       BOOLEAN NOT NULL DEFAULT FALSE,\\\n    acquired_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()\\\n);\\\n\\\nCREATE TABLE pet_equipment (\\\n    id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    pet_id     UUID NOT NULL REFERENCES pets(id) ON DELETE CASCADE,\\\n    item_id    UUID NOT NULL,\\\n    slot       VARCHAR(16) NOT NULL  -- collar, armor, trinket\\\n);\\\n\\\n-- Crafting professions\\\nCREATE TABLE character_professions (\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    profession    VARCHAR(32) NOT NULL,\\\n    mastery_level VARCHAR(16) NOT NULL DEFAULT 'novice',\\\n    experience    BIGINT NOT NULL DEFAULT 0,\\\n    PRIMARY KEY (character_id, profession)\\\n);\\\n\\\n-- Crafting recipes (discovered)\\\nCREATE TABLE character_recipes (\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    recipe_id     VARCHAR(64) NOT NULL,\\\n    discovered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\\\n    PRIMARY KEY (character_id, recipe_id)\\\n);\\\n\\\n-- Blessings (active)\\\nCREATE TABLE active_blessings (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    blessing_type VARCHAR(32) NOT NULL,\\\n    effect_value  DECIMAL(5,2) NOT NULL,\\\n    activated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),\\\n    expires_at    TIMESTAMPTZ NOT NULL\\\n);\\\n\\\n-- Inn workers (hired NPCs)\\\nCREATE TABLE hired_workers (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    worker_type   VARCHAR(32) NOT NULL,  -- mercenary, apprentice, gatherer\\\n    quality       VARCHAR(16) NOT NULL DEFAULT 'novice',\\\n    task          VARCHAR(64),\\\n    hired_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),\\\n    expires_at    TIMESTAMPTZ NOT NULL,\\\n    cost_per_hour BIGINT NOT NULL\\\n);\\\n\\\n-- Slayer tasks\\\nCREATE TABLE slayer_tasks (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    enemy_type    VARCHAR(64) NOT NULL,\\\n    target_count  INT NOT NULL,\\\n    current_count INT NOT NULL DEFAULT 0,\\\n    assigned_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),\\\n    completed     BOOLEAN NOT NULL DEFAULT FALSE\\\n);\\\n\\\n-- Offline session state\\\nCREATE TABLE offline_sessions (\\\n    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),\\\n    character_id  UUID NOT NULL REFERENCES characters(id),\\\n    session_type  VARCHAR(32) NOT NULL,  -- adventure, skill_training, gathering\\\n    started_at    TIMESTAMPTZ NOT NULL,\\\n    max_duration  INTERVAL NOT NULL DEFAULT INTERVAL '1 hour',\\\n    claimed       BOOLEAN NOT NULL DEFAULT FALSE\\\n);\\\n```\\\n\\\n---\\\n\\\n## Why Rust for a Game Like This\\\n\\\n1. **Performance**: Torn serves millions of requests. Rust handles this with minimal resources — single server can serve thousands of concurrent WebSocket connections.\\\n\\\n2. **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.\\\n\\\n3. **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.\\\n\\\n4. **No GC pauses**: Critical for real-time combat. Rust provides deterministic performance.\\\n\\\n5. **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.\\\n\\\n6. **Ecosystem**: Axum, SQLx, Tokio, Serde are all mature, production-grade crates in 2026.\\\n\\\n---\\\n\\\n## Why Vue.js (Not React/Svelte)\\\n\\\n- **Composition API** maps well to game UI patterns (reactive stat bars, inventory grids, combat logs)\\\n- **Pinia** stores model game state naturally (character store, market store, combat store)\\\n- **Single-file components** keep template + logic + styles co-located — good for complex game UIs\\\n- User preference (stated)\\\n- **PWA support** via Vite plugin for mobile-like experience without a separate app\\\n\\\n---\\\n\\\n## Related Notes\\\n\\\n- [Game Concept Overview](joplin://fcd381c235694f29abf73665317a40f5) — Full game design with crafting, pets, inn, guilds\\\n- [Combat System & Stat Scaling Design](joplin://5ff1fed180fa4b39b4cdb925f34c1008) — Crit tables, BIGINT stats, combat formula\\\n- [Idle Fantasy Inspirations](joplin://fea2d81f0414484fa4edf126f8ca17ed) — Offline sessions, workers, pets, blessings\\\n- [Lore & World Building](joplin://2c8e2a844da0497e8a93c820ec8901a1) — Districts, NPCs, storyline\\\n- [Project Plan](joplin://088737f31c514f1da21cc42c0ab6acc1) — Roadmap, MVP scope, repo structure\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":15966}]"
metadata_diff: {"new":{"id":"980c3eb587294e4383474b94988f2f88","parent_id":"d1892c7c531848f5a5a3ac5e1749f7cf","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,"markup_language":1,"is_shared":0,"share_id":"","conflict_original_id":"","master_key_id":"","user_data":"","deleted_time":0},"deleted":[]}
encryption_cipher_text: 
encryption_applied: 0
updated_time: 2026-06-11T16:26:30.982Z
created_time: 2026-06-11T16:26:30.982Z
type_: 13