From 2e611ff78e52108a4e0e3f0bb18e71c16bce87a5 Mon Sep 17 00:00:00 2001 From: otivm Date: Sat, 2 May 2026 21:20:27 +0000 Subject: [PATCH] schema: add session_anchor_at to actor_profile for simulation clock --- data/create_player_db.sql | 430 ++++++++++++++++++++------------------ 1 file changed, 228 insertions(+), 202 deletions(-) diff --git a/data/create_player_db.sql b/data/create_player_db.sql index ee25259..0c61909 100644 --- a/data/create_player_db.sql +++ b/data/create_player_db.sql @@ -1,6 +1,6 @@ -- OTIVM Per-Player Database Schema -- File: data/create_player_db.sql --- Version: OTIVM-III +-- Version: OTIVM-IV (schema version 4) -- 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) @@ -11,6 +11,12 @@ -- value_true and value_perceived are always separate columns -- confidence_tag is always a first-class column, never a comment -- +-- Simulation clock: see docs/architecture/simulation-clock.md +-- session_anchor_at is the real UTC timestamp at which the player's +-- simulation clock started (set at first dispatch, NULL until then). +-- All simulated date arithmetic uses this field as the epoch anchor +-- per the integer constraint documented in simulation-clock.md. +-- -- 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 @@ -21,31 +27,39 @@ PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; -PRAGMA user_version = 3; -- OTIVM-III schema version +PRAGMA user_version = 4; -- OTIVM-IV 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). --- ============================================================ +-- in OTIVM-IV but the structure supports it). +-- +-- session_anchor_at: NULL until first dispatch. Set once at the +-- moment the player fires their first venture. All simulation +-- clock arithmetic is relative to this timestamp. +-- See docs/architecture/simulation-clock.md Section 7. +-- ================================================================ 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 + 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 4, + recorded_at TEXT NOT NULL, -- ISO 8601 UTC — row creation time + session_anchor_at TEXT, -- ISO 8601 UTC — first dispatch time + -- NULL until player fires first venture + -- Never updated after it is set PRIMARY KEY (actor_id, recorded_at) ); --- ============================================================ +-- ================================================================ -- TABLE: actor_parameters -- The core parameter table. -- One row per parameter per actor per recorded moment. @@ -68,23 +82,23 @@ CREATE TABLE IF NOT EXISTS actor_profile ( -- 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 + 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) ); @@ -92,121 +106,127 @@ CREATE TABLE IF NOT EXISTS actor_parameters ( 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 + 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 + 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. --- ============================================================ +-- +-- duration_days: integer simulated days per simulation-clock.md. +-- Must be a positive integer. Never a decimal. +-- Derived from duration_ms / MS_PER_SIM_DAY at write time. +-- See docs/architecture/simulation-clock.md Section 2. +-- ================================================================ 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, + 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 INTEGER, -- simulated days (INTEGER, never REAL) + -- NULL until leg completes + 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 INTEGER, -- deviation from expected duration (INTEGER) + 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 + 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. @@ -214,23 +234,23 @@ CREATE INDEX IF NOT EXISTS idx_scenario_active -- 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 + 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) ); @@ -240,136 +260,137 @@ CREATE INDEX IF NOT EXISTS idx_events_actor_time 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, + 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), +('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), +('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'), +('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), +('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), +('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); +('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 + 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'); +(3, 'OTIVM-III: Per-player SQLite schema, parameter registry v1', '2026-05-02T00:00:00Z'), +(4, 'OTIVM-IV: Add session_anchor_at to actor_profile for simulation clock','2026-05-02T00:00:00Z'); --- ============================================================ +-- ================================================================ -- END OF SCHEMA -- To initialise a player database: -- sqlite3 data/saves/{actor_id}.sqlite3 < data/create_player_db.sql @@ -378,4 +399,9 @@ INSERT OR IGNORE INTO schema_changelog VALUES -- 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) --- ============================================================ +-- +-- Note on venture_legs.duration_days and delay_days: +-- Declared INTEGER, not REAL. This enforces the integer clock +-- constraint from docs/architecture/simulation-clock.md. +-- A fractional value here is a schema violation. +-- ================================================================