-- OTIVM Per-Player Database Schema
-- File: data/create_player_db.sql
-- Version: OTIVM-III
-- Status: Game schema — transitional, designed to be throw-away
-- Replaces: data/saves/{session_id}.json (OTIVM-I/II)
-- Parallel to: data/create_otivm_db.sql (TESSERA world substrate)
--
-- Naming conventions: terminology.md Layer 3 (snake_case, period-neutral)
-- Timestamp column: recorded_at (never created_at, never timestamp)
-- Rows are never deleted — only archived or superseded
-- value_true and value_perceived are always separate columns
-- confidence_tag is always a first-class column, never a comment
--
-- When this schema is retired, it becomes a read-only layer
-- readable by the Simulator via the existing API translation layer.
-- The API is the universal wiring — this schema does not need to
-- anticipate the Simulator schema.
--
-- TheRON — single contributor. AI assistants implement, document,
-- flag — do not direct.

PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA user_version = 3; -- OTIVM-III schema version

-- ============================================================
-- TABLE: actor_profile
-- One row per actor. The static anchor.
-- Created at session initialisation. Never updated — superseded
-- by a new row if background is changed (not currently possible
-- in OTIVM-III but the structure supports it).
-- ============================================================

CREATE TABLE IF NOT EXISTS actor_profile (
    actor_id        TEXT NOT NULL,          -- uuid, matches save file naming
    session_id      TEXT NOT NULL,          -- uuid, links to session chain
    background_id   TEXT NOT NULL,          -- former_legionary | freedman_trader |
                                            -- noble_younger_son | failed_magistrate |
                                            -- camp_logistician | guild_scribe
    actor_name      TEXT NOT NULL,          -- display name chosen by participant
    epoch           TEXT NOT NULL           -- roman_14bce (Layer 3 code token)
                    DEFAULT 'roman_14bce',
    schema_version  INTEGER NOT NULL DEFAULT 3,
    recorded_at     TEXT NOT NULL,          -- ISO 8601 UTC
    PRIMARY KEY (actor_id, recorded_at)
);

-- ============================================================
-- TABLE: actor_parameters
-- The core parameter table.
-- One row per parameter per actor per recorded moment.
-- Append-only — new row on every change, old row retained.
--
-- Parameters with observable: partial or observable: hidden
-- carry both value_true (server-side ground truth) and
-- value_perceived (what the actor believes or can infer).
-- These are never merged into one column.
--
-- For parameters where perceived == true (observable: full),
-- value_perceived is set equal to value_true at write time.
--
-- confidence_tag vocabulary (from parameter-registry.md):
--   measured | indicated | inferred | estimated | unknown
--
-- observable_level vocabulary:
--   full | partial | hidden
--
-- value columns are TEXT to support ordinal bands (low/medium/high),
-- enums, floats, integers, and JSON structures without schema changes.
-- The parameter_token identifies what type to expect.
-- ============================================================

CREATE TABLE IF NOT EXISTS actor_parameters (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    actor_id        TEXT NOT NULL,
    parameter_token TEXT NOT NULL,          -- from parameter-registry.md token field
    scope           TEXT NOT NULL,          -- actor | scenario | relation
    layer           TEXT NOT NULL,          -- roman | mesolithic | universal
    value_true      TEXT NOT NULL,          -- server-side ground truth
    value_perceived TEXT NOT NULL,          -- actor-side belief (= value_true when full)
    value_social    TEXT,                   -- market consensus (auctoritas only)
    confidence_tag  TEXT NOT NULL           -- measured | indicated | inferred |
                    DEFAULT 'estimated',    -- estimated | unknown
    observable_level TEXT NOT NULL,         -- full | partial | hidden
    drift_source    TEXT,                   -- what caused this value (event_id, or NULL if initial)
    recorded_at     TEXT NOT NULL,          -- ISO 8601 UTC
    superseded_at   TEXT,                   -- NULL if current; set when a newer row replaces this one
    FOREIGN KEY (actor_id) REFERENCES actor_profile(actor_id)
);

-- Index for fast current-value lookup
CREATE INDEX IF NOT EXISTS idx_actor_parameters_current
    ON actor_parameters (actor_id, parameter_token, superseded_at);

-- ============================================================
-- TABLE: parameter_drift_log
-- Append-only event log of every parameter change.
-- What changed, why, what triggered it, old and new values.
-- The behavioral record. Rows are never deleted.
-- This is the source of truth for the Simulator when it
-- reads this database as a read-only layer.
-- ============================================================

