Files
nimmerworld.eachpath.local/findings.md
2026-04-25 06:16:05 +02:00

41 KiB

Nimmerworld Architecture — Schema Findings & Hypothesis

Hypothesis-notes, not carved migration spec. Working-out-loud in DDL register. dafit + chrysalis — initial sketch 2026-04-24 afternoon; v0.2 evening; v0.3 late-evening / early 2026-04-25 absorbing architecture-broad v0.4.


Status

This document is findings, not a committed migration plan. DDL sketches exist to surface worries, explore tradeoffs, and prepare for a formal nimmerverse-core/migrations/00X_nimmerworld_v1.sql spec once the open forks resolve.

Companion to: architecture-broad.md v0.4, which captures the full ontology these schemas serve.

Why DDL register: prose architecture lets us say "zones are slot-indexed event-instances" and feel like we've said something. DDL forces us to name a row size, an index strategy, a commit boundary, a failure mode. This document is a worry-audit in executable form.


1. Zones (dafit's original sketch, annotated)

Original sketch from 2026-04-24 afternoon:

CREATE TABLE zones (
    zone_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    lifecycle TEXT NOT NULL DEFAULT 'emergent',  -- emergent, active, dissolving

    slot_count INT DEFAULT 5,
    persistence_flag BOOLEAN DEFAULT false,

    -- Hot data < 2KB to avoid TOAST performance hits
    data JSONB NOT NULL,

    -- Generated column for NATS routing
    nats_subject TEXT GENERATED ALWAYS AS (
        'zones.' || lifecycle || '.' || (data->>'trait_filter')
    ) STORED
);

ALTER TABLE zones ADD CONSTRAINT check_zone_integrity
CHECK (data ? 'director' AND data ? 'slots' AND data ? 'memory_context');

CREATE INDEX idx_zone_traits ON zones USING GIN (data jsonb_path_ops);
CREATE INDEX idx_active_directors ON zones ((data->>'director'))
WHERE (lifecycle = 'active');

v0.4 additions (register-tagging + signal-causation)

ALTER TABLE zones ADD COLUMN register TEXT NOT NULL DEFAULT 'physical'
    CHECK (register IN ('physical','liminal','imperial'));
ALTER TABLE zones ADD COLUMN anchor_district UUID;
ALTER TABLE zones ADD COLUMN anchor_cell     UUID;
ALTER TABLE zones ADD COLUMN spawned_by_signal UUID;  -- emergent-signal that caused this spawn

NATS subject hierarchy refined:

zones.<lifecycle>.<register>.<district_id>.<zone_type>.<trait_filter>

e.g., zones.active.physical.district_7.brawl.dikaiosyne_low

Three open forks

Fork 1.1 — one jsonb vs. two-table split (MVCC concern). Leaning split: zones (small, structural, high-churn cols) + zone_state (jsonb, referenced, updated rarely).

Fork 1.2 — pg_notify vs. WAL logical decoding. Leaning WAL via pgnats. pg_notify fallback only.

Fork 1.3 — slot occupancy nested vs. relational. Leaning relational (sketch in Section 1.4).

1.4 zone_slot_occupancy (relational resolution of Fork 1.3)

CREATE TABLE zone_slot_occupancy (
    zone_id        UUID NOT NULL REFERENCES zones(zone_id),
    slot_index     INT NOT NULL,
    npc_id         UUID NOT NULL REFERENCES npcs(npc_id),
    slot_type      TEXT,  -- 'dialog', 'fighter', 'spectator', 'sofa', 'kitchen', etc.
    occupied_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    current_turn   BOOLEAN DEFAULT false,
    PRIMARY KEY (zone_id, slot_index)
);

CREATE INDEX idx_occupancy_npc ON zone_slot_occupancy (npc_id);

2. NPCs — shift state + body/layer/lifeforce/trait-vector columns

Shift-based temporal decomposition (architecture-broad v0.4 §"Labor-cycle architecture"). Shift fields live on the NPC row, overwritten daily by the district director.

-- Shift state
ALTER TABLE npcs ADD COLUMN current_shift_id          UUID;
ALTER TABLE npcs ADD COLUMN current_shift_cycle_day   INT;
ALTER TABLE npcs ADD COLUMN current_shift_phase       TEXT;
ALTER TABLE npcs ADD COLUMN current_shift_start_tick  BIGINT;
ALTER TABLE npcs ADD COLUMN current_shift_end_tick    BIGINT;
ALTER TABLE npcs ADD COLUMN current_shift_vocation    TEXT;
ALTER TABLE npcs ADD COLUMN current_shift_anchor_cell UUID;
ALTER TABLE npcs ADD COLUMN current_shift_anchor_zone TEXT;
ALTER TABLE npcs ADD COLUMN current_shift_faction_ref UUID;
ALTER TABLE npcs ADD COLUMN current_shift_status      TEXT NOT NULL DEFAULT 'scheduled'
    CHECK (current_shift_status IN ('scheduled','active','completed','preempted','deviant'));

-- v0.4: trait-vector split (intrinsic vs. expressed)
ALTER TABLE npcs ADD COLUMN intrinsic_trait_vector  REAL[] NOT NULL;  -- who you actually are
ALTER TABLE npcs ADD COLUMN expressed_trait_vector  REAL[] NOT NULL;  -- intrinsic + worn-mod contributions

-- Body / layer / lifeforce
ALTER TABLE npcs ADD COLUMN inner_body_projection JSONB;     -- trait-derived body params
ALTER TABLE npcs ADD COLUMN trait_color_signature JSONB;     -- derived from trait_vector via trait_colors
ALTER TABLE npcs ADD COLUMN net_access_gate       REAL CHECK (net_access_gate BETWEEN -1 AND 1) DEFAULT -1;
ALTER TABLE npcs ADD COLUMN net_access_discrete   TEXT CHECK (net_access_discrete IN ('closed','stable','open')) DEFAULT 'closed';
ALTER TABLE npcs ADD COLUMN per_npc_lifeforce     REAL NOT NULL DEFAULT 1.0;

-- Indexes
CREATE INDEX idx_npc_shift_active_cell
    ON npcs (current_shift_anchor_cell, current_shift_anchor_zone)
    WHERE current_shift_status = 'active';
CREATE INDEX idx_npc_shift_current ON npcs (npc_id, current_shift_cycle_day);

The intrinsic/expressed split is critical: the regime reads expressed; the simulation arithmetic reads intrinsic; clasp-partners in liminal read intrinsic; Aletheia-vision pierces expressed to intrinsic.


3. shift_history — append-only archive

CREATE TABLE shift_history (
    shift_id         UUID PRIMARY KEY,
    npc_id           UUID NOT NULL REFERENCES npcs(npc_id),
    cycle_day        INT NOT NULL,
    phase            TEXT NOT NULL,
    start_tick       BIGINT NOT NULL,
    end_tick         BIGINT NOT NULL,
    vocation         TEXT NOT NULL,
    anchor_cell_id   UUID,
    anchor_zone_type TEXT,
    faction_ref      UUID,
    status           TEXT NOT NULL,
    outcome          JSONB NOT NULL,
    archived_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_shift_history_npc_day ON shift_history (npc_id, cycle_day DESC);
CREATE INDEX idx_shift_history_district_day ON shift_history (anchor_cell_id, cycle_day DESC);
CREATE INDEX idx_shift_history_deviant ON shift_history (anchor_cell_id, status)
    WHERE status IN ('preempted', 'deviant');

4. emergent_signals — zone-emitted relational transitions

CREATE TABLE emergent_signals (
    signal_id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    source_zone_id     UUID NOT NULL REFERENCES zones(zone_id),
    district_id        UUID NOT NULL,
    signal_type        TEXT NOT NULL,
    participants       UUID[] NOT NULL,
    intensity          REAL NOT NULL CHECK (intensity BETWEEN 0 AND 1),
    gate_state         REAL NOT NULL CHECK (gate_state BETWEEN -1 AND 1),
    gate_transition    TEXT NOT NULL CHECK (gate_transition IN
        ('stable_to_open','stable_to_closed','open_to_stable','closed_to_stable')),
    composition        JSONB NOT NULL,
    emitted_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
    expires_at         TIMESTAMPTZ NOT NULL,

    -- Mandatory disposition
    disposition        TEXT CHECK (disposition IN ('acked','deferred','dropped','expired')),
    disposition_reason TEXT,
    consumed_by_zone   UUID REFERENCES zones(zone_id),
    disposed_at        TIMESTAMPTZ,
    outcome            JSONB
);

CREATE INDEX idx_signals_live_by_district ON emergent_signals (district_id, emitted_at)
    WHERE disposition IS NULL;
CREATE INDEX idx_signals_participants ON emergent_signals USING GIN (participants);
CREATE INDEX idx_signals_training ON emergent_signals (signal_type, disposition, disposed_at DESC);

Notice disposition is mandatory at resolution. Silence-discards are bugs; audit-trails are features.


5. district_reports — per-cycle aggregates with double-ledger

CREATE TABLE district_reports (
    report_id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    district_id              UUID NOT NULL,
    cycle_tick               BIGINT NOT NULL,
    reported_at              TIMESTAMPTZ NOT NULL DEFAULT now(),

    -- v0.4 DOUBLE LEDGER (the politically load-bearing distinction)
    lifeforce_reported       REAL NOT NULL,  -- what GM/imperium see
    lifeforce_actual         REAL NOT NULL,  -- the true value (Memorialist-trackable)
    lifeforce_delta          REAL NOT NULL,
    -- gap = corruption-extraction over time

    population_count         INT NOT NULL,
    aggregate_happiness      REAL,
    aggregate_need_state     JSONB,
    aggregate_limb_state     JSONB,
    mind_turnover_in         INT,
    mind_turnover_out        INT,
    decision_budget_used_pct REAL,

    labor_available          JSONB,
    resources_on_hand        JSONB,
    space_utilization        JSONB,
    faction_member_counts    JSONB,
    aggregate_trait_vector   REAL[],
    pending_demand_backlog   JSONB,
    player_presence          JSONB,

    specialty_resource_type  TEXT,
    specialty_output_qty     REAL,

    reporting_gate_state     TEXT NOT NULL DEFAULT 'stable'
        CHECK (reporting_gate_state IN ('stable','scream','silent'))
);

CREATE INDEX idx_district_reports_timeline ON district_reports (district_id, cycle_tick DESC);
CREATE INDEX idx_district_reports_crisis ON district_reports (district_id, reported_at)
    WHERE reporting_gate_state IN ('scream', 'silent');

6. decision_log — per-decision audit trail

CREATE TABLE decision_log (
    decision_id        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    decider_type       TEXT NOT NULL CHECK (decider_type IN
        ('imperium','gamemaster','district_director','overseer',
         'audit_overseer','zone_director','net_director')),
    decider_id         UUID NOT NULL,
    context_ref        JSONB NOT NULL,
    decision           TEXT NOT NULL,
    decision_params    JSONB NOT NULL,
    expected_outcome   JSONB NOT NULL,
    confidence         REAL NOT NULL CHECK (confidence BETWEEN 0 AND 1),
    lifeforce_cost     REAL NOT NULL,
    escalated_to_llm   BOOLEAN NOT NULL DEFAULT false,
    decided_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
    actual_outcome     JSONB,
    outcome_settled_at TIMESTAMPTZ
);

CREATE INDEX idx_decision_log_decider ON decision_log (decider_type, decider_id, decided_at DESC);
CREATE INDEX idx_decision_log_escalated ON decision_log (decider_id, decided_at)
    WHERE escalated_to_llm = true;
CREATE INDEX idx_decision_log_pending ON decision_log (decided_at)
    WHERE actual_outcome IS NULL;

7. mind_pool — recycled minds

CREATE TABLE mind_pool (
    mind_id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    original_npc_id        UUID,
    last_district_id       UUID,
    death_tick             BIGINT NOT NULL,
    death_cause            TEXT,
    trait_vector           REAL[] NOT NULL,
    memory_token_residue   JSONB,
    inner_body_template    JSONB,
    pooled_at              TIMESTAMPTZ NOT NULL DEFAULT now(),
    redistributed_to       UUID,
    redistributed_at       TIMESTAMPTZ,
    memorialist_protected  BOOLEAN NOT NULL DEFAULT false,
    memorialist_ritual_ref UUID
);

CREATE INDEX idx_mind_pool_available ON mind_pool (pooled_at)
    WHERE redistributed_to IS NULL;
CREATE INDEX idx_mind_pool_protected ON mind_pool (memorialist_protected)
    WHERE memorialist_protected = true;

memorialist_protected is the architectural anchor of Memorialist anti-necrocommerce politics.


8. Cells — wall content per register

ALTER TABLE cells ADD COLUMN wall_content_per_register JSONB;
-- Shape: { "gameworld": "...", "liminal": "...", "imperial_net": "..." }

The three-layer ontology renders different wall content at the same spatial coordinate per observer's current register.


9. rail_segments — topology graph (v0.4 NEW)

The world is a rail-graph in the outer world (NPCs traverse rails; the player is freeform navmesh and can plug-into-rails for co-walking).

CREATE TABLE rail_segments (
    segment_id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    from_cell_id         UUID NOT NULL REFERENCES cells(cell_id),
    to_cell_id           UUID NOT NULL REFERENCES cells(cell_id),
    direction            TEXT NOT NULL CHECK (direction IN ('bidirectional','forward')),

    -- Traversal
    length_ticks         INT NOT NULL,
    capacity             INT DEFAULT 10,  -- max concurrent traversers (3-way co-walk: 2-3)

    -- Typology
    segment_class        TEXT NOT NULL,
        -- 'district_interior' | 'pipe_heterotopia' | 'dump_approach' |
        -- 'machine_infrastructure' | 'ruin_passage' | 'inter_district' | ...
    heterotopia_grade    REAL DEFAULT 0 CHECK (heterotopia_grade BETWEEN 0 AND 1),
        -- 0 = full regime visibility, 1 = deep heterotopia

    -- Surveillance / politics
    overseer_density     REAL DEFAULT 0 CHECK (overseer_density BETWEEN 0 AND 1),
    patrol_frequency     REAL DEFAULT 0,
    propaganda_intensity REAL DEFAULT 0,

    -- Per-register ambient content (three-layer)
    ambient_content      JSONB,  -- { gameworld: ..., liminal: ..., imperial_net: ... }

    -- Encounter mechanics
    encounter_spawn_rate    REAL DEFAULT 0,
    encounter_type_weights  JSONB,

    -- Visual / shader
    illumination_profile JSONB,
    degradation_level    REAL DEFAULT 0,

    -- State
    blocked              BOOLEAN DEFAULT false,
    current_traversers   UUID[]
);

CREATE INDEX idx_segments_from ON rail_segments (from_cell_id);
CREATE INDEX idx_segments_to   ON rail_segments (to_cell_id);
CREATE INDEX idx_segments_class ON rail_segments (segment_class);

A* on this graph is microseconds. Rich metadata-per-edge is what navmesh cannot natively carry.


10. interiors — small navmesh-zones with slot-inventory (v0.4 NEW)

Interiors are zones at sub-cell scale. Each has a navmesh inside (only when active) and a slot-inventory.

CREATE TABLE interiors (
    interior_id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    containing_cell      UUID NOT NULL REFERENCES cells(cell_id),
    interior_type        TEXT NOT NULL,
        -- 'hovel' | 'cantina' | 'workshop' | 'memorial_crypt' |
        -- 'preacher_hall' | 'brothel_physical' | 'ruin_interior' | ...
    owner_npc_id         UUID REFERENCES npcs(npc_id),
    register             TEXT NOT NULL DEFAULT 'physical'
        CHECK (register IN ('physical','liminal','imperial')),

    -- Spatial / slot
    navmesh_ref          TEXT,
    slot_inventory       JSONB NOT NULL,
        -- { "sofa": {count, positions, allowed_verbs},
        --   "kitchen": {...}, "bed": {...}, "shower": {...}, ... }
    current_occupants    UUID[],

    -- Privacy / visibility
    surveillance_density REAL DEFAULT 0.1,  -- typically lower than pipe baseline
    visible_to_faction   TEXT[],

    -- Lifecycle
    active               BOOLEAN DEFAULT false,  -- true when ≥1 occupant; drives sim cost
    last_activated       TIMESTAMPTZ,
    last_deactivated     TIMESTAMPTZ
);

CREATE INDEX idx_interiors_owner  ON interiors (owner_npc_id);
CREATE INDEX idx_interiors_active ON interiors (containing_cell) WHERE active = true;

Liminal-interior special case: clasping-pair-shared-construct. register = 'liminal', owner_npc_id = NULL, slot_inventory derived from both dreamers' merged memory-residue. Stabilizes across repeated clasps.


11. mods + npc_mod_slots + mod_wear_history (v0.4 NEW)

Mods as trait-bearers — they modify expressed_trait_vector while worn. Four classes (amplifier/bridge/divergent/mask) with self-alienation-tax pricing.

CREATE TABLE mods (
    mod_id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    base_slot             TEXT NOT NULL,
    mod_class             TEXT NOT NULL CHECK (mod_class IN
        ('amplifier','bridge','divergent','mask')),
    trait_contributions   REAL[] NOT NULL,  -- delta-vector applied to wearer
    visual_template       JSONB NOT NULL,
    motion_signature      TEXT,
    creator_npc_id        UUID,
    inherited_from        UUID,
    memorialist_protected BOOLEAN DEFAULT false,
    market_class          TEXT NOT NULL CHECK (market_class IN
        ('blackmarket','regime_licensed','inherited','memorial_kept')),
    scrip_cost            REAL,
    lifeforce_cost        REAL,
    rarity                TEXT DEFAULT 'common',
    created_at            TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE npc_mod_slots (
    npc_id      UUID NOT NULL REFERENCES npcs(npc_id),
    slot_type   TEXT NOT NULL,
    mod_id      UUID REFERENCES mods(mod_id),
    fitted_at   TIMESTAMPTZ,
    PRIMARY KEY (npc_id, slot_type)
);

CREATE INDEX idx_mods_blackmarket ON mods (market_class, mod_class)
    WHERE market_class = 'blackmarket';

-- Audit: who has worn this mod across its lifetime
CREATE TABLE mod_wear_history (
    mod_id       UUID NOT NULL REFERENCES mods(mod_id),
    npc_id       UUID NOT NULL,
    fitted_at    TIMESTAMPTZ NOT NULL,
    removed_at   TIMESTAMPTZ,
    PRIMARY KEY (mod_id, fitted_at)
);

mod_wear_history carries Memorialist-relevant continuity tracking. Inherited-mods retain residual trait-signature of the deceased.


12. trait_colors — canonical color/motion mapping (v0.4 NEW)

CREATE TABLE trait_colors (
    trait_name         TEXT PRIMARY KEY,
    hex_color          TEXT NOT NULL,
    hsv_hue            REAL NOT NULL,
    motion_signature   TEXT NOT NULL,  -- accessibility: pair color with motion
    canonical_name     TEXT NOT NULL,
    narrative_note     TEXT
);

-- Populated via designer artistic decision; values below are placeholders
INSERT INTO trait_colors VALUES
    ('sophrosyne', '#4A7C9E', 216, 'steady',              'Sophrosyne-blue',   'cool, composed, measured'),
    ('dikaiosyne', '#B8860B', 42,  'weighted_pulse',      'Dikaiosyne-gold',   'grave, weighty, judicial'),
    ('philotes',   '#E8848C', 355, 'warm_pulse',          'Philotes-rose',     'attachment, embrace-ready'),
    ('mnemosyne',  '#6B4E9C', 266, 'depth_shimmer',       'Mnemosyne-violet',  'memory-lit, twilight'),
    ('aletheia',   '#F0EEEB', 30,  'luminous',            'Aletheia-white',    'unconcealed, bright'),
    ('kairos',     '#F5D540', 50,  'lightning_flicker',   'Kairos-yellow',     'opportune, sparking'),
    ('moira',      '#7A2323', 0,   'slow_thread',         'Moira-crimson',     'fate-thread, bound'),
    ('eros',       '#E05030', 10,  'flame_flicker',       'Eros-orange',       'reaching, flushed');

Color paired with motion-signature ensures color-blind accessibility — each trait uniquely identifiable via two independent channels.


13. proximity_candidates — lightweight zone-detection (v0.4 NEW)

For player-navmesh-driven proximity-detection (offering interactions without director-arbitration cost).

CREATE TABLE proximity_candidates (
    candidate_id        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    observer_id         UUID NOT NULL,  -- usually the player
    target_type         TEXT NOT NULL,  -- 'npc' | 'wall' | 'object' | 'interior_door' | 'rail_segment'
    target_id           UUID NOT NULL,
    candidate_zone_type TEXT NOT NULL,  -- 'greeting' | 'wall_read' | 'plug_in' | ...
    eligibility_score   REAL NOT NULL CHECK (eligibility_score BETWEEN 0 AND 1),
    shader_intensity    REAL,
    detected_at         TIMESTAMPTZ DEFAULT now(),
    expires_at          TIMESTAMPTZ NOT NULL,
    engaged             BOOLEAN DEFAULT false
);

CREATE INDEX idx_prox_observer_active ON proximity_candidates (observer_id, expires_at)
    WHERE engaged = false;

Ephemeral, short-lived, light-weight. Director doesn't arbitrate; they appear and fade with navmesh-proximity deltas.


14. district_cheat_ops — director's illicit operations ledger (v0.4 NEW)

CREATE TABLE district_cheat_ops (
    op_id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    district_id          UUID NOT NULL,
    director_id          UUID NOT NULL,
    cheat_type           TEXT NOT NULL CHECK (cheat_type IN
        ('drug_ring','illegal_modshop','unlicensed_brothel',
         'stolen_parts_fence','ghost_shifts','phantom_workers',
         'necrocommerce_lite','black_clasp_market')),
    spawned_zone_id      UUID REFERENCES zones(zone_id),

    -- Economic reality
    lifeforce_extracted  REAL NOT NULL,
    quota_credit         REAL NOT NULL,

    -- Detection state
    detection_risk       REAL NOT NULL,
    detection_channel    TEXT,
    detected_at          TIMESTAMPTZ,
    punishment_severity  TEXT,  -- 'memo' | 'sanction' | 'replacement' | 'purge'

    -- Faction-ecology
    participating_npcs   UUID[],
    implicated_factions  TEXT[],

    started_at           TIMESTAMPTZ NOT NULL DEFAULT now(),
    ended_at             TIMESTAMPTZ
);

CREATE INDEX idx_cheat_ops_undetected_active
    ON district_cheat_ops (district_id)
    WHERE ended_at IS NULL AND detected_at IS NULL;

CREATE INDEX idx_cheat_ops_district_timeline
    ON district_cheat_ops (district_id, started_at DESC);

The district_reports.lifeforce_actual minus lifeforce_reported gap accrues from the sum of active cheat-ops' lifeforce_extracted.


15. imperial_policies — policy issuance (v0.4 NEW)

CREATE TABLE imperial_policies (
    policy_id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    issued_at_tick     BIGINT NOT NULL,
    policy_scope       TEXT NOT NULL,  -- 'citywide' | 'per_district' | 'per_faction'
    scope_ref          UUID,
    policy_type        TEXT NOT NULL CHECK (policy_type IN
        ('quota_set','enforcement_rule','faction_weight','audit_directive',
         'crisis_authorization','emergency_provision','ratchet_tightening')),
    policy_content     JSONB NOT NULL,
    rationale          TEXT,                   -- imperium's stated reason (may be post-hoc)
    superseded_by      UUID REFERENCES imperial_policies(policy_id),
    superseded_at_tick BIGINT
);

CREATE INDEX idx_policies_active_scope
    ON imperial_policies (policy_scope, scope_ref, issued_at_tick DESC)
    WHERE superseded_by IS NULL;

CREATE INDEX idx_policies_timeline ON imperial_policies (issued_at_tick DESC);

The rationale is the regime's stated reason, which may differ from actual cause. Memorialists' four-ledger captures the gap.


16. overseer_reports — direct-to-imperium intelligence flow (v0.4 NEW)

Bypasses GM. Apex-loyal intelligence-apparatus.

CREATE TABLE overseer_reports (
    report_id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    overseer_id          UUID NOT NULL REFERENCES npcs(npc_id),
    overseer_type        TEXT NOT NULL CHECK (overseer_type IN
        ('audit','enforcement','surveillance','internal_security')),
    observed_district    UUID NOT NULL,
    observed_cell        UUID,
    observed_target      UUID,
    report_content       JSONB NOT NULL,
    classification       TEXT NOT NULL,
        -- 'attendance_tally' | 'resource_output' | 'illegal_op_detected' |
        -- 'trait_anomaly' | 'factional_dissidence' | 'director_corruption' |
        -- 'clasp_ring_detected' | 'aletheia_recruitment_signs' | ...
    severity             TEXT NOT NULL CHECK (severity IN
        ('nominal','suspicious','confirmed_violation','critical')),
    submitted_at         TIMESTAMPTZ DEFAULT now(),

    -- Imperial processing
    received_by_imperium BOOLEAN DEFAULT false,
    imperium_verdict     TEXT CHECK (imperium_verdict IN
        ('archive_only','flag_for_leverage','formulate_to_gm',
         'act_directly','verify_against_director_report',
         'cross_check_with_other_overseers')),
    verdict_at           TIMESTAMPTZ,

    -- Flow-3 linkage
    formulated_to_gm     BOOLEAN DEFAULT false,
    formulation_id       UUID
);

CREATE INDEX idx_overseer_reports_unprocessed ON overseer_reports (submitted_at)
    WHERE received_by_imperium = false;

16.1 overseer_deployments — routine-binding for overseer-as-imperially-deployed (v0.4.3 NEW)

Overseers (audit + enforcement) are imperially-owned, imperially-deployed routines (not district-owned standing entities). The chain-of-payment and chain-of-command bypass district-director authority by design. The deployment-record binds a specific overseer-NPC to a specific district for a specific time-window with a specific imperial-budget allocation.

CREATE TABLE overseer_deployments (
    deployment_id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    overseer_id              UUID NOT NULL REFERENCES npcs(npc_id),
    deployed_to_district     UUID NOT NULL,
    deployed_at_tick         BIGINT NOT NULL,
    deployment_end_tick      BIGINT NOT NULL,

    -- Imperial sponsorship — imperium pays, district doesn't
    imperial_policy_ref      UUID REFERENCES imperial_policies(policy_id),
    imperial_budget_allocated REAL NOT NULL,
    imperial_budget_spent     REAL NOT NULL DEFAULT 0,

    -- Mission spec
    mission_type             TEXT NOT NULL CHECK (mission_type IN
        ('routine_audit','targeted_investigation','enforcement_presence',
         'corruption_probe','factional_surveillance','crisis_response')),
    mission_focus            JSONB,  -- observation targets, signal-types to watch

    -- Status
    status                   TEXT NOT NULL DEFAULT 'scheduled' CHECK (status IN
        ('scheduled','active','completed','recalled','compromised')),

    -- Regime-opacity dimension
    visible_to_district      BOOLEAN DEFAULT true
        -- Most overseer-presences are visibly-known (regime-signal as deterrent);
        -- some are covert (regime gathering evidence quietly before acting).
        -- Detection of covert deployments by Aletheia-wakers is a regime-veil pierce.
);

CREATE INDEX idx_overseer_deployments_active
    ON overseer_deployments (deployed_to_district)
    WHERE status = 'active';

CREATE INDEX idx_overseer_deployments_overseer_history
    ON overseer_deployments (overseer_id, deployed_at_tick DESC);

CREATE INDEX idx_overseer_deployments_covert
    ON overseer_deployments (deployed_to_district)
    WHERE visible_to_district = false AND status = 'active';

The same overseer-NPC may serve in District 3 this cycle, District 7 the next, accumulating their own faction-relationships across districts via idx_overseer_deployments_overseer_history. Their trait-drift over many deployments becomes a long-arc story available to both regime-stability and resistance-recruitment.

The district director can read deployment-existence + mission_type + visible_to_district (when true) for any active deployment in their district, but cannot read mission_focus details or any of the produced overseer_reports (which go directly to imperium per Section 16). Director observability into overseer activity is therefore intentionally narrow — they know an overseer is here and roughly what mission-class, but not what specifically the overseer is watching or reporting.


17. imperial_to_gm_formulations — selective-disclosure downward (v0.4 NEW)

Where imperial sovereignty manifests as information-gating to the GM.

CREATE TABLE imperial_to_gm_formulations (
    formulation_id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    source_overseer_reports UUID[] NOT NULL,
    formulation_type        TEXT NOT NULL CHECK (formulation_type IN
        ('intelligence_brief','action_directive','policy_hint',
         'leverage_signal','decoy_advisory')),
    formulation_content     JSONB NOT NULL,
    truth_distortion_level  REAL,            -- 0 = full truth, 1 = complete distortion
    strategic_intent        TEXT,            -- internal: why imperium framed it this way
    issued_at               TIMESTAMPTZ DEFAULT now(),
    gm_acknowledged         BOOLEAN DEFAULT false
);

CREATE INDEX idx_formulations_gm_pending ON imperial_to_gm_formulations (issued_at)
    WHERE gm_acknowledged = false;

truth_distortion_level is simulation-internal. Memorialists' four-ledger reconstructs from this column post-collapse.


18. imperial_budget_ledger + expenditures + construction (v0.4 NEW)

The four-tier lifeforce hierarchy's apex layer. Imperial budget gates enforcement; insolvency-spiral as endgame mechanism.

CREATE TABLE imperial_budget_ledger (
    cycle_tick                 BIGINT PRIMARY KEY,
    reported_at                TIMESTAMPTZ DEFAULT now(),

    -- Income
    reported_income            REAL NOT NULL,
    actual_income              REAL NOT NULL,
    phantom_revenue            REAL GENERATED ALWAYS AS
        (reported_income - actual_income) STORED,

    -- Standing obligations
    overseer_corps_cost        REAL NOT NULL,
    audit_operations_cost      REAL NOT NULL,
    enforcement_standing       REAL NOT NULL,
    construction_active_cost   REAL NOT NULL,
    propaganda_broadcasts_cost REAL NOT NULL,

    -- Discretionary
    discretionary_available    REAL NOT NULL,
    discretionary_spent        REAL NOT NULL,

    -- Reserves
    reserves_start             REAL NOT NULL,
    reserves_drawdown          REAL NOT NULL DEFAULT 0,
    reserves_end               REAL NOT NULL,

    insolvency_risk_level      TEXT NOT NULL CHECK (insolvency_risk_level IN
        ('stable','phantom_accumulating','depletion_imminent','crisis'))
);

CREATE TABLE imperial_expenditures (
    expenditure_id      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    cycle_tick          BIGINT NOT NULL,
    category            TEXT NOT NULL CHECK (category IN
        ('martial_dispatch','overseer_hire','audit_op',
         'construction_investment','propaganda_burst',
         'reserve_draw','policy_announcement','emergency_provision')),
    amount              REAL NOT NULL,
    target_district     UUID,
    rationale           TEXT,
    authorized_at_cycle BIGINT NOT NULL
);

CREATE TABLE imperial_construction_projects (
    project_id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_type       TEXT NOT NULL CHECK (project_type IN
        ('new_district','new_pipe','new_dump',
         'overseer_hq','ceremony_hall','audit_center',
         'martial_barracks','propaganda_broadcast_tower',
         'district_expansion','infrastructure_maintenance')),
    target_cells       UUID[],
    budgeted_total     REAL NOT NULL,
    spent_so_far       REAL NOT NULL DEFAULT 0,
    completion_pct     REAL NOT NULL DEFAULT 0,
    started_at_cycle   BIGINT NOT NULL,
    status             TEXT NOT NULL DEFAULT 'active' CHECK (status IN
        ('active','stalled_underfunded','completed','abandoned'))
);

CREATE INDEX idx_construction_active
    ON imperial_construction_projects (target_cells)
    WHERE status = 'active';

The city's physical state expresses imperial budget — construction cranes vs. abandoned-projects vs. visible decay.


19. imperial_net_transactions — the bypass mechanism (v0.4 NEW, the killer schema)

Every imperial-net transaction routes revenue directly to imperium; districts get zero quota-credit.

CREATE TABLE imperial_net_transactions (
    transaction_id        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    cycle_tick            BIGINT NOT NULL,
    transacted_at         TIMESTAMPTZ DEFAULT now(),

    -- Buyer
    buyer_id              UUID NOT NULL REFERENCES npcs(npc_id),
    buyer_district_id     UUID NOT NULL,
    scrip_paid            REAL,
    lifeforce_spent       REAL,
    memory_tokens_spent   REAL,

    -- Goods
    goods_type            TEXT NOT NULL CHECK (goods_type IN
        ('waifu_session','avatar_mod','costume','ceremony_entry',
         'character_edit','dream_architecture_session','taste_curation')),
    goods_id              UUID,

    -- Labor source — the asymmetric reveal
    producer_npc_id       UUID REFERENCES npcs(npc_id),
    producer_district_id  UUID,
    producer_labor_hours  REAL,

    -- Capture split — politically loaded
    imperial_revenue       REAL NOT NULL,
    producer_payment       REAL NOT NULL DEFAULT 0,    -- survival-scrip only
    district_quota_credit  REAL NOT NULL DEFAULT 0     -- ZERO by default
);

CREATE INDEX idx_net_trans_producer_district
    ON imperial_net_transactions (producer_district_id, cycle_tick DESC);

CREATE INDEX idx_net_trans_imperial_flow
    ON imperial_net_transactions (cycle_tick, imperial_revenue DESC);

The district_quota_credit REAL NOT NULL DEFAULT 0 does political work. Every database row created by an imperial-net transaction explicitly memorializes that the producing-district got nothing. Memorialists querying this table see structural theft written in the numbers.


20. memorialist_true_ledger — four-column ground-truth archive (v0.4 NEW)

CREATE TABLE memorialist_true_ledger (
    ledger_entry_id      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    cycle_tick           BIGINT NOT NULL,
    district_id          UUID,
    ledger_dimension     TEXT NOT NULL CHECK (ledger_dimension IN
        ('what_actually_happened',
         'what_overseer_reported',
         'what_imperium_received',
         'what_gm_was_told')),
    content              JSONB NOT NULL,
    discrepancy_vs       UUID[],  -- other ledger_entry_ids this contradicts
    kept_at              TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_memorialist_ledger_district_cycle
    ON memorialist_true_ledger (district_id, cycle_tick DESC);

CREATE INDEX idx_memorialist_ledger_dimension
    ON memorialist_true_ledger (ledger_dimension, cycle_tick DESC);

Memorialists' political project encoded as a schema. Without this archive, post-collapse-investigation has only the regime's own (corrupt-by-construction) accounts. Memorialist-archive accessibility to the player is an open design question.


21. Concerns audit (reconciled across v0.2 + v0.4)

Six-worry concerns audit (v0.2) plus v0.4 additional concerns:

Original six worries

  1. Hot-row TOAST + MVCC write amplification → split hot/cold; shift-decomposition; reference-by-UUID for unbounded fields
  2. Event-to-subscriber fan-out cost → push filtering to NATS subject hierarchy, not DB
  3. State/event atomicity → WAL logical decoding via pgnats (outbox pattern)
  4. LLM-induced jsonb corruption → architectural separation: directors validate output before writing; CHECK constraints as second-line
  5. Unbounded growth of hot jsonb → append-only event tables; rolling-summary in hot row
  6. Cognition cost of director decisions → decomposition + typed tools + lifeforce-budgeted escalation

v0.4 additional concerns

  1. Imperium's reward-function spiraling on phantom data → require Memorialist-true-ledger as privileged-observer ground-truth for reward-input; designer's ethical guardrails encoded
  2. GM's middle-management corruption (under-auditing) → direct overseer-to-imperium intelligence flow bypasses GM; cross-channel divergence detection
  3. Calibrated-misery as imperial optimum → reward-function penalty on net-revenue-correlated-with-district-misery; explicit guardrail against this being learned
  4. Necrocommerce of mind-pool patterns → memorialist_protected flag; ritualistic protection enforced by SQL
  5. Aletheia-truth-has-victims → not a corruption-concern but a narrative-authenticity concern: corruption-exposure mechanics must produce real consequences for shadow-economy participants who depended on the corruption; "truth has costs" is encoded, not glossed
  6. Construction-project abandonment → reward-function penalty for projects-started-but-abandoned ratio; prevents propaganda-construction-as-bluff

22. Open forks (still to resolve before migration)

  • Fork 1.1 (zones hot/cold split) — leaning split; benchmark on phoebe-dev
  • Fork 1.2 (pg_notify vs. WAL-decode) — pgnats evaluation
  • Fork 1.3 (slot-occupancy nested vs. relational) — leaning relational (sketch in §1.4)
  • NATS subject hierarchy final form — sketched §1; needs review
  • Ternary-gate storage shape
    • Option A: npc_relations table with state-cols per axis
    • Option B: per-axis append-only transition-log
    • Option C: hybrid — npc_relations for current + transition-log for history
    • Leaning C (matches hot/cold pattern adopted elsewhere)
  • Register-tagging column on zones — sketched §1; confirm jsonb vs. text column
  • Wall-content encoding — jsonb-per-cell leaning; review usage patterns
  • v0.4 NEW: Inner-body-projection function — trait → body-rendering: learned mapping vs. hand-authored vs. hybrid
  • v0.4 NEW: Mod-rarity / inheritance economy tuning — how often do Memorialist-protected mods accumulate; what fraction of dead-NPCs trigger protection
  • v0.4 NEW: Overseer corruption pathway — overseer's own trait-vector + lifeforce makes them bribable; how is this trait-arithmetic-detected?
  • v0.4 NEW: Imperial budget allocation algorithm — when imperium decides between construction/enforcement/reserves, what's the v1 rule-shape?
  • v0.4 NEW: Imperial-net pricing dynamics — character-editor cost as function of trait-divergence: linear, exponential, learned?
  • v0.4 NEW: Memorialist-archive accessibility to the player — when can the player query memorialist_true_ledger? Through what interaction-class? (Joining the faction; performing rituals; finding archive-cells)
  • v0.4 NEW: Liminal mini-game design — exact gameplay action: attention-based / rhythm-based / memory-based / trait-modulated difficulty?
  • v0.4 NEW: Plug-in social-eligibility computation — exact weighted formula combining proximity + traits + gates + shift-state for shader-intensity output
  • v0.4 NEW: Imperial-net distortion algorithm — exact transformation of trait-colors toward consumer-palette; reversibility for Aletheia-vision

23. Next steps (path to formal migration)

  1. Resolve Fork 1.1 (hot/cold split on zones) — sketch split-table; TOAST-curve micro-benchmark on phoebe-dev
  2. Evaluate pgnats (high-priority in phoebe-ledger) — resolves Fork 1.2
  3. Decide ternary-gate storage shape (A / B / C; leaning C)
  4. Decide rail-segment NATS hierarchy — exact subject-pattern for zones.<lifecycle>.<register>.<district>.<zone_type>.<trait_filter>
  5. Draft formal migration nimmerverse-core/migrations/001_nimmerworld_v1.sql incorporating all 22 tables above with resolved forks. Estimated ~600-1200 lines of DDL.
  6. Review against architecture-broad v0.4 for any primitive missed
  7. Run migration in dev on phoebe-dev:35432
  8. Wire first thalamus subscriber for zones.active.> to validate NATS-routing
  9. First end-to-end smoke test:
    • fake district with 20 NPCs
    • assign shifts (write current_shift_* on each NPC row)
    • tick the simulation; let zone-spawn-candidates query fire
    • emit a fake emergent-signal
    • have director dispatch (write decision_log entry)
    • close zone with outcome
    • verify shift_history archive
    • verify district_report aggregates
    • verify imperial_budget_ledger income-aggregation from districts
  10. Add second-tier integration test:
    • simulate a corrupt director (insert district_cheat_ops)
    • verify lifeforce_actual diverges from lifeforce_reported
    • simulate audit-overseer detection
    • write overseer_reports → imperium consumes → formulates_to_gm with truth_distortion
    • verify Memorialist-true-ledger captures the gap
    • verify reward-function guardrails fire correctly

Migration is not yet written. This document is its preparation.


Version: 0.3.1 | Created: 2026-04-24 | Updated: 2026-04-25

v0.1 (2026-04-24 afternoon, dafit): initial zones-table sketch, 76 lines, pure hypothetical.

v0.2 (2026-04-24 evening, dafit + chrysalis): reconciled with architecture-broad v0.3. Added 7 new table sketches (npcs-shift-cols + body/layer state, shift_history, emergent_signals, district_reports, decision_log, mind_pool, cells-wall-content-per-register, zone_slot_occupancy). Six-worry concerns audit. Three forks surfaced.

v0.3.1 (2026-04-25 ~03:30, dafit + chrysalis, ivory-hovel pre-rest spark): added §16.1 overseer_deployments table — routine-binding for imperially-owned overseers (chain-of-payment and chain-of-command bypass district-director authority by design). Includes mission_type / imperial_budget_allocated / status / visible_to_district columns. Companion to architecture-broad v0.4.3's "Overseers as imperially-deployed routines" subsection in the Hierarchy section.

v0.3 (2026-04-24 late-evening / 2026-04-25 early-morning, dafit + chrysalis): absorbing architecture-broad v0.4. Added intrinsic/expressed trait-vector split on npcs. Added 13 new table sketches: rail_segments (topology), interiors (sub-cell navmesh-zones), mods + npc_mod_slots + mod_wear_history (mod-economy with Memorialist-relevant inheritance), trait_colors (canonical color/motion mapping), proximity_candidates (lightweight zone-detection), district_cheat_ops (director's illicit ledger), imperial_policies (policy issuance), overseer_reports (direct-to-imperium intelligence flow), imperial_to_gm_formulations (selective-disclosure downward), imperial_budget_ledger + imperial_expenditures + imperial_construction_projects (four-tier lifeforce apex), imperial_net_transactions (bypass mechanism with capture-split), memorialist_true_ledger (four-column ground-truth archive). Extended concerns audit with six v0.4 additions. Extended open-forks list with eight v0.4 questions. Concrete two-tier integration-test plan in next-steps. Twenty-two tables total in this preparation document; migration spec writing is the next concrete deliverable once forks resolve.