CREATE TABLE IF NOT EXISTS parameter_drift_log (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    actor_id        TEXT NOT NULL,
    parameter_token TEXT NOT NULL,
    trigger_type    TEXT NOT NULL,          -- venture_complete | leg_complete |
                                            -- interval_complete | exchange_complete |
                                            -- scenario_event | session_start |
                                            -- background_drift | manual
    trigger_ref     TEXT,                   -- venture_id, leg_id, scenario_id, or NULL
    value_before    TEXT NOT NULL,
    value_after     TEXT NOT NULL,
    delta_note      TEXT,                   -- human-readable reason for drift
    recorded_at     TEXT NOT NULL,          -- ISO 8601 UTC
    FOREIGN KEY (actor_id) REFERENCES actor_profile(actor_id)
);

CREATE INDEX IF NOT EXISTS idx_drift_log_actor
    ON parameter_drift_log (actor_id, recorded_at);

-- ============================================================
-- TABLE: ventures
-- One row per venture (NEGOTIVM in Roman layer).
-- Status tracks the lifecycle. Outcome recorded on completion.
-- ============================================================

CREATE TABLE IF NOT EXISTS ventures (
    venture_id      TEXT PRIMARY KEY,       -- uuid
    actor_id        TEXT NOT NULL,
    venture_label   TEXT NOT NULL,          -- human-readable, e.g. "Ostia to Capua"
    status          TEXT NOT NULL           -- planned | active | complete | abandoned
                    DEFAULT 'planned',
    cargo_type      TEXT,                   -- amphora type, modius goods, etc.
    cargo_quantity  REAL,
    cargo_unit      TEXT,                   -- amphora | modius | talent | unit
    cost_total      REAL,                   -- denarii-equivalent, sum of all legs
    revenue_total   REAL,                   -- denarii-equivalent, at settlement
    outcome_net     REAL,                   -- revenue_total - cost_total (NULL until complete)
    outcome_note    TEXT,                   -- narrative summary of outcome
    recorded_at     TEXT NOT NULL,          -- ISO 8601 UTC — venture created
    started_at      TEXT,                   -- ISO 8601 UTC — first leg begun
    completed_at    TEXT,                   -- ISO 8601 UTC — final settlement
    FOREIGN KEY (actor_id) REFERENCES actor_profile(actor_id)
);

CREATE INDEX IF NOT EXISTS idx_ventures_actor
    ON ventures (actor_id, status);

-- ============================================================
-- TABLE: venture_legs
-- One row per leg (ITER in Roman layer).
-- Indivisible unit of movement within a venture.
-- Cost components recorded separately for Simulator readability.
-- ============================================================

CREATE TABLE IF NOT EXISTS venture_legs (
    leg_id          TEXT PRIMARY KEY,       -- uuid
    venture_id      TEXT NOT NULL,
    leg_sequence    INTEGER NOT NULL,       -- 1, 2, 3... within the venture
    origin_h3       TEXT NOT NULL,          -- H3 cell ID (TESSERA-compatible)
    destination_h3  TEXT NOT NULL,          -- H3 cell ID (TESSERA-compatible)
    mode            TEXT NOT NULL,          -- road | sea | river | overland
    vessel_type     TEXT,                   -- navis_oneraria | actuaria | NULL if land
    duration_days   REAL,                   -- actual duration (NULL until complete)
    cost_vectura    REAL,                   -- freight charge (VECTVRA)
    cost_portoria   REAL,                   -- customs duty (PORTORIVM)
    cost_other      REAL,                   -- horreum, incidentals
    cost_total      REAL,                   -- sum of above
    status          TEXT NOT NULL           -- planned | active | complete | failed
                    DEFAULT 'planned',
    delay_days      REAL,                   -- deviation from expected duration
    delay_cause     TEXT,                   -- weather | congestion | dispute | NULL
    recorded_at     TEXT NOT NULL,          -- ISO 8601 UTC — leg created
    started_at      TEXT,
    completed_at    TEXT,
    FOREIGN KEY (venture_id) REFERENCES ventures(venture_id)
);

CREATE INDEX IF NOT EXISTS idx_legs_venture
    ON venture_legs (venture_id, leg_sequence);

-- ============================================================
-- TABLE: scenario_state
-- Active and archived scenario parameters.
-- Transient during active window, archived on close.
-- Must not persist into actor_parameters as permanent values —
-- scenario parameters are pressures, not conditions.
-- ============================================================

CREATE TABLE IF NOT EXISTS scenario_state (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    scenario_id     TEXT NOT NULL,          -- e.g. SCENARIO-MERCHANT-0001
    actor_id        TEXT NOT NULL,
    parameter_token TEXT NOT NULL,          -- from parameter-registry.md
    value_true      TEXT NOT NULL,
    value_perceived TEXT NOT NULL,
    confidence_tag  TEXT NOT NULL DEFAULT 'estimated',
    observable_level TEXT NOT NULL,
    recorded_at     TEXT NOT NULL,          -- ISO 8601 UTC — parameter set
    archived_at     TEXT,                   -- NULL if active; set on scenario close
    FOREIGN KEY (actor_id) REFERENCES actor_profile(actor_id)
);

CREATE INDEX IF NOT EXISTS idx_scenario_active
    ON scenario_state (actor_id, scenario_id, archived_at);

-- ============================================================
-- TABLE: events
-- Append-only chronological record of all simulation state
-- changes. The atomic unit of history.
-- Every event has a recorded_at. No event is undated.
-- This table is the substrate for the behavioral model —
-- when this database becomes a read-only Simulator layer,
-- this table is the primary read target.
-- ============================================================

CREATE TABLE IF NOT EXISTS events (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    actor_id        TEXT NOT NULL,
    event_type      TEXT NOT NULL,          -- from terminology.md Layer 3 event_type values:
                                            -- venture_start | venture_complete |
                                            -- leg_start | leg_complete |
                                            -- interval_start | interval_complete |
                                            -- exchange_complete | session_abandoned |
                                            -- chapter_advance | journal_unlock |
                                            -- scenario_trigger | scenario_close |
                                            -- parameter_drift
    ref_id          TEXT,                   -- venture_id, leg_id, scenario_id, or NULL
    ref_type        TEXT,                   -- venture | leg | scenario | actor | NULL
    payload         TEXT,                   -- JSON — event-specific detail
    recorded_at     TEXT NOT NULL,          -- ISO 8601 UTC
    FOREIGN KEY (actor_id) REFERENCES actor_profile(actor_id)
);

CREATE INDEX IF NOT EXISTS idx_events_actor_time
    ON events (actor_id, recorded_at);

CREATE INDEX IF NOT EXISTS idx_events_type
    ON events (event_type, recorded_at);

-- ============================================================
-- TABLE: background_starting_values
-- Seed data — the canonical starting parameter values for
-- each of the six backgrounds, per parameter-registry.md.
-- Read at actor initialisation to populate actor_parameters.
-- Never modified after initial insert.
-- ============================================================

CREATE TABLE IF NOT EXISTS background_starting_values (
    background_id   TEXT NOT NULL,
    parameter_token TEXT NOT NULL,
    value_true      TEXT NOT NULL,
    value_perceived TEXT NOT NULL,
    confidence_tag  TEXT NOT NULL DEFAULT 'indicated',
    observable_level TEXT NOT NULL,
    notes           TEXT,
    PRIMARY KEY (background_id, parameter_token)
);

-- ============================================================
-- SEED DATA: background_starting_values
-- Source: parameter-registry.md, section 1 (Actor Parameters)
-- Ordinal bands: low | medium | high | distinguished | extensive
-- ============================================================

INSERT OR IGNORE INTO background_starting_values VALUES
-- FORMER LEGIONARY
('former_legionary', 'auctoritas',           'medium', 'medium', 'indicated', 'partial', 'Reliable but not distinguished'),
('former_legionary', 'clientela',            'low',    'low',    'indicated', 'partial', 'Military network, limited commercial reach'),
('former_legionary', 'liquiditas',           '200',    '200',    'measured',  'full',    'Denarii — modest savings'),
('former_legionary', 'fama',                 'neutral','neutral','indicated', 'partial', NULL),
('former_legionary', 'disciplina',           'high',   'high',   'indicated', 'full',    NULL),
('former_legionary', 'mercatus_scientia',    'low',    'low',    'indicated', 'full',    'Military logistics, not commercial markets'),
('former_legionary', 'itineris_scientia',    'high',   'high',   'indicated', 'full',    'Extensive road and route knowledge'),
('former_legionary', 'ius_accessus',         'medium', 'medium', 'indicated', 'partial', 'Citizen standing'),
('former_legionary', 'periculum_tolerantia', 'high',   'high',   'indicated', 'full',    NULL),
('former_legionary', 'negotiatio',           'low',    'low',    'indicated', 'full',    NULL),
('former_legionary', 'litterae',             'medium', 'medium', 'indicated', 'full',    NULL),
('former_legionary', 'officia_burden',       'low',    'low',    'indicated', 'partial', NULL),

-- FREEDMAN TRADER
('freedman_trader',  'auctoritas',           'low',    'low',    'indicated', 'partial', 'Practical reputation growing faster than social recognition'),
('freedman_trader',  'clientela',            'medium', 'medium', 'indicated', 'partial', 'Commercial network, active'),
('freedman_trader',  'liquiditas',           '350',    '350',    'measured',  'full',    'Denarii — working capital'),
('freedman_trader',  'fama',                 'neutral','neutral','indicated', 'partial', NULL),
('freedman_trader',  'disciplina',           'medium', 'medium', 'indicated', 'full',    NULL),
('freedman_trader',  'mercatus_scientia',    'high',   'high',   'indicated', 'full',    NULL),
('freedman_trader',  'itineris_scientia',    'medium', 'medium', 'indicated', 'full',    NULL),
('freedman_trader',  'ius_accessus',         'low',    'low',    'indicated', 'partial', 'Freedman limits on contract enforceability'),
('freedman_trader',  'periculum_tolerantia', 'medium', 'medium', 'indicated', 'full',    NULL),
('freedman_trader',  'negotiatio',           'high',   'high',   'indicated', 'full',    NULL),
('freedman_trader',  'litterae',             'high',   'high',   'indicated', 'full',    NULL),
('freedman_trader',  'officia_burden',       'low',    'low',    'indicated', 'partial', NULL),

-- NOBLE YOUNGER SON
('noble_younger_son','auctoritas',           'high',   'high',   'indicated', 'partial', NULL),
('noble_younger_son','clientela',            'high',   'high',   'indicated', 'partial', 'Inherited network, not personally built'),
('noble_younger_son','liquiditas',           '150',    '150',    'measured',  'full',    'Denarii — constrained by elder sibling priority'),
('noble_younger_son','fama',                 'good',   'good',   'indicated', 'partial', NULL),
('noble_younger_son','disciplina',           'low',    'low',    'indicated', 'full',    NULL),
('noble_younger_son','mercatus_scientia',    'low',    'low',    'indicated', 'full',    NULL),
('noble_younger_son','itineris_scientia',    'low',    'low',    'indicated', 'full',    NULL),
('noble_younger_son','ius_accessus',         'high',   'high',   'indicated', 'partial', NULL),
('noble_younger_son','periculum_tolerantia', 'low',    'low',    'indicated', 'full',    NULL),
('noble_younger_son','negotiatio',           'medium', 'medium', 'indicated', 'full',    NULL),
('noble_younger_son','litterae',             'high',   'high',   'indicated', 'full',    NULL),
('noble_younger_son','officia_burden',       'high',   'medium', 'indicated', 'partial', 'Underestimates informal obligations'),

-- FAILED MAGISTRATE
('failed_magistrate','auctoritas',           'low',    'medium', 'indicated', 'partial', 'True value lower than perceived — falling'),
('failed_magistrate','clientela',            'medium', 'medium', 'indicated', 'partial', 'Legal contacts, politically exposed'),
('failed_magistrate','liquiditas',           '100',    '100',    'measured',  'full',    'Denarii — depleted by failed campaign'),
('failed_magistrate','fama',                 'mixed',  'mixed',  'indicated', 'partial', NULL),
('failed_magistrate','disciplina',           'medium', 'medium', 'indicated', 'full',    NULL),
('failed_magistrate','mercatus_scientia',    'low',    'low',    'indicated', 'full',    NULL),
('failed_magistrate','itineris_scientia',    'low',    'low',    'indicated', 'full',    NULL),
('failed_magistrate','ius_accessus',         'high',   'high',   'indicated', 'partial', 'Formal standing intact, practical access eroding'),
('failed_magistrate','periculum_tolerantia', 'low',    'low',    'indicated', 'full',    NULL),
('failed_magistrate','negotiatio',           'medium', 'medium', 'indicated', 'full',    NULL),
('failed_magistrate','litterae',             'high',   'high',   'indicated', 'full',    NULL),
('failed_magistrate','officia_burden',       'high',   'high',   'indicated', 'partial', NULL),

-- CAMP LOGISTICIAN
('camp_logistician', 'auctoritas',           'low',    'low',    'indicated', 'partial', NULL),
('camp_logistician', 'clientela',            'low',    'low',    'indicated', 'partial', 'Supply chain contacts, narrow'),
('camp_logistician', 'liquiditas',           '180',    '180',    'measured',  'full',    'Denarii — steady savings'),
('camp_logistician', 'fama',                 'neutral','neutral','indicated', 'partial', NULL),
('camp_logistician', 'disciplina',           'high',   'high',   'indicated', 'full',    NULL),
('camp_logistician', 'mercatus_scientia',    'high',   'high',   'indicated', 'full',    'Bulk goods, logistics pricing'),
('camp_logistician', 'itineris_scientia',    'high',   'high',   'indicated', 'full',    NULL),
('camp_logistician', 'ius_accessus',         'medium', 'medium', 'indicated', 'partial', NULL),
('camp_logistician', 'periculum_tolerantia', 'medium', 'medium', 'indicated', 'full',    NULL),
('camp_logistician', 'negotiatio',           'medium', 'medium', 'indicated', 'full',    NULL),
('camp_logistician', 'litterae',             'medium', 'medium', 'indicated', 'full',    NULL),
('camp_logistician', 'officia_burden',       'low',    'low',    'indicated', 'partial', NULL),

-- GUILD SCRIBE
('guild_scribe',     'auctoritas',           'low',    'low',    'indicated', 'partial', NULL),
('guild_scribe',     'clientela',            'low',    'low',    'indicated', 'partial', 'Document and account network'),
('guild_scribe',     'liquiditas',           '120',    '120',    'measured',  'full',    'Denarii — modest, careful'),
('guild_scribe',     'fama',                 'neutral','neutral','indicated', 'partial', NULL),
('guild_scribe',     'disciplina',           'medium', 'medium', 'indicated', 'full',    NULL),
('guild_scribe',     'mercatus_scientia',    'medium', 'medium', 'indicated', 'full',    'Account knowledge, not market instinct'),
('guild_scribe',     'itineris_scientia',    'low',    'low',    'indicated', 'full',    NULL),
('guild_scribe',     'ius_accessus',         'medium', 'medium', 'indicated', 'partial', 'Document access, limited enforcement'),
('guild_scribe',     'periculum_tolerantia', 'low',    'low',    'indicated', 'full',    NULL),
('guild_scribe',     'negotiatio',           'low',    'low',    'indicated', 'full',    NULL),
('guild_scribe',     'litterae',             'high',   'high',   'indicated', 'full',    NULL),
('guild_scribe',     'officia_burden',       'low',    'low',    'indicated', 'partial', NULL);

-- ============================================================
-- TABLE: schema_changelog
-- Documents schema version history.
-- Required: if this database becomes a read-only Simulator
-- layer, the Simulator must be able to identify schema version
-- and adapt its reader accordingly.
-- ============================================================

CREATE TABLE IF NOT EXISTS schema_changelog (
    version         INTEGER PRIMARY KEY,
    description     TEXT NOT NULL,
    applied_at      TEXT NOT NULL           -- ISO 8601 UTC
);

INSERT OR IGNORE INTO schema_changelog VALUES
(1, 'OTIVM-I: JSON save files (not SQLite — recorded here for continuity)', '2026-04-25T00:00:00Z'),
(2, 'OTIVM-II: JSON save files with TESSERA map integration',               '2026-04-28T00:00:00Z'),
(3, 'OTIVM-III: Per-player SQLite schema, parameter registry v1',           '2026-05-02T00:00:00Z');

-- ============================================================
-- END OF SCHEMA
-- To initialise a player database:
--   sqlite3 data/saves/{actor_id}.sqlite3 < data/create_player_db.sql
--
-- To verify integrity after creation:
--   sqlite3 data/saves/{actor_id}.sqlite3 "PRAGMA integrity_check;"
--   sqlite3 data/saves/{actor_id}.sqlite3 "SELECT COUNT(*) FROM background_starting_values;"
--   Expected: 72 rows (12 parameters × 6 backgrounds)
-- ============================================================
