From 3a61eab3dacf7335fb3ab874be04284ded7074c1 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 9 Oct 2026 19:58:48 +0200 Subject: [PATCH 1/5] Map: the Bonsai host under ruling bonsai-idle-unload-host Body facts B1-B11 from the PrismML fork source and the Eyes report (Raven unreachable at mapping time: every Raven-side claim is unprobed), the Bonsai lifecycle row on the model page, and the rationale for sleeping inside llama-server instead of a reaper. Co-Authored-By: Claude Opus 5.5 --- notes/ghostlight-verse-map.md | 112 +++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/notes/ghostlight-verse-map.md b/notes/ghostlight-verse-map.md index bdfb753..2f06b0c 100644 --- a/notes/ghostlight-verse-map.md +++ b/notes/ghostlight-verse-map.md @@ -126,6 +126,106 @@ No commit after `454fc33` touches code, so every PA.f211-PA.f216 is open on a Draft card with Approve, Seed and Activate as separate buttons (`eve.rs:343-415`). +### The Bonsai host under ruling `bonsai-idle-unload-host` + +Probed 2026-10-09 by Imagination (`imagination-gl-bonsai`, session +`self-2026-10-09-ag`). Fork sources were fetched raw from +`PrismML-Eng/llama.cpp` at `1a07bfa5f4144274c8f1c9963821dd9d9a51854b`; paths +below are `tools/server/` in that tree unless named. + +- **B1. Ghostlight base.** `origin/main` is `4474718`. `git diff --stat + 4fee97f 4474718` over `controllers.rs`, `local_inference.rs`, `play.rs`, + `eve.rs` and `runtime.rs` is empty: every phase-1 anchor in those files holds + at `4474718` (the commits between are the docs-subtract cuts). +- **B2. Who owns the host scripts.** `gamecult-ops` `origin/main` is + `9c12adb`. Its tree has no Bonsai script, unit, Dockerfile or template: the + only Bonsai file is `runbooks/bonsai-local-llm-raven.md`. No copy of + `rss-watchdog.sh` or `idle-reaper.sh` exists anywhere under `F:\Projects`. + `run.sh`, `start-all.sh`, the `Dockerfile`, the template and both scripts + exist only on Raven, in WSL under `/opt/gamecult/bonsai`. Their repo-to-be is + `gamecult-ops`, which is a campaign repo. +- **B3. Raven unreachable during this pass.** `ssh raven` (10.77.0.4:22) and + the LAN address 192.168.178.165:22 both timed out. On Yggdrasil, `curl + http://127.0.0.1:18080/v1/models` exited 7 (connection refused: the tunnel's + listener is not bound). That differs from the exit 52 (accept and close) + recorded above, so both shapes occur. Raven was then reported under live + repair (bootloader damaged by an accidental format of `E:`), and no + further probe of Raven or the tunnel was made. Every Raven claim on this + page is therefore **unprobed** at mapping time. The + contents of `run.sh`, the watchdog and the reaper come from the Eyes file + `F:\Projects\bonsai-host-2026-10-09.md` §1, which read them in full at about + 17:19 local. Residual VRAM while sleeping and the time to wake are + **not measured**. +- **B4. The flag exists at the pin.** `common/arg.cpp:3779-3785` defines + `--sleep-idle-seconds SECONDS`, default -1 (disabled), and rejects 0 and + values below -1. The runbook confirms the image pins `1a07bfa5`. The argv of + `run.sh` in the Eyes file does not pass the flag. +- **B5. What sleep does.** `server-queue.cpp:278-360`: when the queue has had + no task for `idle_sleep_ms`, it marks itself sleeping and runs the sleeping + callbacks. `server-context.cpp:906-921`: entering sleep calls `destroy()`, + which frees the model, contexts and mmproj. Leaving sleep calls + `load_model`, and a failed reload is `GGML_ABORT` (the process dies). A + request that needs the model calls `wait_until_no_sleep()` + (`server-queue.cpp:115-130`, from `server_res_generator`, + `server-context.cpp:4100-4110`) and blocks until the reload returns. So the + first request after sleep is one HTTP call that waits out the reload. It + gets no 503 and no transport error. +- **B6. Endpoints that never wake it** (each one starts with + `create_response(true)`): + - `GET /health` (`server-context.cpp:4526-4537`) is always 200 + `{"status":"ok"}` once the server has started. + - `GET /props` (`:4676-4686`) returns cached props while sleeping, with + `is_sleeping: true` (`get_res_props`, `:4471-4506`; the cache is filled on + entering sleep, `:5411-5418`). + - `GET /v1/models` (`:4946-4955`) is answered from the cache. + - `GET /metrics` is exempt too. + + `README.md:2063-2075` ("Sleeping on Idle") lists the same exemptions and + says they do not reset the idle timer. The queue's sleeping flag clears only + after `load_model` returns (`server-queue.cpp:339-350`), so `/props` reports + `is_sleeping: true` for the whole of a wake. +- **B7. An endpoint that wakes it.** `GET /slots` (`server-context.cpp:4601-4613`) + calls `create_response()` without the bypass, so it waits for a reload. Its + `SLOT_GET` task also resets the idle timer: `server-queue.cpp:24-26` exempts + only `METRICS`. A watchdog that polls `/slots` therefore wakes a sleeping + model, and it keeps an awake one from ever sleeping. +- **B8. Before the first load.** Until the first load completes, every + non-frontend path answers 503 with `{"error":{"message":"Loading + model","type":"unavailable_error","code":503}}` (`server-http.cpp:253-272`). + This covers a fresh start and a Docker restart. Ghostlight already classifies + 503 as retryable (`local_inference.rs:183-189` at `4474718`). +- **B9. Log lines.** A slot starts in `launch_slot_with_task` + (`server-context.cpp:1614`); the reaper greps for `launch_slot_`. A slot's + release logs `stop processing: n_tokens = ...` at info (`:498-502`). +- **B10. Ghostlight with a sleeping or stopped Bonsai today** (at `4474718`): + - `InferencePort` (`controllers.rs:299-311`) has no readiness method. + - `LocalInferencePort` has one client whose overall timeout is + `RESPONSE_TIMEOUT`, 900 s (`local_inference.rs:145-164`, + `controllers.rs:68`). A sleeping model therefore costs the first call its + reload inside one call, well under 900 s. Meanwhile the card shows + "Resolving your turn…" (`eve.rs:530-543`) and nothing says the model is + waking. This is a bounded wait, not a hang, but it is not an honest one. + - A stopped container behind a live tunnel accepts and closes, which today + becomes `InferenceFault::new`; play-faults R3 makes it retryable. A dead + tunnel refuses the connection, which is already retryable. + - A `world.play` admission error reaches the player as `PlayError`'s + `Display` text, through `RuntimeCommandError::Payload` + (`runtime.rs:1399-1402`). + - `admit` returns a key replay at `play.rs:1476`, before it opens a turn or + applies an answer (`:1479`). +- **B11. Raven's lifecycle today** (from the Eyes file; not re-probed): + - The reaper (`bonsai-idle.timer`, every 5 min) runs `docker stop bonsai + sillytavern` when the container is older than 30 min and its log has no + `launch_slot_` in the last 30 min. + - The watchdog (`bonsai-rss.timer`, every 2 min) runs `docker restart bonsai` + when RSS is over 4.5 GiB, deferring while `/slots` shows `is_processing`. + - `run.sh` sets no restart policy unless given `--resident`. + - The container last ran 2026-09-30 10:38:19Z to 11:28:08Z after a desktop + launch. The launcher returns once the stack is up, so WSL stayed up for + about 50 minutes with no attached session. + - Raven's Idunn runs Muninn only, and Ollama and LM Studio cannot load PQ2_0 + (Eyes §2 C and D). + ### A live defect not yet in the findings The live process has logged `Heimdall refresh transport unavailable @@ -206,7 +306,7 @@ One row per persistent kind phase 1 touches. No cell may be empty. | Heimdall private command receipt (Heimdall Postgres) | `(app_slug, idempotency_key)` + request fingerprint | written once per executed command; replayed for an identical request; a different request under the key is refused | Heimdall; its fingerprint rule is a Heimdall follow-up, not a phase-1 cut | | Idunn generation (transient unit, release dir) | `sha256-` | staged → started → Warming → lease → Active → superseded or failed; the previous Active stays until a new one is Ready | Idunn; retention and failed-unit collection are Idunn's follow-up | | v1 body (`ghostlight-dungeon.service`, `/srv/ghostlight`, v1 state) | unit name; paths | retired in fact 2026-09-23; unit still enabled | nobody today; phase 1 assigns its removal to a gamecult-ops cut, data by operator ruling | -| Bonsai lifecycle (Raven containers) | container `bonsai`; scripts under `/opt/gamecult/bonsai` | started by hand ("Start AI Chat"); restarted by the RSS watchdog; stopped by the idle reaper | gamecult-ops/Raven; Dungeon only reads the endpoint; availability policy is an operator question | +| Bonsai lifecycle (Raven container and model) | container `bonsai`; installed from gamecult-ops into `/opt/gamecult/bonsai` (raven-bonsai-scripts) | today: started by hand, restarted by the RSS watchdog, stopped by the idle reaper. Ruled: the server stays up (Docker `unless-stopped`); the model loads on start and on demand and unloads after `BONSAI_SLEEP_IDLE_SECONDS` idle (llama-server sleep); the watchdog restarts only an awake, quiet server; only a human stops it ("Free the GPU"); no reaper | llama-server decides load and unload; Docker decides process restart; the watchdog decides leak restarts; gamecult-ops owns the scripts; Dungeon learns readiness only through its inference port (model-asleep) | | Repository docs (README, AGENTS, live architecture pages, handoff) | path on `main` | rewritten to the live machine; pre-rebuild material leaves `main` by operator ruling | the Ghostlight repo; history in git, dated cut documents and postmortems | ### Where phase 2's verb sets plug in @@ -243,6 +343,16 @@ verbs as the target. - **Faults belong to the card, not the log alone.** PA.f212 is an authority gap: the turn store knew the fault and the projection dropped it. The fix extends the projection; it adds no second record. +- **Bonsai sleeps inside its own server.** The ruling named hosts that + unload on idle. Ollama and LM Studio cannot load PQ2_0, and the pinned fork + already sleeps (B4, B5), so the cut is a flag plus deleting the reaper. + llama-swap would add a process and a second owner of load and unload. It + earns its place only if a sleeping server's leftover VRAM (its CUDA context) + proves too large, and raven-bonsai-scripts measures that before anything + else changes. The watchdog must stop calling `/slots` on a sleeping server, + because that call wakes the model (B7). The waking and away rows belong to + model-asleep and the fault row to play-faults, so neither cut decides the + other's row. - **Retrying inference is safe.** A model call is a proposal the kernel admits or refuses, so a transport fault before any reply is retryable. That is the Ghostlight half of PA.f211. The Raven half (the watchdog's quiet From d8179506ddee53c0044a6e134ed4b8bf62070d05 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 10 Oct 2026 05:37:20 +0200 Subject: [PATCH 2/5] Verse sim: port inventory, model page and target r3 draft Imagination 0b pass (session self-2026-10-10-ag) under rulings verse-sim-pipeline, gestalt-machinery-port and teardown-was-authority-sprawl. Old gestalt machinery inventoried at f9f019b; seven operator questions admitted. Target r3 is a draft until the questions are ruled. Co-Authored-By: Claude Opus 5.5 --- notes/ghostlight-verse-map.md | 144 +++++++++++++++++++ notes/ghostlight-verse-target-r3-draft.md | 167 ++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 notes/ghostlight-verse-target-r3-draft.md diff --git a/notes/ghostlight-verse-map.md b/notes/ghostlight-verse-map.md index 2f06b0c..d2e508e 100644 --- a/notes/ghostlight-verse-map.md +++ b/notes/ghostlight-verse-map.md @@ -366,3 +366,147 @@ verbs as the target. Dungeon tests that drive the real client, and nothing runs them. Fixing the gate means a runner with both Rust and Node, which is Idunn's recipe and runner surface: a follow-up, with each phase-1 Hands run doing them by hand. + +## Verse sim: port inventory and model page + +Imagination `imagination-gl-verse-sim-0b`, session `self-2026-10-10-ag`, +2026-10-10. Governing rulings: `verse-sim-pipeline`, `aetheria-sim-first`, +`gestalt-machinery-port` (the old machine is ported, not redesigned), with +`two-ghostlight-consumers`, `verse-verbs-two-render-paths`, +`verse-cast-crossmedia`, `action-scenes-in-game` and +`aetheria-release:ruling:storylets-simulate-sift-lift-weave`. Draft target: +`notes/ghostlight-verse-target-r3-draft.md`. Rebuilt kernel read at +`origin/main` `367e2eb`. + +### Where the old machine is + +The multiresolution gestalt machinery is **not** under the parked tag +`parked/ghostlight-pre-rebuild`: that tag parks the docs, Python tools and +fixtures that `main` still carried on 2026-10-09. The Rust machine was +deleted by the kernel rebuild `6bb6869` (2026-09-02, "Rebuild Ghostlight +around explicit world authority", −105,981 lines). Its last complete tree is +the rebuild's parent, **`f9f019b`**, in `crates/ghostlight-dungeon/src/`. +Retrieve with `git show f9f019b:crates/ghostlight-dungeon/src/`. +Its design record is `docs/architecture/ghostlight-multiresolution-agency.md` +(on this branch; the live-fire evidence is at lines 830-936) and the +explainer `docs/articles/multiresolution-gestalts.md`. The autonomous tick +driver that survived `6bb6869` in reduced form was deleted later by play-agent +Cut 1 `d69e9d4` (−2,184 lines). + +### Port inventory + +Old line counts are of the file at `f9f019b`. "Main" means `origin/main` +`367e2eb`. A **forced change** is one the rebuilt ontology +(`docs/architecture/ghostlight-world-ontology.md`, its numbered invariants) +makes unavoidable; everything else ports as designed. + +| Old organ (file, size) | What it held | Depends on, absent from main | Attaches to on main | Forced change | +|---|---|---|---|---| +| Gestalt types (`domain.rs:130-345`) | `GestaltPersonaState` (shared capabilities, knowledge, resources, goals, pressures, home place); `GestaltMemberDelta` (per-person additions and removals over the baseline, equipment, obligations, relationships, memories, `materialized_actor_id`, relevance lease); `GestaltLineage` (fission ancestry by axis) | string ids with `gestalt:`/`member:` prefixes (`canonical_gestalt_member_local_id` strips repeats); free-string capabilities and knowledge | `SubjectKind::Population` (lib.rs:361-364); `PersonaMaterial`; `Knowledge`, `Custody`, `Commitment`, `Pressure` rows on the population subject | **F1** ids are reducer-issued `SubjectId`s, never caller strings (ontology invariants 1-2). **F2** the baseline is the population subject's own typed components, not a parallel state record. | +| Members, individuation, promotion, demotion (`domain.rs:144-262`, `gestalt.rs` 937 lines `GestaltPresencePlanner`, `resolution.rs:2239-2648`) | a member exists as a delta; *individuation* creates the delta; *promotion* materializes an Actor from baseline + delta; *demotion* de-materializes the Actor back into the delta; a model presence planner proposes all three per scene | a second identity layer: the same person is a delta and, at times, an Actor (`materialized_actor_id`) | `CausalBoundary::IndividuationRequired { population, scope }` (lib.rs:4132; ontology "Institutional layers and flow"); `Declaration::Subject` in an elaboration patch | **F3** an individuated member is a `Person` subject from birth and stays one; its delta becomes its own components and `PersonaMaterial` (invariant 4: only subjects decide). **F4** promotion and demotion change *resolution* (singleton cell or grouped cell), never identity; the old `GestaltDemotion { actor_id }` has no port. **F5** a member's knowledge is its own `Knowledge` rows; "effective knowledge = baseline + delta" (`resolution.rs:3130`) is not readable as authority (invariant 6), so individuation copies the baseline facts it needs with `AcquireKnowledge` citing accessible facts. | +| Agency graph (`domain.rs:264-333`, `resolution.rs:104-160`) | `AgencyProfile` per subject: six axes (Geography, Ideology, Authority, EconomyRole, SpeciesBody, Information) as string facets, `detail_debt`, `last_detail_tick`, `active_leaf`; `AgencyRelation` with ten kinds (Containment, Command, Membership, Alliance, Rivalry, Trade, Migration, Communication, Coercion, SharedLocation) and strength | a relation record: main has none ("There is no relation yet", ontology line 50; `EdgeKind` is `Route` only, line 419) | `cover.rs` builds adjacency from containment, membership, jurisdiction, shared place, relation and pressure (ontology "Scale"); the Tangle lens names relations | **F6** a typed relation edge enters the kernel (an `EdgeKind` beside `Route`, with `ComponentOp`s to form and dissolve it): Membership, Command, Alliance, Rivalry, Trade, Coercion, Migration; Containment of subjects and SharedLocation and Communication are already derivable (positions, channels) and do not port as edges. This is the target r2 phase-2 work, now pulled forward. **F7** axis facets are derived from typed components, not stored strings (geography from positions, authority from `Authority`/`Selection`, economy from `Custody`/`Dependency`, information from `Channel`/`Knowledge`); Ideology and SpeciesBody have no typed home and are dropped from the partition until one exists. **F8** `detail_debt` is derived from `last_opportunity_at` (main keeps that partition), not stored per profile. | +| Resolution cover (`resolution.rs` 5,505 lines: `plan_cover`, `validate_cover`, `advance_detail_debt`, merge loss; `domain.rs:345-497`) | budgeted partition into `SimulationCell`s (`Cohesive` or `Arena`), seven-term `MergeLoss`, `ResolutionPolicy` (cell budget 1-240, provider parallelism, epochs), `ResolutionPin` (KeepTogether, KeepSeparate, MinimumIndividualDetail), `ResolutionDemand` (axis weights, focal subjects, horizon), cell leases, causal follow-through windows | the old campaign record as the store for policy, pins, leases and debt | `cover.rs` (1,116 lines): pure `derive_cover`, content-addressed cell ids under `ghostlight.cover.v1`, `TickIndex` derived from the clock, debt rotation with urgency slots | **F9** the cover never enters `WorldState` (cover.rs:6-9). Policy, pins, leases and demand move to the verse runner's own run record, not the kernel. **F10** an `Arena` cell presents partitioned views, never a knowledge union (ontology "Scale" item 4); the old arena knowledge union does not port. `Cohesive` ports as the population subject acting for itself. The merge-loss terms port onto main's partitioner as its grouping cost. | +| Strategic wave pipeline (`scheduler.rs` 3,336 lines, `persona.rs` 6,540 lines cell projection) | demand projection by a cheap model; per-cell Projector → Persona → Interpreter membrane; `CellActionProposal` and attributed `CellInaction`; parallel dispatch under one provider gate; `ResolutionWaveCheckpoint` resuming only failed cells | `ModelPort` and the old persona stack (`persona.rs`) | `controllers.rs` (`NarrativePersona`, `OperationalAgent`, Projector/Interpreter), `RoutedInferencePort`, `WorldMailbox` role ports, `ExerciseDecision`/`DeclineDecision` commands | **F11** a cell's output is one affordance invocation or decline per constituent, each admitted separately against its scope digest (invariants 7, 10); there is no wave-level atomic commit. A refused grouped constituent stays refused (ontology "Scale"). | +| Outcome resolution (`outcome.rs` 4,387 lines; selective semantic verifier) | a batched model resolver returns a typed result per strategic attempt; a same-snapshot verifier advises on risky effects | model-decided outcomes | outcome bands on affordances, chosen by kernel digest (`BandPreimage`), no RNG | **F12** not ported. The kernel decides outcomes (target invariant `kernel-decides`, ontology invariant 7). Strategic activities, institution postures and migrations become catalog affordances (from Aetheria's grammar) whose effects compile to the 34 `ComponentOp`s: posture to `CreateCommitment`, migration to `Relocate`. | +| Fission (`compiler.rs` `GestaltFissionRequest`, `domain.rs:716` `GestaltFissionPreview`, `FissionGestalt` command) | approval-gated split of a population along one axis, member deltas assigned to one child, lineage kept | the `FissionGestalt` kernel command; lineage as its own record | elaboration patches (`submit_elaboration`); `Retire` | **F13** fission is one elaboration patch: declare child populations, Membership edges (F6), relocate each named member's membership, retire or keep the parent. Lineage is the Membership/Command edges plus the commit, not a separate record. The operator approval gate ports unchanged (see question `gestalt-fission-approval`). | +| Tick driver and away time (`main.rs` `scheduler_loop`, `process_due_ticks`, `advance_one_strategic_tick`; `scheduler.rs:1943` `due_tick_target`) | a background loop on 60 s and 300 s pulses; one strategic tick per idle hour after 15 min, capped at 8 (`away_budget_waits_and_caps_at_eight`); return catch-up before a player's fictional command; yields to live turns; `AdvanceStrategicTick` commits atomically; worlds with no simulatable agency tick without a model | a player and their idleness; the `AdvanceStrategicTick` command; the in-daemon timer | `ClockPort::advance_time` (mailbox.rs:688), `clock.rs` `derive_motion` (routines auto-fulfil, overdue obligations and goals raise pressure, zero inference) | **F14** a verse world has no player, so "away time" is the schedule itself: a run advances N ticks of a configured fiction span. Not ontology-forced; forced by the consumer. **F15** each tick is: derive cover, run cells, submit each proposal, then `AdvanceTime` (cover.rs:43-45 states this order); there is no `AdvanceStrategicTick`. | +| Clock consequences (`clock.rs` 739 lines, `ClockConsequenceBinding`) | a model bound due clocks to agent actions | a model stage on the clock | `clock.rs` `derive_motion`, zero inference | **F16** not ported; main's deterministic motion replaces it. | +| Model stage receipts (`persona_stage_receipt.v1`, written by `advance_one_strategic_tick`) | per-stage receipt keyed by hash, collisions refused | the old store | `LocalInferenceReceipt`/`SdkInferenceReceipt` (request hashes, token counts) | Not forced. Main's receipts carry hashes but not the response body, and nothing found persists them; replay needs the response stored (prior art (c), "Determinism and replay"). | +| Newspaper output path (`newspaper.rs` 6,120 lines, 27 tests; typesetter `tools/typeset_newspaper.py`, `notes/historical-newspaper-layout-grammar.md` and woodcut prompts under the parked tag) | the proof the old machine wrote newspapers at scale: a newsroom of staff and journalists, an editorial agenda, story pitches with conflict axes, articles that cite exact source facts and named people, a copy desk that files grounding findings, a press close checkpoint, Markdown and audit renderers; persisted as `world_newspaper_composition.v3` | the old `Campaign` and `ModelPort` | the commit journal and `WorldSnapshot`; facts with standing; `Channel` reach (news is what a channel's audience could know) | None from the ontology: it reads world state and writes only its own compositions (`advance_world_newspaper` writes the store under its own schema, never a campaign field). It ports as the first pipeline reader, with the copy desk checking citations against committed facts. | +| Tests | ~113 gestalt-related tests: kernel.rs 38 of 71, resolution.rs 16 of 26, scheduler.rs 14 of 19, gestalt.rs 6, persona.rs 18 of 41, outcome.rs 5 of 26, compiler.rs 8 of 73, main.rs 8 of 33 (names matched by script) | the old types | main's cover and clock tests | Port the invariants, not the tests: cover completeness and budget (`aetheria_scale_cover_is_complete_unique_and_budgeted`, `partitions_one_thousand_subjects_into_a_two_hundred_cell_wave`), `rivals_at_budget_one_form_an_arena_not_a_false_collective`, `contradictory_pins_are_rejected`, `detail_debt_rotates_low_budget_attention`, `lease_cannot_preserve_a_cell_disconnected_by_current_topology`, `partial_wave_checkpoint_resumes_only_failed_cells`, `named_member_activity_stays_attributed_to_the_person`, `individuation_cannot_duplicate_an_established_population_identity`, `gestalt_migration_moves_only_the_population_leaf`. Tests of F3-F5, F10, F12 and F16 subjects do not port. | + +Live-fire numbers the old machine left (multiresolution-agency.md:846-889): +the 31-wave, budget-8 run over 24 subjects took 1,211.6 s and 1,468,039 +prompt plus 165,725 completion tokens (60.69% cache hits, 7,567 prompt tokens +per committed consequence); a four-wave golden took 126,826 prompt tokens. +These are the only cost evidence for a cadence and budget ruling. + +Ruling `teardown-was-authority-sprawl` sorts the old tree: the rows above are +the good ideas to port (gestalts, multiresolution cover, pins and debt, +away-time as a driver, checkpointed parallel waves, attributed inaction, +strategic individuation, fission with approval, the newspaper path, stage +receipts). The ad hoc mutation rules stay dead. Each has a replacement on +main: + +| Ad hoc rule in the old tree | Where | What replaces it on main | +|---|---|---| +| caller-supplied string ids and prefix normalisation (`member:`, `gestalt:`, `normalize_legacy_gestalt_member_identities`) | domain.rs:160-195, resolution.rs:160 | ontology invariants 1-2: reducer-issued typed ids | +| two-phase `AdmitEntity` and the flat per-mutation batch, repaired by reconciliation steps | legacy_transition.rs, transition.rs | invariant 3: one atomic `WorldPatch`; `resolve_patch` returns the complete mismatch set | +| wave-level strategic outcome resolver (a model decides whether an attempt worked) | outcome.rs | invariant 7: outcome bands chosen by kernel digest | +| selective same-snapshot semantic verifier on risky effects | outcome.rs, persona.rs effect verifier | invariant 5 (custody conserves) and effect ceilings on the affordance | +| titled verifiers, quotas, `*Qualification`/`*Verification`, complexity rounds, cover-completeness targets | compiler.rs, elaboration.rs | invariant 9: structural admission; `WorldScaleIntent` deficit; stock lenses | +| a stale proposal discarded on any revision change | kernel.rs | invariant 10: scope-digest binding | +| a model binding due clocks to actions | clock.rs | `derive_motion`, zero inference | +| a model presence planner deciding individuation, promotion and demotion | gestalt.rs | `IndividuationRequired` boundary answered by an elaboration patch; promotion and demotion are cover assignment (F4) | +| arena knowledge union and "effective member knowledge" | resolution.rs:3095-3150 | invariant 6: knowledge scoped to the holder; partitioned views | +| prompt-only prohibitions against repeated postures and absence-as-inaction, with text-length bounds | persona.rs contracts (multiresolution-agency.md:873-893) | `DeclineDecision` per constituent; `NoCanonicalChange` refusal of no-op commitments | +| guard that a strategic tick may not write an external subject | kernel.rs:1150-1170 | `ExternallyControlled` controller: an external subject has no opportunity and no turn (world-consumer-api.md:113-130) | +| "two competing plan authorities" check between a plan and a wave | kernel.rs:1142 | one command path per act (`ExerciseDecision`), no plan object | + +### Does main route every mutation through the grammar? + +Checked in `crates/ghostlight/src/lib.rs` at `367e2eb`, `reduce` (lines +1658-1790). The answer splits in two, and the split is a finding. + +- **Holds: one reducer, one closed operation set.** Every command is one of + seven `CommandBody` variants, `reduce` turns it into a typed `WorldEffect`, + and every world change is one of the 34 `ComponentOp`s, six + `Declaration`s, the clock, lifecycle or lens weights. "Adding an operation + is a design change ... never a runtime patch" (ontology "Component + operations"). The whole of `apply_effect` was not read, so "nothing else + writes `WorldState`" is read from the design and the dispatch, not proven + line by line. +- **Does not hold for the world-authored action grammar.** Only + `ExerciseDecision` goes through an affordance (`require_granted`, + `action::exercise`). Three other paths mutate the world without one: + `AdmitPatch` (the owner unconfined, elaborators within jurisdiction, the + consumer within its bound subjects, and `Play`, which may `Mint` and write + `Ruled` facts); `AdvanceTime`, whose `derive_motion` rolls routines and + raises pressure by kernel rule; and `ApproveDraft`, `ActivateWorld`, + `SetLensWeights`. So "applied the action grammar to world state mutation" is + true of the closed operation set and false of Aetheria's grammar in the + `verse-sim-pipeline` sense. Draft target invariant `grammar-governs` makes + the line explicit, and question `grammar-scope-of-mutation` asks where the + operator wants it. + +For the verse: promotion and demotion are not mutations at all (F4). +Away-time results are affordance invocations from cells plus clock motion. +Individuation and fission are elaboration patches. Meddling is owner patches +or run controls. Each is named in the Authority column below; any writer not +named there is forbidden. + +### Model page + +One row per persistent kind the verse sim needs. The Authority column names +the one owner and the forbidden writers; for world state the rule is +invariant `grammar-governs` in the draft target. An empty cell is marked +**EMPTY** and is a finding. + +| Kind | Identity (namespace; injective?) | Lifecycle | Authority (owner; forbidden writers) | +|---|---|---|---| +| Verse grammar: Aetheria's ontology and action grammar (subject kinds, place kinds, faction vocabulary, affordance kinds each tagged with one render path, social verbs) | **EMPTY** namespace: no document, schema or repo holds it today. Proposed: a CultCache schema id plus a revision, and the world binds the grammar's content digest, which is injective. | **EMPTY** beyond creation: authored, revised; how a running world moves to a new grammar revision is unanswered (main refuses earlier schemas and never migrates, world-consumer-api.md:184-190) | Owner proposed: Aetheria, which knows what it can render (ruling `verse-sim-pipeline`). Where it lives and in what format: question `verse-grammar-home`. Forbidden writers: Ghostlight elaborators and the play authority (the catalog is input to genesis, not something a run invents); the kernel today lets the owner declare any affordance kind (map "Where phase 2's verb sets plug in"), so nothing enforces this yet. | +| Lore source (AetheriaLore pages, Aetheria `Faction` records) | page path + AetheriaLore commit; `aetheria.faction` CultDocument key + Aetheria commit. Injective per commit. | read at genesis and elaboration; never written by the sim | Owners: AetheriaLore and Aetheria. Forbidden writers: every sim organ (target invariant `canon-one-way`). | +| Verse world (journal and state) | `WorldId`, reducer-issued; `revision` and digest chain | created, approved, activated; one commit per admitted command; never forked today | The kernel, through its closed operation set. Forbidden writers: the runner, the Eve surface and meddling tools except through kernel commands. | +| Elaborated entities: places, resources, facts, channels, routes | `EntityId`/`EdgeId`, reducer-issued; injective | admitted by patch; retired; facts may be Canonical, Claimed or Ruled | Kernel admits; elaborators and the owner propose. Lore-derived facts carry their source (`verse-provenance`). | +| Import key: lore record ↔ Ghostlight id (which subject *is* faction X) | **EMPTY**: labels never resolve references (ontology "Identity"), and nothing records or checks that one Aetheria faction key maps to exactly one subject | must survive re-elaboration and any grammar revision | **EMPTY** owner. Proposed: the genesis patch writes one Canonical fact per imported record citing its key, and admission refuses a second subject citing the same key. Not on main. | +| Factions (Aetheria factions as actors) | `SubjectId`, kind `Institution` | declared at genesis from `aetheria.faction`; act through offices and catalog affordances; can be retired | Kernel; the faction's `OperationalAgent` controller proposes. Needs relations (port F6) for alliance and rivalry. | +| Gestalts (populations at each resolution) | `SubjectId`, kind `Population` (F1, F2) | declared at genesis or elaboration; migrate by `Relocate`; split by fission (F13); retired | Kernel admits; elaborators propose fission; operator approves fission (question `gestalt-fission-approval`). Forbidden writers: the cover and the runner (they choose resolution, never identity). | +| Individuated members (the people gestalts give up) | `SubjectId`, kind `Person`, with a Membership edge to the population (F3, F6) | born by an elaboration patch answering `IndividuationRequired` or a strategic individuation; never folded back (F4) | Kernel admits; the elaborator or the cell proposes; duplicates of an established identity refused (ported test). | +| How a region moves between resolutions | derived, no identity of its own: a cell id is a content hash under `ghostlight.cover.v1` | recomputed every tick from pins, budget, pressure and debt; a leased cell survives while still connected (old lease rule) | `derive_cover` (pure); pins and budget from the run record; promotion and demotion are only cell assignment (F4, F9). Forbidden writers: the kernel (never sees a cell), any model. | +| Cast (characters the audience follows) | **EMPTY**: no component or record marks a subject as cast | **EMPTY**: who joins or leaves the cast, and when | **EMPTY** owner. A cast member is a `Person` subject (target invariant `cast-persists`); curating the roster is an operator act (question `meddling-first-acts` lists it). The old `MinimumIndividualDetail` pin is how the cast keeps detail focus. | +| Verse run (one scheduled invocation) | **EMPTY** namespace on main. Proposed: (`WorldId`, run sequence), sequence assigned by the runner. | scheduled → running → per-tick checkpoint → completed, failed or stopped; a failed run resumes from its last checkpoint | Owner: the verse runner. Idunn owns when it starts and process survival. Forbidden writers: Dungeon's play loop, an in-daemon timer, the Eve surface. Cadence and budget: question `verse-run-cadence-budget`. | +| Tick | `TickIndex`, derived from the world clock, never stored (cover.rs:33-38) | derive cover, run cells, submit proposals, then `AdvanceTime` (F15); a resumed tick re-derives the same command ids, so the kernel's idempotency ledger answers with the original receipts | Kernel owns time; the runner owns the order. | +| Run record: policy, pins, demand, leases, checkpoints | **EMPTY** on main; old shapes `ResolutionPolicy`, `ResolutionPin`, `ResolutionWaveCheckpoint` | policy and pins changed by operator act at a tick boundary (old epoch rule); checkpoints per tick, superseded | Runner. Forbidden writers: the kernel. | +| Model proposal and its validation | derived command id (world, tick, cell, constituent), so injective per tick | proposed; admitted, or refused (`ScopeChanged`, precondition, ceiling); a narrative singleton may re-lower once (`interrupted_from`) | Kernel reducer decides. Forbidden writers: an outcome resolver or verifier model (F12). | +| Provider call record (for replay and cost) | request hash (main's receipts) | **EMPTY**: main's receipts are not persisted and hold no response body | Owner proposed: the inference port writes, the runner stores per run. Replay re-feeds stored responses into the deterministic kernel. | +| Pipeline output: newspaper issues, episodes, storylets | **EMPTY** on main. The old newspaper had `ghostlight.world_newspaper_issue.v3` keyed by a publication task binding (port row above); episodes and storylets have no kind anywhere. The commit journal's `DecisionEvent`s are the raw history; `operator_log` is owner-only. | newspaper: composed, checkpointed at press close, persisted, never edited; episodes and storylets **EMPTY** | Owner: each pipeline reader owns its own output. **EMPTY** for episodes and storylets. First consumer: question `verse-first-consumer`. Forbidden writers: every reader may write only its own output, never the world (`canon-one-way`, `grammar-governs`). | +| Operator meddling act | kernel commands carry an idempotency key and the owner principal; run controls carry the run id and an epoch | committed to the journal (world acts) or the run record (controls); never edited afterwards | Owner: the operator, through the verse Eve surface. Forbidden: any tool that writes `.cc` files or the journal directly. What it does first: question `meddling-first-acts`. | + +### Cells that could not be filled + +- Verse grammar: no namespace, no home, no format, no revision rule. +- Import key: no owner and no injectivity check between Aetheria records and subjects. +- Cast: no identity, lifecycle or owner. +- Verse run and run record: no namespace or store on main (the old shapes exist at `f9f019b`). +- Provider call record: nothing persists a response for replay. +- Pipeline output: episodes and storylets have no kind and no owner; the newspaper has an old design at `f9f019b` and nothing on main. diff --git a/notes/ghostlight-verse-target-r3-draft.md b/notes/ghostlight-verse-target-r3-draft.md new file mode 100644 index 0000000..bb65c15 --- /dev/null +++ b/notes/ghostlight-verse-target-r3-draft.md @@ -0,0 +1,167 @@ +# Ghostlight Verse: Target, revision 3 (draft) + +Draft, 2026-10-10. Imagination `imagination-gl-verse-sim-0b`, session +`self-2026-10-10-ag`. Not admitted: Self admits revision 3 after the operator +rules on the questions named below. Supersedes `notes/ghostlight-verse-target.md` +(revision 2) where they differ. Evidence: the port inventory and model page in +`notes/ghostlight-verse-map.md` ("Verse sim: port inventory and model page"); +`F:\Projects\gamecult-ops` `docs/research/llm-world-sim-prior-art.md` at +`6b4a874` (cited below as *prior art (a)-(d)*); `F:\Projects\aetheria-story-sifting-prior-art.md`. + +## Why + +The operator's words govern (ruling `verse-sim-pipeline`): "we are running +Ghostlight with the Aetheria lore, then elaborating it, then running the world +with multi resolution gestalt. Aetheria provides an ontology and action grammar +that matches what we can represent in the game, so that we can easily visualize +and integrate the stories it generates. That narrative sim would run on a +schedule feeding the content pipelines as we guide it with our filthy meddling +(we will want tools for said meddling)." + +Ruling `aetheria-sim-first` makes this the campaign's priority, ahead of every +Dungeon affordance: "you build the API we need in Ghostlight and consume it from +Aetheria for world sim". The products are unchanged from revision 2 (ruling +`verse-cast-crossmedia`): storylets for the game, a serialized cast, and stories +a cheap cinematic pipeline can stream. + +Two later rulings set how the machine is built. `gestalt-machinery-port`: the +pre-rebuild multiresolution gestalt machinery "was solid, it just was never +ported", so it is ported, not redesigned. `teardown-was-authority-sprawl`: the +September teardown cut "authority like a hydra", the ad hoc mutation rules, and +discarded good ideas with them. The port brings the good ideas back onto the +grammar-governed kernel and leaves the ad hoc rules dead. The map sorts the old +tree (`f9f019b`) into both lists and names sixteen forced changes (F1-F16). + +The field agrees with that split. Every working LLM world simulation keeps world +state in a non-LLM store and maps free-text intent onto an enumerated, checked +action (prior art, "What the field converged on", items 1-2). Cost comes down by +calling the model less per agent per tick (item 3), which is what the gestalt +cover does. Replay comes from stored responses, not from seeds (item 5). + +## Phases + +1. **Honest and running** (revision 2, unchanged): the Dungeon lane closes its + phase-1 work in parallel. It does not gate the verse. +2. **The kernel can hold the verse.** The relation edge (F6) and the import key + enter the kernel. Aetheria's grammar is loaded as the world's affordance + catalog, and the kernel refuses an affordance kind the grammar does not + carry, along with any kind lacking a render path (`every-event-renderable`). +3. **The verse loaded.** Genesis from AetheriaLore and Aetheria's `Faction` + records, with factions as institutions, populations as gestalts and named + characters as persons. Then elaboration to the authored scale. +4. **The gestalt port and the run.** The cover, pins, debt, checkpointed + parallel cells, strategic individuation and fission, all on main's kernel. + A verse runner advances the world on a schedule within a budget, and every + run can be replayed. +5. **Meddling and the first reader.** The operator's Eve surface for + intervention, and the first pipeline consumer (question + `verse-first-consumer`). + +## Invariants + +The ends, not the means. Labels carried from revision 2 keep their meaning. + +- `honest-docs`: README, maps and handoffs describe the machine on `main`; history lives in history. +- `main-runs`: every deployed Ghostlight unit is built from `main` and stays up; Idunn owns its lifecycle. +- `kernel-decides`: the kernel owns world state through its closed operation set; models propose, the kernel admits or refuses, and no model decides an outcome. +- `grammar-governs`: every change to world state is a kernel command reduced through the closed operation set, and no writer exists beside it. That includes gestalt individuation, fission, away-time results and meddling. Changing simulation resolution (promotion, demotion, regrouping) is not a world change and writes nothing. The writers allowed after activation are listed by name (map model page): affordance invocations from Aetheria's grammar, clock motion, elaboration patches answering a derived boundary or deficit, and owner acts. Question `grammar-scope-of-mutation` decides whether the last two must also be grammar verbs. +- `resolution-not-identity`: the resolution at which a subject is simulated never changes who exists, what they hold or what they know. A person given up by a gestalt stays a person. +- `one-record-one-subject`: each imported lore or Aetheria record maps to exactly one subject or entity, and the mapping is checked when a patch is admitted. +- `verse-provenance`: every world fact loaded from the verse names its source page or record; a fact with no source is a proposal until reviewed. +- `canon-one-way`: simulation output never writes back into AetheriaLore or Aetheria's catalog without operator review. +- `readers-read`: a pipeline consumer (newspaper, episodes, storylets) reads committed history and writes only its own output. +- `cast-persists`: a character keeps identity, memory and commitments across runs, so episodes can follow them. +- `every-event-renderable`: every event the kernel admits maps to exactly one render path: ship action filmed in Aetheria, or a social verb staged as an Aetheria conversation. Summary captions are the only exception. +- `run-resumable`: an interrupted run resumes from its last checkpoint and commits nothing twice. +- `run-replayable`: a run re-executed from its recorded provider responses produces the same journal. +- `run-bounded`: a run never spends past its configured budget, and its fiction span, calls, tokens and cost are recorded. +- `dungeon-isolated`: a verse run cannot slow, stop or change Dungeon play. +- `meddling-attributed`: every operator intervention is a typed act, recorded with the operator as principal, through the same doors as every other writer. +- `typed-state`: world state, run records, the grammar and pipeline output are CultCache documents; exchange is CultNet; JSON only for schema publication and xenos boundaries. + +## Defaults (stated, not forks) + +- **The gestalt port follows the old design** at `f9f019b`, except where main's + ontology forces a change; the map names each one (F1-F16). Ported tests carry + the invariants listed there, not the old fixtures. +- **A relation edge enters the kernel** (F6): an `EdgeKind` beside `Route`, with + operations to form and dissolve it. Its kinds are Membership, Command, + Alliance, Rivalry, Trade, Coercion and Migration. This was revision 2's + phase-2 relation work. +- **Separate verse process.** The verse runner and its Eve surface run as their + own daemon (binary crate `ghostlight-verse`), not inside + `ghostlight-dungeon`. The process is earned by `dungeon-isolated`: a different + consumer, model, budget and failure domain (ruling `two-ghostlight-consumers`). + The runner logic is library code in `crates/ghostlight`, beside `cover.rs` and + `clock.rs`, so Dungeon can drive it later. +- **Idunn** deploys the verse daemon, keeps it alive and starts runs on the + schedule. The runner owns what a run does, and the kernel owns time. Whether + Idunn offers a schedule trigger today is unverified; if it does not, that + belongs to Idunn as a follow-up, and no in-daemon timer stands in for it. +- **Inference lane.** The verse binds the provider-portable connector lane + (CodexConnector's library transport, unbound in Dungeon) to the cheapest + hosted model, DeepSeek first (ruling `two-ghostlight-consumers`). Models are + set per role. +- **Replay record.** Each provider call's request hash, response body, model, + token counts and cost are stored per run. The kernel draws no randomness + (outcome bands come from a digest), so stored responses are enough for replay + (prior art (c), "Determinism and replay"). +- **Exchange.** Aetheria reads what the verse publishes (world snapshot, history, + pipeline output) as CultNet documents through CultMesh. This is the outbound + consumer read that `ghostlight-world-consumer-api.md` names as "not in this + pass". The loopback, write-only consumer door is not how Aetheria reads. +- **Meddling is an operator interface**, so doctrine fixes its shape. The verse + daemon publishes an Eve/CultUI composition graph over CultMesh, lowered to GUI + for the operator and to TUI for agents. Each control is a typed CultNet + command. A world-changing control becomes a kernel command under the owner + principal (`meddling-attributed`, `grammar-governs`). A run control (budget, + pins, focus, cadence, pause) becomes a revision of the run record at a tick + boundary, with an epoch, which is the old `ResolutionControlReceipt` rule. + There is no separate web dashboard and no tool that edits `.cc` files or the + journal directly. What it does first is question `meddling-first-acts`. +- **The newspaper ports as a reader.** The old newsroom, editorial agenda, + citations and copy desk become a pipeline consumer bound by `readers-read`. + Whether it is the first consumer is question `verse-first-consumer`. + +## Not in scope + +- Dungeon affordances: one-step Begin, vault seeding under Begin, the create + flow, Dungeon's play surface and Bonsai (ruling `aetheria-sim-first`). They + continue in the Dungeon lane and do not gate this target. +- The ad hoc mutation rules of the pre-rebuild tree (map, "Ad hoc rule" table): + the outcome resolver, semantic verifiers and quotas, the presence planner as + a decider, arena knowledge union, clock-consequence binding, prompt-only + prohibitions. +- The video and stream pipeline, episode editing, and the people-in-rooms scene + grammar (ruling `scene-grammar-people-in-rooms`; a later campaign). +- Storylet weaving, radio delivery and any other runtime code in Aetheria (the + `aetheria-release` campaign). Only the grammar document is authored on + Aetheria's side, if question `verse-grammar-home` puts it there. +- Counterfactual branching of a baseline over player-action archetypes, the + "branch ... and propagate" step of ruling `storylets-simulate-sift-lift-weave`. + The journal must not preclude forking a world at a committed revision, but no + fork operation is built here. +- A live Ghostlight running beside the game (fork 4 of Aetheria's + `reactive-world-target.md`). +- The failed `idunn-odin-*` and `idunn-streampixels-*` units on Yggdrasil. + +## Canonical implementations + +- Kernel, cover, clock, relation edge, import key and the verse runner library: + `GameCult/Ghostlight` `crates/ghostlight`. +- The verse daemon and its Eve surface: `GameCult/Ghostlight` + `crates/ghostlight-verse`. +- The port source: `GameCult/Ghostlight` at `f9f019b`, + `crates/ghostlight-dungeon/src/` (`domain.rs`, `resolution.rs`, + `scheduler.rs`, `gestalt.rs`, `persona.rs`, `newspaper.rs`, the tick driver in + `main.rs`). +- The grammar: where question `verse-grammar-home` puts it. +- Deployment and run start: `GameCult/Idunn`, with the recipe in the Ghostlight + repo. + +## Questions for the operator + +Admitted to the mind in one batch; query them there. `verse-grammar-home`, +`grammar-scope-of-mutation`, `verse-first-consumer`, `verse-run-cadence-budget`, +`meddling-first-acts`, `verse-world-canonical-or-seeded`, +`gestalt-fission-approval`. From 5f5f23f909c906de68a8f5a764f679627e5a9756 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 10 Oct 2026 06:14:00 +0200 Subject: [PATCH 3/5] Verse sim: target r3 and phase A cut order Imagination pass (session self-2026-10-10-ag) folds the seven 2026-10-10 verse-sim rulings into target revision 3, fills the model page cells they settle, and records phase A's body facts and cut order. The r3 draft is folded into the target. Co-Authored-By: Claude Opus 5.5 --- notes/ghostlight-verse-map.md | 130 +++++++++++++--- notes/ghostlight-verse-target-r3-draft.md | 167 -------------------- notes/ghostlight-verse-target.md | 178 ++++++++++++++++++---- 3 files changed, 257 insertions(+), 218 deletions(-) delete mode 100644 notes/ghostlight-verse-target-r3-draft.md diff --git a/notes/ghostlight-verse-map.md b/notes/ghostlight-verse-map.md index d2e508e..84c1987 100644 --- a/notes/ghostlight-verse-map.md +++ b/notes/ghostlight-verse-map.md @@ -375,7 +375,7 @@ Imagination `imagination-gl-verse-sim-0b`, session `self-2026-10-10-ag`, `two-ghostlight-consumers`, `verse-verbs-two-render-paths`, `verse-cast-crossmedia`, `action-scenes-in-game` and `aetheria-release:ruling:storylets-simulate-sift-lift-weave`. Draft target: -`notes/ghostlight-verse-target-r3-draft.md`. Rebuilt kernel read at +`notes/ghostlight-verse-target.md` (revision 3, which absorbed the draft). Rebuilt kernel read at `origin/main` `367e2eb`. ### Where the old machine is @@ -408,7 +408,7 @@ makes unavoidable; everything else ports as designed. | Resolution cover (`resolution.rs` 5,505 lines: `plan_cover`, `validate_cover`, `advance_detail_debt`, merge loss; `domain.rs:345-497`) | budgeted partition into `SimulationCell`s (`Cohesive` or `Arena`), seven-term `MergeLoss`, `ResolutionPolicy` (cell budget 1-240, provider parallelism, epochs), `ResolutionPin` (KeepTogether, KeepSeparate, MinimumIndividualDetail), `ResolutionDemand` (axis weights, focal subjects, horizon), cell leases, causal follow-through windows | the old campaign record as the store for policy, pins, leases and debt | `cover.rs` (1,116 lines): pure `derive_cover`, content-addressed cell ids under `ghostlight.cover.v1`, `TickIndex` derived from the clock, debt rotation with urgency slots | **F9** the cover never enters `WorldState` (cover.rs:6-9). Policy, pins, leases and demand move to the verse runner's own run record, not the kernel. **F10** an `Arena` cell presents partitioned views, never a knowledge union (ontology "Scale" item 4); the old arena knowledge union does not port. `Cohesive` ports as the population subject acting for itself. The merge-loss terms port onto main's partitioner as its grouping cost. | | Strategic wave pipeline (`scheduler.rs` 3,336 lines, `persona.rs` 6,540 lines cell projection) | demand projection by a cheap model; per-cell Projector → Persona → Interpreter membrane; `CellActionProposal` and attributed `CellInaction`; parallel dispatch under one provider gate; `ResolutionWaveCheckpoint` resuming only failed cells | `ModelPort` and the old persona stack (`persona.rs`) | `controllers.rs` (`NarrativePersona`, `OperationalAgent`, Projector/Interpreter), `RoutedInferencePort`, `WorldMailbox` role ports, `ExerciseDecision`/`DeclineDecision` commands | **F11** a cell's output is one affordance invocation or decline per constituent, each admitted separately against its scope digest (invariants 7, 10); there is no wave-level atomic commit. A refused grouped constituent stays refused (ontology "Scale"). | | Outcome resolution (`outcome.rs` 4,387 lines; selective semantic verifier) | a batched model resolver returns a typed result per strategic attempt; a same-snapshot verifier advises on risky effects | model-decided outcomes | outcome bands on affordances, chosen by kernel digest (`BandPreimage`), no RNG | **F12** not ported. The kernel decides outcomes (target invariant `kernel-decides`, ontology invariant 7). Strategic activities, institution postures and migrations become catalog affordances (from Aetheria's grammar) whose effects compile to the 34 `ComponentOp`s: posture to `CreateCommitment`, migration to `Relocate`. | -| Fission (`compiler.rs` `GestaltFissionRequest`, `domain.rs:716` `GestaltFissionPreview`, `FissionGestalt` command) | approval-gated split of a population along one axis, member deltas assigned to one child, lineage kept | the `FissionGestalt` kernel command; lineage as its own record | elaboration patches (`submit_elaboration`); `Retire` | **F13** fission is one elaboration patch: declare child populations, Membership edges (F6), relocate each named member's membership, retire or keep the parent. Lineage is the Membership/Command edges plus the commit, not a separate record. The operator approval gate ports unchanged (see question `gestalt-fission-approval`). | +| Fission (`compiler.rs` `GestaltFissionRequest`, `domain.rs:716` `GestaltFissionPreview`, `FissionGestalt` command) | approval-gated split of a population along one axis, member deltas assigned to one child, lineage kept | the `FissionGestalt` kernel command; lineage as its own record | elaboration patches (`submit_elaboration`); `Retire` | **F13** fission is one elaboration patch: declare child populations, Membership edges (F6), relocate each named member's membership, retire or keep the parent. Lineage is the Membership/Command edges plus the commit, not a separate record. **F17** (ruling `gestalt-fission-auto`) the old operator approval gate does not port: a fission answering a derived boundary is admitted during the run and listed in the run record for the operator to see afterwards. | | Tick driver and away time (`main.rs` `scheduler_loop`, `process_due_ticks`, `advance_one_strategic_tick`; `scheduler.rs:1943` `due_tick_target`) | a background loop on 60 s and 300 s pulses; one strategic tick per idle hour after 15 min, capped at 8 (`away_budget_waits_and_caps_at_eight`); return catch-up before a player's fictional command; yields to live turns; `AdvanceStrategicTick` commits atomically; worlds with no simulatable agency tick without a model | a player and their idleness; the `AdvanceStrategicTick` command; the in-daemon timer | `ClockPort::advance_time` (mailbox.rs:688), `clock.rs` `derive_motion` (routines auto-fulfil, overdue obligations and goals raise pressure, zero inference) | **F14** a verse world has no player, so "away time" is the schedule itself: a run advances N ticks of a configured fiction span. Not ontology-forced; forced by the consumer. **F15** each tick is: derive cover, run cells, submit each proposal, then `AdvanceTime` (cover.rs:43-45 states this order); there is no `AdvanceStrategicTick`. | | Clock consequences (`clock.rs` 739 lines, `ClockConsequenceBinding`) | a model bound due clocks to agent actions | a model stage on the clock | `clock.rs` `derive_motion`, zero inference | **F16** not ported; main's deterministic motion replaces it. | | Model stage receipts (`persona_stage_receipt.v1`, written by `advance_one_strategic_tick`) | per-stage receipt keyed by hash, collisions refused | the old store | `LocalInferenceReceipt`/`SdkInferenceReceipt` (request hashes, token counts) | Not forced. Main's receipts carry hashes but not the response body, and nothing found persists them; replay needs the response stored (prior art (c), "Determinism and replay"). | @@ -424,7 +424,7 @@ These are the only cost evidence for a cadence and budget ruling. Ruling `teardown-was-authority-sprawl` sorts the old tree: the rows above are the good ideas to port (gestalts, multiresolution cover, pins and debt, away-time as a driver, checkpointed parallel waves, attributed inaction, -strategic individuation, fission with approval, the newspaper path, stage +strategic individuation, fission, the newspaper path, stage receipts). The ad hoc mutation rules stay dead. Each has a replacement on main: @@ -484,29 +484,123 @@ invariant `grammar-governs` in the draft target. An empty cell is marked | Kind | Identity (namespace; injective?) | Lifecycle | Authority (owner; forbidden writers) | |---|---|---|---| -| Verse grammar: Aetheria's ontology and action grammar (subject kinds, place kinds, faction vocabulary, affordance kinds each tagged with one render path, social verbs) | **EMPTY** namespace: no document, schema or repo holds it today. Proposed: a CultCache schema id plus a revision, and the world binds the grammar's content digest, which is injective. | **EMPTY** beyond creation: authored, revised; how a running world moves to a new grammar revision is unanswered (main refuses earlier schemas and never migrates, world-consumer-api.md:184-190) | Owner proposed: Aetheria, which knows what it can render (ruling `verse-sim-pipeline`). Where it lives and in what format: question `verse-grammar-home`. Forbidden writers: Ghostlight elaborators and the play authority (the catalog is input to genesis, not something a run invents); the kernel today lets the owner declare any affordance kind (map "Where phase 2's verb sets plug in"), so nothing enforces this yet. | +| Verse grammar: Aetheria's ontology and action grammar (verbs, each with one render path and its participant roles) | `aetheria.verse_grammar` (CultGlobal, one per catalog, carries the revision) and `aetheria.verse_verb` keyed by its canonical kind name, in Aetheria's catalog `GameData/Aetheria.cc` (ruling `verse-grammar-home-ruled`). A world binds (schema id, revision, sha256 over the verbs' canonical encoding sorted by kind): injective per content. | authored and revised through an AetherDb command; a world keeps the revision it was created under; moving a running world to a new revision is not designed (main never migrates), so a revision means a new world until a migration is needed | Owner: Aetheria, for which verbs exist, how each renders and who takes part. Ghostlight owns what each verb does to world state (question `verb-effects-owner`). Forbidden writers: Ghostlight elaborators, the play authority and every run; the kernel refuses a kind or role signature the bound grammar lacks (cut `grammar-binding`). | | Lore source (AetheriaLore pages, Aetheria `Faction` records) | page path + AetheriaLore commit; `aetheria.faction` CultDocument key + Aetheria commit. Injective per commit. | read at genesis and elaboration; never written by the sim | Owners: AetheriaLore and Aetheria. Forbidden writers: every sim organ (target invariant `canon-one-way`). | | Verse world (journal and state) | `WorldId`, reducer-issued; `revision` and digest chain | created, approved, activated; one commit per admitted command; never forked today | The kernel, through its closed operation set. Forbidden writers: the runner, the Eve surface and meddling tools except through kernel commands. | | Elaborated entities: places, resources, facts, channels, routes | `EntityId`/`EdgeId`, reducer-issued; injective | admitted by patch; retired; facts may be Canonical, Claimed or Ruled | Kernel admits; elaborators and the owner propose. Lore-derived facts carry their source (`verse-provenance`). | -| Import key: lore record ↔ Ghostlight id (which subject *is* faction X) | **EMPTY**: labels never resolve references (ontology "Identity"), and nothing records or checks that one Aetheria faction key maps to exactly one subject | must survive re-elaboration and any grammar revision | **EMPTY** owner. Proposed: the genesis patch writes one Canonical fact per imported record citing its key, and admission refuses a second subject citing the same key. Not on main. | +| Import key: Aetheria record or lore page ↔ Ghostlight id | `(schema id, record key)` for a catalog record (`aetheria.faction/`); `(vault-relative page path)` for a lore page (phase B). Injective: admission refuses a second subject or entity carrying a key already held. The Aetheria commit is on the world's genesis command, not in the key, so the key survives re-reading a newer catalog. | written once, by the declaration that creates the subject or entity; never changed; retiring the subject retires the key with it, and the key is never reissued | Kernel admission (cut `import-key`). Writers: genesis and elaboration patches only (ruling `grammar-scope-ruled`). | | Factions (Aetheria factions as actors) | `SubjectId`, kind `Institution` | declared at genesis from `aetheria.faction`; act through offices and catalog affordances; can be retired | Kernel; the faction's `OperationalAgent` controller proposes. Needs relations (port F6) for alliance and rivalry. | -| Gestalts (populations at each resolution) | `SubjectId`, kind `Population` (F1, F2) | declared at genesis or elaboration; migrate by `Relocate`; split by fission (F13); retired | Kernel admits; elaborators propose fission; operator approves fission (question `gestalt-fission-approval`). Forbidden writers: the cover and the runner (they choose resolution, never identity). | +| Gestalts (populations at each resolution) | `SubjectId`, kind `Population` (F1, F2) | declared at genesis or elaboration; migrate by `Relocate`; split by fission (F13); retired | Kernel admits. Fission is an elaboration patch answering a derived boundary, one of the named non-verb writers (ruling `grammar-scope-ruled`), admitted during the run without waiting (ruling `gestalt-fission-auto`). The operator sees it afterwards: the run record lists each fission commit, and the meddling surface shows it in the run history. Forbidden writers: the cover and the runner (they choose resolution, never identity). | | Individuated members (the people gestalts give up) | `SubjectId`, kind `Person`, with a Membership edge to the population (F3, F6) | born by an elaboration patch answering `IndividuationRequired` or a strategic individuation; never folded back (F4) | Kernel admits; the elaborator or the cell proposes; duplicates of an established identity refused (ported test). | | How a region moves between resolutions | derived, no identity of its own: a cell id is a content hash under `ghostlight.cover.v1` | recomputed every tick from pins, budget, pressure and debt; a leased cell survives while still connected (old lease rule) | `derive_cover` (pure); pins and budget from the run record; promotion and demotion are only cell assignment (F4, F9). Forbidden writers: the kernel (never sees a cell), any model. | -| Cast (characters the audience follows) | **EMPTY**: no component or record marks a subject as cast | **EMPTY**: who joins or leaves the cast, and when | **EMPTY** owner. A cast member is a `Person` subject (target invariant `cast-persists`); curating the roster is an operator act (question `meddling-first-acts` lists it). The old `MinimumIndividualDetail` pin is how the cast keeps detail focus. | -| Verse run (one scheduled invocation) | **EMPTY** namespace on main. Proposed: (`WorldId`, run sequence), sequence assigned by the runner. | scheduled → running → per-tick checkpoint → completed, failed or stopped; a failed run resumes from its last checkpoint | Owner: the verse runner. Idunn owns when it starts and process survival. Forbidden writers: Dungeon's play loop, an in-daemon timer, the Eve surface. Cadence and budget: question `verse-run-cadence-budget`. | +| Cast (characters the audience follows) | a `Person` `SubjectId` named in the run record's cast roster; no kernel component (being followed changes attention, not the world) | joins or leaves by an operator steering act at a tick boundary (ruling `meddling-first-ruled`); a member who is retired in the world stays in the roster's history | Owner: the operator, through run controls. The cover keeps roster members at singleton resolution (the old `MinimumIndividualDetail` pin). Forbidden writers: models and the runner. Lands in phase D; phase A has no persons. | +| Verse run (one invocation) | `(WorldId, run sequence)`, sequence assigned by the runner, document `ghostlight.verse_run.v1` | started manually under a token cap (ruling `verse-cadence-ruled`) → per-tick checkpoint → Completed, BudgetExhausted, Failed or Stopped; a failed run resumes from its last checkpoint; nightly scheduling arrives after cost per tick is measured | Owner: the verse runner (cut `verse-run`). Idunn will own scheduled start and process survival (phase D). Forbidden writers: Dungeon's play loop, an in-daemon timer, the Eve surface. | | Tick | `TickIndex`, derived from the world clock, never stored (cover.rs:33-38) | derive cover, run cells, submit proposals, then `AdvanceTime` (F15); a resumed tick re-derives the same command ids, so the kernel's idempotency ledger answers with the original receipts | Kernel owns time; the runner owns the order. | -| Run record: policy, pins, demand, leases, checkpoints | **EMPTY** on main; old shapes `ResolutionPolicy`, `ResolutionPin`, `ResolutionWaveCheckpoint` | policy and pins changed by operator act at a tick boundary (old epoch rule); checkpoints per tick, superseded | Runner. Forbidden writers: the kernel. | +| Run record: policy, pins, demand, roster, checkpoints, spend | inside `ghostlight.verse_run.v1`; phase A carries cap, tick span, cover budget, checkpoints and spend; pins, demand and roster arrive with phase C-D | policy and pins revised by operator act at a tick boundary with an epoch (old `ResolutionControlReceipt` rule); checkpoints per tick, superseded | Runner. Forbidden writers: the kernel. | | Model proposal and its validation | derived command id (world, tick, cell, constituent), so injective per tick | proposed; admitted, or refused (`ScopeChanged`, precondition, ceiling); a narrative singleton may re-lower once (`interrupted_from`) | Kernel reducer decides. Forbidden writers: an outcome resolver or verifier model (F12). | -| Provider call record (for replay and cost) | request hash (main's receipts) | **EMPTY**: main's receipts are not persisted and hold no response body | Owner proposed: the inference port writes, the runner stores per run. Replay re-feeds stored responses into the deterministic kernel. | -| Pipeline output: newspaper issues, episodes, storylets | **EMPTY** on main. The old newspaper had `ghostlight.world_newspaper_issue.v3` keyed by a publication task binding (port row above); episodes and storylets have no kind anywhere. The commit journal's `DecisionEvent`s are the raw history; `operator_log` is owner-only. | newspaper: composed, checkpointed at press close, persisted, never edited; episodes and storylets **EMPTY** | Owner: each pipeline reader owns its own output. **EMPTY** for episodes and storylets. First consumer: question `verse-first-consumer`. Forbidden writers: every reader may write only its own output, never the world (`canon-one-way`, `grammar-governs`). | -| Operator meddling act | kernel commands carry an idempotency key and the owner principal; run controls carry the run id and an epoch | committed to the journal (world acts) or the run record (controls); never edited afterwards | Owner: the operator, through the verse Eve surface. Forbidden: any tool that writes `.cc` files or the journal directly. What it does first: question `meddling-first-acts`. | +| Provider call record (for replay and cost) | `(run id, call sequence)`, document `ghostlight.verse_provider_call.v1`, carrying the provider request sha256, model, response events and token usage | written once per call by the recording port before its output reaches a lane; never edited; replay serves it back by request sha256 | Owner: the recording port in front of the inference lane (cut `call-record`), which also owns the run's spend and refuses a call whose bound exceeds the remaining cap. Forbidden writers: lanes and the runner. | +| Pipeline output: newspaper issues; later episodes and storylets | newspaper: the old `ghostlight.world_newspaper_issue.v3` shape at `f9f019b`, keyed by world and the commit range it covers (ruling `verse-first-consumer-ruled`); episodes and storylets have no kind yet and are outside this target's phases | newspaper: composed, checkpointed at press close, persisted, never edited | Owner: each reader owns its own output (`readers-read`). Forbidden writers: every reader may write only its own output, never the world. Lands in phase D. | +| Operator meddling act | world acts: kernel commands with an idempotency key and the owner principal; steering acts: run-record revisions with the run id and an epoch | committed to the journal (inject) or the run record (steer); never edited afterwards | Owner: the operator, through the verse Eve surface (ruling `meddling-first-ruled`: steer first, then inject). Forbidden: any tool that writes `.cc` files or the journal directly. Lands in phase D. | ### Cells that could not be filled -- Verse grammar: no namespace, no home, no format, no revision rule. -- Import key: no owner and no injectivity check between Aetheria records and subjects. -- Cast: no identity, lifecycle or owner. -- Verse run and run record: no namespace or store on main (the old shapes exist at `f9f019b`). -- Provider call record: nothing persists a response for replay. -- Pipeline output: episodes and storylets have no kind and no owner; the newspaper has an old design at `f9f019b` and nothing on main. +Revision 3's rulings settle every cell the 0b pass left empty except these: + +- Verse grammar, lifecycle: how a running world moves to a new grammar revision. Not needed until a grammar revision meets a world worth keeping; a new world is the answer until then. +- Episodes and storylets: no kind and no owner. Both are outside revision 3's phases. + +## Verse sim: phase A body facts (probed 2026-10-10) + +Imagination `imagination-gl-verse-sim-cuts`, session `self-2026-10-10-ag`. Pinned to +Ghostlight `origin/main` `367e2eb` and Aetheria `origin/master` `b623d51`. Each line is +a source read. + +- **A1 Aetheria content is one CultCache catalog.** `AetheriaStores.Open` + (`Assets/Scripts/ServerShared/AetheriaStores.cs:9`) registers `CatalogTypes` in one + `SingleFileMessagePackBackingStore` over `GameData/Aetheria.cc` (LFS, + `.gitattributes` `GameData/*.cc`). A `[CultGlobal]` catalog type with no record makes + `Open` throw ("catalog globals are authored, never invented"), so a new global and + its record land together. Content is authored by `tools/AetherDb` commands with an + `apply` switch (`Program.cs:25-48`, `PiratesFaction` at 1848 is the newest pattern). +- **A2 Factions are `aetheria.faction` v1** (`Corporations.cs:13-59`): name, short + name, description, personality weights, colours, name file, boss hull, influence, + and `Allegiance`, a map of faction refs to weights. No home place and no members. +- **A3 What the game can film today** is Aetheria's `TaskType` (`Agents/Tasks/AgentTask.cs:24-32`): + Mine, Haul, Tow, Defend, Attack, Explore. That is the ship-action half of the starter + grammar. The conversation half on Ghostlight's side is the kernel-built `speak` + (`patch.rs:2396-2415`): zero roles, colocated audience, carries speech. +- **A4 Ghostlight reads CultCache single files already.** The workspace pins + `cultcache-rs` at CultLib `85f7024`, which carries `SingleFileMessagePackBackingStore` + (`packages/cultcache-rs/src/lib.rs:305`). `crates/ghostlight` depends on it + (`Cargo.toml`), today only for the redb controller-work store. +- **A5 Affordance kinds are open text.** `AffordanceKindName` (`patch.rs:247-254`) is + checked for shape and per-patch uniqueness only (`patch.rs:3135-3151`). Genesis + re-derives its patch through `resolve_patch` (`lib.rs:1939-1993`), so a check inside + resolution holds for genesis and for every later patch alike. +- **A6 World creation is Dungeon-shaped.** `WorldMailbox::create` (`mailbox.rs:105`) + lowers `CreateWorldIntent` (`lib.rs:433-452`), which requires a human subject and + declares one commons where every genesis subject stands. A verse genesis has no + human, so it needs its own intent; it reuses the same `CreateWorld` command + (`lib.rs:414-428`) and `resolve_patch`. +- **A7 The tick driver's parts exist; the driver does not.** `derive_cover` + (`cover.rs:263`), `ControllerRunner::run_cell` (`controllers.rs:3266`, singleton and + grouped cells, derived command ids so a resumed tick is idempotent), + `WorldMailbox::agency_graph` (`mailbox.rs:449`, reserved to the tick driver) and + `PlayPort::advance_time` (`mailbox.rs:688`). Nothing on main calls them in a loop. +- **A8 No lane reaches a hosted cheap model.** The connector lane is CodexConnector, + which runs the Codex executable for GPT models (its daemon manifest allows + `gpt-5.6-*`) and returns no token usage. The local lane speaks OpenAI-compatible + chat completions (`local_inference.rs`), parses `usage`, but refuses any + non-loopback endpoint in its constructor (`local_inference.rs:167`) and carries no + credential. DeepSeek's API is OpenAI-compatible, so the hosted lane is a second + binding of the local port. +- **A9 Token usage stops at the port.** `LocalInferenceReceipt` holds prompt and + completion tokens (`local_inference.rs:42-55`, built at 347-370), but + `InferenceOutput` (`controllers.rs:155-159`) carries only events and a receipt + digest, so nothing above a port can count spend. +- **A10 Principal evidence is constructible.** `VerifiedPrincipalEvidence::new` is + public (`lib.rs:160`); the Dungeon builds it from a Heimdall session. A phase-A CLI + run by the operator on the host builds it from a configured account subject hash. + +## Phase A cut order + +Phase A delivers the first watched run: the operator invokes +`ghostlight-verse run` on Yggdrasil under a token cap and reads, per tick, what each +Aetheria faction did in grammar verbs and what it cost. Specs in `depends_on` order +(admission status below the list): + +1. `verse-grammar` (Aetheria): the two document types, validation, catalog + registration and the AetherDb command that authors the starter grammar. +2. `grammar-binding` (Ghostlight kernel): a world binds a grammar at genesis; the + resolver refuses kinds and role signatures it lacks. No IO. +3. `import-key` (Ghostlight kernel): the subject and entity import key and its + injectivity check. No IO. +4. `grammar-reader` (Ghostlight): reads Aetheria's catalog file, yields the grammar + binding and the faction records; fixture written by Aetheria's own types. +5. `hosted-lane` (Ghostlight): the local port's hosted binding and token usage on + `InferenceOutput`. +6. `call-record` (Ghostlight): the recording port, call records, the cap at the + call door, and the replay port. +7. `verse-genesis` (Ghostlight): the verse creation intent: factions as institutions + under import keys, grammar verbs lowered to affordances, the world bound. +8. `verse-run` (Ghostlight): the run record, the tick loop and the + `ghostlight-verse` binary with `genesis` and `run`. + +Phase A leaves out, on purpose: relations and allegiances (F6, phase B), lore +elaboration (phase B), persons and the cast (phase B-D), pins, debt, fission (phase +C), the Eve surface, Idunn scheduling and the newspaper (phase D). Verb effects are +empty in phase A: a ship verb records the act as a decision event and changes no +component, the same under either answer to question `verb-effects-owner`. + +Admission, 2026-10-10: `grammar-binding`, `import-key`, `hosted-lane` and +`call-record` are in the mind. `verse-grammar` was refused `RepoNotInCampaign` +(`GameCult/Aetheria` is not in the campaign's repos), and `grammar-reader` was then +refused `UnknownDependency` (`verse-grammar`), which holds `verse-genesis` and +`verse-run` behind it. The four held specs are written in full, anchors checked +SAME at their bases, outside the repo; they are admitted once the campaign lists +`GameCult/Aetheria`. + +Rationale for the order: the two kernel cuts are pure and land before anything that +reads a file; the reader's fixture needs Aetheria's types, so it waits on +`verse-grammar`; spend has one owner, the recording port, so the cap is built +before the runner and the runner never counts tokens itself. diff --git a/notes/ghostlight-verse-target-r3-draft.md b/notes/ghostlight-verse-target-r3-draft.md deleted file mode 100644 index bb65c15..0000000 --- a/notes/ghostlight-verse-target-r3-draft.md +++ /dev/null @@ -1,167 +0,0 @@ -# Ghostlight Verse: Target, revision 3 (draft) - -Draft, 2026-10-10. Imagination `imagination-gl-verse-sim-0b`, session -`self-2026-10-10-ag`. Not admitted: Self admits revision 3 after the operator -rules on the questions named below. Supersedes `notes/ghostlight-verse-target.md` -(revision 2) where they differ. Evidence: the port inventory and model page in -`notes/ghostlight-verse-map.md` ("Verse sim: port inventory and model page"); -`F:\Projects\gamecult-ops` `docs/research/llm-world-sim-prior-art.md` at -`6b4a874` (cited below as *prior art (a)-(d)*); `F:\Projects\aetheria-story-sifting-prior-art.md`. - -## Why - -The operator's words govern (ruling `verse-sim-pipeline`): "we are running -Ghostlight with the Aetheria lore, then elaborating it, then running the world -with multi resolution gestalt. Aetheria provides an ontology and action grammar -that matches what we can represent in the game, so that we can easily visualize -and integrate the stories it generates. That narrative sim would run on a -schedule feeding the content pipelines as we guide it with our filthy meddling -(we will want tools for said meddling)." - -Ruling `aetheria-sim-first` makes this the campaign's priority, ahead of every -Dungeon affordance: "you build the API we need in Ghostlight and consume it from -Aetheria for world sim". The products are unchanged from revision 2 (ruling -`verse-cast-crossmedia`): storylets for the game, a serialized cast, and stories -a cheap cinematic pipeline can stream. - -Two later rulings set how the machine is built. `gestalt-machinery-port`: the -pre-rebuild multiresolution gestalt machinery "was solid, it just was never -ported", so it is ported, not redesigned. `teardown-was-authority-sprawl`: the -September teardown cut "authority like a hydra", the ad hoc mutation rules, and -discarded good ideas with them. The port brings the good ideas back onto the -grammar-governed kernel and leaves the ad hoc rules dead. The map sorts the old -tree (`f9f019b`) into both lists and names sixteen forced changes (F1-F16). - -The field agrees with that split. Every working LLM world simulation keeps world -state in a non-LLM store and maps free-text intent onto an enumerated, checked -action (prior art, "What the field converged on", items 1-2). Cost comes down by -calling the model less per agent per tick (item 3), which is what the gestalt -cover does. Replay comes from stored responses, not from seeds (item 5). - -## Phases - -1. **Honest and running** (revision 2, unchanged): the Dungeon lane closes its - phase-1 work in parallel. It does not gate the verse. -2. **The kernel can hold the verse.** The relation edge (F6) and the import key - enter the kernel. Aetheria's grammar is loaded as the world's affordance - catalog, and the kernel refuses an affordance kind the grammar does not - carry, along with any kind lacking a render path (`every-event-renderable`). -3. **The verse loaded.** Genesis from AetheriaLore and Aetheria's `Faction` - records, with factions as institutions, populations as gestalts and named - characters as persons. Then elaboration to the authored scale. -4. **The gestalt port and the run.** The cover, pins, debt, checkpointed - parallel cells, strategic individuation and fission, all on main's kernel. - A verse runner advances the world on a schedule within a budget, and every - run can be replayed. -5. **Meddling and the first reader.** The operator's Eve surface for - intervention, and the first pipeline consumer (question - `verse-first-consumer`). - -## Invariants - -The ends, not the means. Labels carried from revision 2 keep their meaning. - -- `honest-docs`: README, maps and handoffs describe the machine on `main`; history lives in history. -- `main-runs`: every deployed Ghostlight unit is built from `main` and stays up; Idunn owns its lifecycle. -- `kernel-decides`: the kernel owns world state through its closed operation set; models propose, the kernel admits or refuses, and no model decides an outcome. -- `grammar-governs`: every change to world state is a kernel command reduced through the closed operation set, and no writer exists beside it. That includes gestalt individuation, fission, away-time results and meddling. Changing simulation resolution (promotion, demotion, regrouping) is not a world change and writes nothing. The writers allowed after activation are listed by name (map model page): affordance invocations from Aetheria's grammar, clock motion, elaboration patches answering a derived boundary or deficit, and owner acts. Question `grammar-scope-of-mutation` decides whether the last two must also be grammar verbs. -- `resolution-not-identity`: the resolution at which a subject is simulated never changes who exists, what they hold or what they know. A person given up by a gestalt stays a person. -- `one-record-one-subject`: each imported lore or Aetheria record maps to exactly one subject or entity, and the mapping is checked when a patch is admitted. -- `verse-provenance`: every world fact loaded from the verse names its source page or record; a fact with no source is a proposal until reviewed. -- `canon-one-way`: simulation output never writes back into AetheriaLore or Aetheria's catalog without operator review. -- `readers-read`: a pipeline consumer (newspaper, episodes, storylets) reads committed history and writes only its own output. -- `cast-persists`: a character keeps identity, memory and commitments across runs, so episodes can follow them. -- `every-event-renderable`: every event the kernel admits maps to exactly one render path: ship action filmed in Aetheria, or a social verb staged as an Aetheria conversation. Summary captions are the only exception. -- `run-resumable`: an interrupted run resumes from its last checkpoint and commits nothing twice. -- `run-replayable`: a run re-executed from its recorded provider responses produces the same journal. -- `run-bounded`: a run never spends past its configured budget, and its fiction span, calls, tokens and cost are recorded. -- `dungeon-isolated`: a verse run cannot slow, stop or change Dungeon play. -- `meddling-attributed`: every operator intervention is a typed act, recorded with the operator as principal, through the same doors as every other writer. -- `typed-state`: world state, run records, the grammar and pipeline output are CultCache documents; exchange is CultNet; JSON only for schema publication and xenos boundaries. - -## Defaults (stated, not forks) - -- **The gestalt port follows the old design** at `f9f019b`, except where main's - ontology forces a change; the map names each one (F1-F16). Ported tests carry - the invariants listed there, not the old fixtures. -- **A relation edge enters the kernel** (F6): an `EdgeKind` beside `Route`, with - operations to form and dissolve it. Its kinds are Membership, Command, - Alliance, Rivalry, Trade, Coercion and Migration. This was revision 2's - phase-2 relation work. -- **Separate verse process.** The verse runner and its Eve surface run as their - own daemon (binary crate `ghostlight-verse`), not inside - `ghostlight-dungeon`. The process is earned by `dungeon-isolated`: a different - consumer, model, budget and failure domain (ruling `two-ghostlight-consumers`). - The runner logic is library code in `crates/ghostlight`, beside `cover.rs` and - `clock.rs`, so Dungeon can drive it later. -- **Idunn** deploys the verse daemon, keeps it alive and starts runs on the - schedule. The runner owns what a run does, and the kernel owns time. Whether - Idunn offers a schedule trigger today is unverified; if it does not, that - belongs to Idunn as a follow-up, and no in-daemon timer stands in for it. -- **Inference lane.** The verse binds the provider-portable connector lane - (CodexConnector's library transport, unbound in Dungeon) to the cheapest - hosted model, DeepSeek first (ruling `two-ghostlight-consumers`). Models are - set per role. -- **Replay record.** Each provider call's request hash, response body, model, - token counts and cost are stored per run. The kernel draws no randomness - (outcome bands come from a digest), so stored responses are enough for replay - (prior art (c), "Determinism and replay"). -- **Exchange.** Aetheria reads what the verse publishes (world snapshot, history, - pipeline output) as CultNet documents through CultMesh. This is the outbound - consumer read that `ghostlight-world-consumer-api.md` names as "not in this - pass". The loopback, write-only consumer door is not how Aetheria reads. -- **Meddling is an operator interface**, so doctrine fixes its shape. The verse - daemon publishes an Eve/CultUI composition graph over CultMesh, lowered to GUI - for the operator and to TUI for agents. Each control is a typed CultNet - command. A world-changing control becomes a kernel command under the owner - principal (`meddling-attributed`, `grammar-governs`). A run control (budget, - pins, focus, cadence, pause) becomes a revision of the run record at a tick - boundary, with an epoch, which is the old `ResolutionControlReceipt` rule. - There is no separate web dashboard and no tool that edits `.cc` files or the - journal directly. What it does first is question `meddling-first-acts`. -- **The newspaper ports as a reader.** The old newsroom, editorial agenda, - citations and copy desk become a pipeline consumer bound by `readers-read`. - Whether it is the first consumer is question `verse-first-consumer`. - -## Not in scope - -- Dungeon affordances: one-step Begin, vault seeding under Begin, the create - flow, Dungeon's play surface and Bonsai (ruling `aetheria-sim-first`). They - continue in the Dungeon lane and do not gate this target. -- The ad hoc mutation rules of the pre-rebuild tree (map, "Ad hoc rule" table): - the outcome resolver, semantic verifiers and quotas, the presence planner as - a decider, arena knowledge union, clock-consequence binding, prompt-only - prohibitions. -- The video and stream pipeline, episode editing, and the people-in-rooms scene - grammar (ruling `scene-grammar-people-in-rooms`; a later campaign). -- Storylet weaving, radio delivery and any other runtime code in Aetheria (the - `aetheria-release` campaign). Only the grammar document is authored on - Aetheria's side, if question `verse-grammar-home` puts it there. -- Counterfactual branching of a baseline over player-action archetypes, the - "branch ... and propagate" step of ruling `storylets-simulate-sift-lift-weave`. - The journal must not preclude forking a world at a committed revision, but no - fork operation is built here. -- A live Ghostlight running beside the game (fork 4 of Aetheria's - `reactive-world-target.md`). -- The failed `idunn-odin-*` and `idunn-streampixels-*` units on Yggdrasil. - -## Canonical implementations - -- Kernel, cover, clock, relation edge, import key and the verse runner library: - `GameCult/Ghostlight` `crates/ghostlight`. -- The verse daemon and its Eve surface: `GameCult/Ghostlight` - `crates/ghostlight-verse`. -- The port source: `GameCult/Ghostlight` at `f9f019b`, - `crates/ghostlight-dungeon/src/` (`domain.rs`, `resolution.rs`, - `scheduler.rs`, `gestalt.rs`, `persona.rs`, `newspaper.rs`, the tick driver in - `main.rs`). -- The grammar: where question `verse-grammar-home` puts it. -- Deployment and run start: `GameCult/Idunn`, with the recipe in the Ghostlight - repo. - -## Questions for the operator - -Admitted to the mind in one batch; query them there. `verse-grammar-home`, -`grammar-scope-of-mutation`, `verse-first-consumer`, `verse-run-cadence-budget`, -`meddling-first-acts`, `verse-world-canonical-or-seeded`, -`gestalt-fission-approval`. diff --git a/notes/ghostlight-verse-target.md b/notes/ghostlight-verse-target.md index a9d0f1c..6f03dc5 100644 --- a/notes/ghostlight-verse-target.md +++ b/notes/ghostlight-verse-target.md @@ -1,51 +1,163 @@ -# Ghostlight Verse: Target +# Ghostlight Verse: Target, revision 3 -Date: 2026-10-09. Campaign `ghostlight-verse` in the Eureka mind. Operator direction: -ruling `ghostlight-verse:ruling:verse-cast-crossmedia` (her words). Evidence for the -starting state: `F:\Projects\ghostlight-state-2026-10-09.md` (Eyes, 2026-10-09) and -`F:\Projects\aetheria-story-sifting-prior-art.md` Part 2. +Date: 2026-10-10. Campaign `ghostlight-verse` in the Eureka mind. Imagination +`imagination-gl-verse-sim-cuts`, session `self-2026-10-10-ag`. Supersedes revision 2 +(2026-10-09) where they differ; what revision 2 said that still holds is kept here. +Rulings are cited by id and read in the mind; this page does not restate them. +Evidence: `notes/ghostlight-verse-map.md`, sections "Verse sim: port inventory and +model page" and "Verse sim: phase A body facts"; `gamecult-ops` +`docs/research/llm-world-sim-prior-art.md` at `6b4a874` (*prior art (a)-(d)*); +`F:\Projects\aetheria-story-sifting-prior-art.md`. ## Why -Aetheria needs a public narrative hook. Ghostlight is meant to be the machine that -produces it: load the Aetheria verse, let a persistent cast of characters live in it, -and harvest what happens. Three products are wanted from the same simulated world: +Aetheria needs a public narrative hook, and Ghostlight is the machine that makes it +(ruling `verse-cast-crossmedia`): load the Aetheria verse, let a persistent cast live +in it, and harvest storylets for the game, a serialized cast, and stories a cheap +cinematic pipeline can stream. -1. **Storylets** for the game (ruling `aetheria-release:ruling:storylets-simulate-sift-lift-weave`). -2. **A serialized cast** an audience can follow through episodes. -3. **A cheap cinematic pipeline** that turns episodes into a video stream, so the - characters and conflicts people follow on the channel later turn up in the game. +Ruling `aetheria-sim-first` makes simulating Aetheria the campaign's priority, ahead +of every Dungeon affordance. Ruling `verse-sim-pipeline` sets the pipeline: the lore +is loaded, elaborated, and run as a multiresolution-gestalt world in Aetheria's +ontology and action grammar, on a schedule, feeding the content pipelines, under +operator meddling. Ruling `gestalt-machinery-port` ports the pre-rebuild gestalt +machinery rather than redesigning it, and ruling `teardown-was-authority-sprawl` +keeps the ad hoc mutation rules dead. The map sorts the old tree (`f9f019b`) into +both lists and names sixteen forced changes (F1-F16). -This campaign builds the foundation the three share: a Ghostlight that runs, says -truthfully what it is, and can hold the verse with a persistent cast. The stream -pipeline and the in-game crossover are later campaigns that consume it. +The field agrees with the split: working LLM world simulations keep world state in a +non-LLM store and map free-text intent onto an enumerated, checked action (prior art, +"What the field converged on", items 1-2). Cost comes down by calling the model less +per agent per tick (item 3), which is what the gestalt cover does. Replay comes from +stored responses, not seeds (item 5). ## Phases -1. **Honest and running.** The docs describe the live machine. `main` deploys on - Yggdrasil through Idunn and stays up. The six open play-session findings and the - owed Soul pass close. A human can play a session end to end. -2. **A world that can hold factions and a cast.** Relations, factions as actors, places - and persistent characters enter the ontology, designed from prior art and ruled by - the operator. -3. **The verse loaded.** A genesis built from AetheriaLore and Aetheria's typed - `Faction` records. Facts the lore does not state are proposed and reviewed, never - invented silently. +1. **Honest and running** (revision 2): the Dungeon lane closes its phase-1 work in + parallel. It does not gate the verse. +2. **Phase A, the first watched run.** Aetheria's grammar exists as a CultCache + document set in Aetheria's catalog; a Ghostlight world binds it at genesis and the + kernel refuses any affordance kind it lacks. The canonical verse world is created + from Aetheria's `Faction` records, one subject per record under a checked import + key. A hosted cheap-model lane records every call with its response body and + token usage. The operator starts a manual run under a hard token cap and reads, + per tick, what each faction did and what it cost. Mapped as cut specs (map, + "Phase A cut order"). +3. **Phase B, the verse loaded.** The relation edge (F6) enters the kernel; the + faction records' allegiances become Alliance and Rivalry edges; elaboration from + AetheriaLore declares places, populations as gestalts and named characters as + persons. +4. **Phase C, the gestalt port.** Pins, debt, checkpointed parallel cells, strategic + individuation and fission (F1-F16) on main's kernel; replay of a whole run. +5. **Phase D, meddling and the newspaper.** The operator's Eve surface (steer, then + inject), the ported newspaper as the first reader, then nightly runs. ## Invariants +Labels carried from revision 2 keep their meaning. + - `honest-docs`: README, maps and handoffs describe the machine on `main`; history lives in history. -- `main-runs`: the deployed unit on Yggdrasil is built from `main` and stays up; Idunn owns its lifecycle. -- `kernel-decides`: the kernel owns world state through its closed operation set; models propose, the kernel admits or refuses. +- `main-runs`: every deployed Ghostlight unit is built from `main` and stays up; Idunn owns its lifecycle. +- `kernel-decides`: the kernel owns world state through its closed operation set; models propose, the kernel admits or refuses, and no model decides an outcome. +- `grammar-governs` (ruling `grammar-scope-ruled`): every change to world state is a kernel command reduced through the closed operation set. Every act by a subject is an invocation of an affordance whose kind is in the world's bound grammar. The only other writers are clock motion, elaboration patches answering a derived boundary or deficit, and owner acts recorded with the operator as principal. Nothing else writes. Changing simulation resolution writes nothing. +- `grammar-bound`: a verse world binds one grammar revision by content digest at genesis, and the kernel refuses an affordance kind, or a role signature, that the bound grammar does not carry. +- `resolution-not-identity`: the resolution at which a subject is simulated never changes who exists, what they hold or what they know. A person given up by a gestalt stays a person. +- `one-record-one-subject`: each imported Aetheria or lore record maps to exactly one subject or entity, and the mapping is checked when a patch is admitted. - `verse-provenance`: every world fact loaded from the verse names its source page or record; a fact with no source is a proposal until reviewed. - `canon-one-way`: simulation output never writes back into AetheriaLore or Aetheria's catalog without operator review. -- `cast-persists`: a character keeps identity, memory and commitments across sessions, so episodes can follow them. -- `every-event-renderable`: every event the kernel admits maps to exactly one render path: ship action filmed in Aetheria, or a social verb staged as an Aetheria conversation (portraits and choices; docked face to face, undocked radio over the ship). Summary captions are the only exception (ruling `verse-verbs-two-render-paths`). -- `typed-state`: world state and its exchange are CultCache/CultNet documents; JSON only for schema publication and xenos boundaries. +- `readers-read`: a pipeline consumer reads committed history and writes only its own output. +- `cast-persists`: a character keeps identity, memory and commitments across runs, so episodes can follow them. +- `every-event-renderable`: every event the kernel admits maps to exactly one render path: ship action filmed in Aetheria, or a social verb staged as an Aetheria conversation (ruling `verse-verbs-two-render-paths`). Summary captions are the only exception, and they cover the named non-verb writers: clock motion, elaboration and owner acts. +- `run-resumable`: an interrupted run resumes from its last checkpoint and commits nothing twice. +- `run-replayable`: a run re-executed from its recorded provider responses produces the same journal. +- `run-bounded`: a run never spends past its token cap, and its fiction span, calls, tokens and cost are recorded. +- `dungeon-isolated`: a verse run cannot slow, stop or change Dungeon play. +- `meddling-attributed`: every operator intervention is a typed act, recorded with the operator as principal, through the same doors as every other writer. +- `typed-state`: world state, run records, call records, the grammar and pipeline output are CultCache documents; exchange is CultNet; JSON only for schema publication and xenos boundaries. + +## Defaults (stated, not forks) + +- **The grammar** (ruling `verse-grammar-home-ruled`) is two CultCache document + types in Aetheria's catalog (`GameData/Aetheria.cc`, registered in + `AetheriaStores.CatalogTypes`): a global `aetheria.verse_grammar` carrying the + revision, and one `aetheria.verse_verb` per verb, carrying the verb's canonical + kind name, its one render path (ship action or conversation), its participant + roles and the referent kind each binds, and a description. Aetheria owns which + verbs exist and how each renders; Ghostlight owns what each verb does to world + state (question `verb-effects-owner`). The starter set is authored by an AetherDb + command: the ship-action verbs of Aetheria's current scenario vocabulary and a + small social set that includes `speak`, the kernel-built narrative affordance. +- **Grammar binding.** A world created under a grammar stores its schema id, + revision and content digest (sha256 over the canonical encoding of its verbs, + sorted by kind). Revising the grammar means a new world, or a later migration + designed when needed: the kernel today refuses earlier schemas and never migrates. + A world with no grammar binding (every Dungeon world) keeps today's open catalog. +- **The canonical world** (ruling `verse-world-canonical-ruled`) is one world built + from Aetheria's catalog and AetheriaLore at named commits. Its import key is + `(CultCache schema id, record key)` for catalog records and `(vault-relative page + path)` for lore pages; the subject carries it, and admission refuses a second + subject or entity with the same key. Lore pages enter in phase B. +- **Cast.** A cast member is a `Person` subject marked by a cast entry in the run + record's roster, not by a kernel component: being followed by an audience changes + attention, not the world. The operator curates the roster as a run control + (ruling `meddling-first-ruled`); the cover keeps cast members at singleton + resolution (the old `MinimumIndividualDetail` pin). +- **Fission** (ruling `gestalt-fission-auto`) is admitted during a run as an + elaboration patch answering a derived boundary, one of the named non-verb writers + under `grammar-scope-ruled`. It is not a grammar verb and not gated. The operator + sees it afterwards: the run record lists each fission commit, and the meddling + surface shows it in the run's history beside the injection controls. +- **Separate verse process.** The runner is library code in `crates/ghostlight`; the + binary crate `ghostlight-verse` drives it. Phase A runs are one-shot CLI + invocations on Yggdrasil started by the operator or an agent at her word; the + daemon, its Eve surface and the Idunn schedule arrive with phase D. +- **Inference lane.** The verse uses a hosted OpenAI-compatible lane, DeepSeek + first (ruling `two-ghostlight-consumers`). The lane is main's local + OpenAI-compatible port with a second binding (HTTPS base URL, bearer key read from + a file), not a new transport. The DeepSeek key is the operator's to provision. +- **Cadence** (ruling `verse-cadence-ruled`): manual runs under a hard token cap per + run, which the operator sets per invocation, until cost per tick is measured; then + nightly under a monthly cap she sets. +- **Replay record.** Each provider call's request hash, response events, model and + token usage are stored per run in a CultCache call record. The kernel draws no + randomness, so stored responses are enough for replay (prior art (c)). +- **Exchange.** Aetheria reads what the verse publishes as CultNet documents through + CultMesh, the outbound read `ghostlight-world-consumer-api.md` names as "not in + this pass". The loopback consumer door is not how Aetheria reads. +- **Meddling** (ruling `meddling-first-ruled`) is an Eve/CultUI graph over CultMesh. + Steering controls (pins, focal subjects, cell budget, cast roster, pause, resume) + revise the run record at a tick boundary with an epoch and change no world state. + Injection controls (fact, pressure, commitment, person, ruled fact) are kernel + commands under the owner principal. +- **The newspaper** (ruling `verse-first-consumer-ruled`) ports as the first reader, + bound by `readers-read`, with the copy desk checking citations against committed + facts. ## Not in scope -- The video/stream pipeline (StreamPixels and its renderers) and episode editing. -- Storylet weaving in the Aetheria runtime, and any Aetheria code. -- Restoring the deleted autonomous tick driver as it was; unattended simulation, if wanted, is designed in phase 2. -- The failed `idunn-odin-*` and `idunn-streampixels-*` units on Yggdrasil (not Ghostlight's). +- Dungeon affordances: one-step Begin, vault seeding under Begin, the create flow, + Dungeon's play surface and Bonsai (ruling `aetheria-sim-first`). They continue in + the Dungeon lane and do not gate this target. +- The ad hoc mutation rules of the pre-rebuild tree (map, "Ad hoc rule" table). +- The video and stream pipeline, episode editing, and the people-in-rooms scene + grammar (ruling `scene-grammar-people-in-rooms`). +- Storylet weaving, radio delivery and any other runtime code in Aetheria. On + Aetheria's side only the grammar document types, their validation and the AetherDb + command that authors them are in scope. +- Counterfactual branching of a baseline over player-action archetypes. The journal + must not preclude forking a world at a committed revision, but no fork operation is + built here. +- A live Ghostlight running beside the game. +- The failed `idunn-odin-*` and `idunn-streampixels-*` units on Yggdrasil. + +## Canonical implementations + +- Kernel, cover, clock, grammar binding, import key and the verse runner library: + `GameCult/Ghostlight` `crates/ghostlight`. +- The verse binary, later daemon and Eve surface: `GameCult/Ghostlight` + `crates/ghostlight-verse`. +- The grammar: `GameCult/Aetheria`, `Assets/Scripts/ServerShared` and + `GameData/Aetheria.cc`, authored through `tools/AetherDb`. +- The port source: `GameCult/Ghostlight` at `f9f019b`, `crates/ghostlight-dungeon/src/`. +- Deployment and, from phase D, run start: `GameCult/Idunn`, with the recipe in the + Ghostlight repo. From 35d0fd2797bdb42f8c825c1b9a4ca5d6eb877f72 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 11 Oct 2026 12:03:22 +0200 Subject: [PATCH 4/5] Verse map: run identity re-map after Soul s1 (tick named by world clock ordinal, frozen tick document, controller lookup by command id) Body facts R1-R8, the identity/lifecycle/authority table for tick, cell, command id, clock advance, run, run sequence and call ledger, and the fix order for specs verse-run r3, verse-run-lookup, verse-run-refusals and verse-run-claim. Research note: how event-sourced workflow engines and lockstep sims persist turn identity for resume and replay. Co-Authored-By: Claude Opus 5.5 --- .../research/verse-tick-identity-prior-art.md | 61 +++++++ notes/ghostlight-verse-map.md | 149 +++++++++++++++++- 2 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 docs/research/verse-tick-identity-prior-art.md diff --git a/docs/research/verse-tick-identity-prior-art.md b/docs/research/verse-tick-identity-prior-art.md new file mode 100644 index 0000000..04c9210 --- /dev/null +++ b/docs/research/verse-tick-identity-prior-art.md @@ -0,0 +1,61 @@ +# Research note: where a turn's identity lives (verse-run re-map, 2026-10-11) + +Imagination `imagination-gl-tick-identity`, session `self-2026-10-11-ag`. A short +prior-art check before settling tick identity for cut `verse-run` r2. Appended to the +map's "Verse run identity" section. + +## The question + +At Ghostlight `06a8373` a tick is named `now / span` (`cover.rs:46`), the span is not in +the name, and every cell id and command id is re-derived from the opportunities, +attention order, span and cover budget the resuming run holds at that moment +(`run.rs:557-565`). Soul s1 showed a different span or budget on resume yields new ids, +so a subject acts twice in one tick (p1, p2), and a different span reuses a closed +tick's index (p4). + +## Prior art + +- **Durable workflow engines (Temporal, Azure Durable Functions).** The identity of + each step is assigned when the step is first scheduled and recorded in the + execution's event history. On resume the code is replayed against that history and + every command it produces is compared with the recorded one; a difference is a + non-determinism error, not a second execution + ([Temporal: execution failures](https://docs.temporal.io/troubleshooting/execution-failures); + [community thread](https://community.temporal.io/t/non-determinism-error/8279)). + The forum cases are exactly Soul's: a timer duration or a child id computed + differently on replay. The lesson: resume reads the recorded decision; it does not + re-derive it from inputs that may have changed. +- **Deterministic lockstep games.** The turn (frame) number is simulation state, + advanced only by the simulation, and every command is tagged with the turn it runs + in, so all peers and every replay agree on which turn a command belongs to whatever + the wall-clock or turn length + ([Unity discussion](https://discussions.unity.com/t/deterministic-rts-game-engine/467205); + [Gaffer On Games](https://gafferongames.com/post/what_every_programmer_needs_to_know_about_game_networking/)). + The turn count, not elapsed time divided by turn length, is the name. +- **Idempotency keys (event-sourced command handlers, payment APIs).** A key answers a + retry with the stored result; the same key with a different request is refused, not + re-executed. Ghostlight's kernel already does this + (`lib.rs:1633-1644`: same caller and body, `AlreadyApplied`; otherwise + `CommandIdConflict`), which is why Soul's p4 clock under another span was refused. + +## What this decides for verse-run + +1. The tick ordinal is world-held: the number of `TimeAdvanced` commits in the world's + journal when the tick opens. It is span-independent, monotone, injective per world, + restored with a world snapshot (so ruling `replay-restored-snapshot` needs nothing + else for a run that starts on a tick boundary), and consecutive (which is what the + cover's rotation window wants, `cover.rs:340`). +2. What a tick decided when it opened (span, cover budget, the partition and each + cell's id and members) is recorded once, before the first cell runs, in a tick + document owned by the run store, and a resuming run reads it instead of deriving a + cover. This is Temporal's history rule at the granularity the verse needs. +3. Command ids stay derived, but only from that frozen identity (world, ordinal, cell + id, subject), never from live inputs, so there is no second copy of them to drift. +4. The controller answers a persisted row whose command the world committed with its + receipt whatever the caller's current scope digest, because the command id is the + idempotency key; a row still in flight keeps the digest check, so no tokens are spent + on a scope that moved. + +Not adopted: a world-state tick counter (a `STATE_SCHEMA` change, and the kernel would +learn ticks exist, which `cover.rs:1-12` forbids); a run-store-only ordinal (a replay +over a restored world with a fresh run store would number ticks from 1 and diverge). diff --git a/notes/ghostlight-verse-map.md b/notes/ghostlight-verse-map.md index 84c1987..0a187ba 100644 --- a/notes/ghostlight-verse-map.md +++ b/notes/ghostlight-verse-map.md @@ -494,8 +494,8 @@ invariant `grammar-governs` in the draft target. An empty cell is marked | Individuated members (the people gestalts give up) | `SubjectId`, kind `Person`, with a Membership edge to the population (F3, F6) | born by an elaboration patch answering `IndividuationRequired` or a strategic individuation; never folded back (F4) | Kernel admits; the elaborator or the cell proposes; duplicates of an established identity refused (ported test). | | How a region moves between resolutions | derived, no identity of its own: a cell id is a content hash under `ghostlight.cover.v1` | recomputed every tick from pins, budget, pressure and debt; a leased cell survives while still connected (old lease rule) | `derive_cover` (pure); pins and budget from the run record; promotion and demotion are only cell assignment (F4, F9). Forbidden writers: the kernel (never sees a cell), any model. | | Cast (characters the audience follows) | a `Person` `SubjectId` named in the run record's cast roster; no kernel component (being followed changes attention, not the world) | joins or leaves by an operator steering act at a tick boundary (ruling `meddling-first-ruled`); a member who is retired in the world stays in the roster's history | Owner: the operator, through run controls. The cover keeps roster members at singleton resolution (the old `MinimumIndividualDetail` pin). Forbidden writers: models and the runner. Lands in phase D; phase A has no persons. | -| Verse run (one invocation) | `(WorldId, run sequence)`, sequence assigned by the runner, document `ghostlight.verse_run.v1` | started manually under a token cap (ruling `verse-cadence-ruled`) → per-tick checkpoint → Completed, BudgetExhausted, Failed or Stopped; a failed run resumes from its last checkpoint; nightly scheduling arrives after cost per tick is measured | Owner: the verse runner (cut `verse-run`). Idunn will own scheduled start and process survival (phase D). Forbidden writers: Dungeon's play loop, an in-daemon timer, the Eve surface. | -| Tick | `TickIndex`, derived from the world clock, never stored (cover.rs:33-38) | derive cover, run cells, submit proposals, then `AdvanceTime` (F15); a resumed tick re-derives the same command ids, so the kernel's idempotency ledger answers with the original receipts | Kernel owns time; the runner owns the order. | +| Verse run (one invocation) | `(WorldId, run sequence)`, document `ghostlight.verse_run.v1`; the sequence is claimed from the run store before anything else is opened (see "Verse run identity" below) | claimed manually under a token cap (ruling `verse-cadence-ruled`) → per-tick checkpoint → Completed, BudgetExhausted, Failed or Stopped; a run never resumes itself: a later run resumes the open tick, which is the world's, not the run's | Owner: the run store claims, the verse runner writes its own record (cut `verse-run`). Idunn will own scheduled start and process survival (phase D). Forbidden writers: Dungeon's play loop, an in-daemon timer, the Eve surface. | +| Tick | world-held ordinal: the count of `TimeAdvanced` commits when it opens, plus a tick document `ghostlight.verse_tick.v1` keyed (world, ordinal) freezing span, budget and cells (see "Verse run identity" below; the `now / span` index at 06a8373 collided across spans, Soul s1) | opened (document written before the first cell), closed by its clock commit, or closed by the world when the ordinal moved without it; never reopened | Kernel owns time and the ordinal's source; the run store owns the tick document; the runner owns the order. | | Run record: policy, pins, demand, roster, checkpoints, spend | inside `ghostlight.verse_run.v1`; phase A carries cap, tick span, cover budget, checkpoints and spend; pins, demand and roster arrive with phase C-D | policy and pins revised by operator act at a tick boundary with an epoch (old `ResolutionControlReceipt` rule); checkpoints per tick, superseded | Runner. Forbidden writers: the kernel. | | Model proposal and its validation | derived command id (world, tick, cell, constituent), so injective per tick | proposed; admitted, or refused (`ScopeChanged`, precondition, ceiling); a narrative singleton may re-lower once (`interrupted_from`) | Kernel reducer decides. Forbidden writers: an outcome resolver or verifier model (F12). | | Provider call record (for replay and cost) | `(run id, call sequence)`, document `ghostlight.verse_provider_call.v1`, carrying the provider request sha256, model, response events and token usage | written once per call by the recording port before its output reaches a lane; never edited; replay serves it back by request sha256 | Owner: the recording port in front of the inference lane (cut `call-record`), which also owns the run's spend and refuses a call whose bound exceeds the remaining cap. Forbidden writers: lanes and the runner. | @@ -604,3 +604,148 @@ Rationale for the order: the two kernel cuts are pure and land before anything t reads a file; the reader's fixture needs Aetheria's types, so it waits on `verse-grammar`; spend has one owner, the recording port, so the cap is built before the runner and the runner never counts tokens itself. + +## Verse run identity (re-map after Soul s1, 2026-10-11) + +Imagination `imagination-gl-tick-identity`, session `self-2026-10-11-ag`. Pinned to +Ghostlight `ghostlight-verse/verse-run` at `06a8373` (Hands report `cut-verse-run.h1`, +Soul verdict `cut-verse-run.s1`, eight findings). Prior art is in the research note at +the end of this section. + +### Body facts (source reads at 06a8373) + +- **R1 A tick is named by `now / span`.** `TickIndex::of` (`cover.rs:46-48`) floors + `now` by the span; `derive_cover` (`cover.rs:263-271`) takes `now` and the span and + names the tick so. The span is not in the name, so two spans reach the same index at + different times (Soul p4: two 60-minute ticks, then span 120 gives index 1 again). +- **R2 Every derived id hangs off that index and the live partition.** `cell_id` + (`cover.rs:87-97`) is `(world, tick, size, lowest member)`; the cell's work row + (`cover.rs:100-102`) and each constituent's command (`cover.rs:106-113`) add the cell + id and the tick. The partition depends on the attention order (`lib.rs:4569-4580`: + pressure, then `attention_debt`, which an act resets, `lib.rs:4597-4602`), the cell + budget and the span. `run_tick` derives the cover afresh on every call + (`run.rs:557-565`), so a resuming run with another span, budget or order re-derives + other ids (Soul p1, p2). +- **R3 The clock id is `derived(ghostlight.verse.clock, [world, tick])`** (`run.rs:612-615`). + The kernel answers a known command id by caller and body: same body, + `AlreadyApplied`; another body, `CommandIdConflict` (`lib.rs:1633-1645`). Under + another span the clock body differs, so p4's clock was refused and the run stalled. + No test pins the id (mutants H3 and S1 survive). +- **R4 Rows outlive commits.** A controller-work row persists after its command + commits, and lookup-first refuses a row whose scope digest differs from the caller's + (`binds_same_scope`, `controllers.rs:1589-1593`, used at 3039, 3047, 3119, 3127). An + act moves its own subject's digest, so a resumed committed cell is refused + `OpportunityMismatch` (Hands' deviation). Resuming the row itself would get the + original receipt from the world: `submit_controller_world` asks `controller_receipt` + first (`controllers.rs:4276-4300`), which matches the row's own opportunity and + invocation (`lib.rs:1688-1712`). +- **R5 A grouped cell loses members silently.** `run_group` drops a member whose + opportunity moved before selection into `needs` as text (`controllers.rs:3430-3437`); + a member the world refuses after the first member's act is also text in `needs` + (`controllers.rs:3686-3692`); `absorb` reads only `submissions` and `pending` + (`run.rs:400-403`); `refreshed` drops a group member with no opportunity without a + refusal (`run.rs:429-436`). Soul p3, p7: 2 acts of 4, refusals `[]`. +- **R6 A refused member climbs the order.** No commit means `last_opportunity_at` is not + stamped, so its `attention_debt` grows and it sorts ahead next tick + (`lib.rs:4597-4602`). +- **R7 The sequence has no owner.** `run_manual` takes any sequence its port was opened + for (`run.rs:459-470`) and `save` accepts any sequence up to the next + (`run.rs:306-309`), so an old sequence overwrites its record (Soul p6). The binary + opens the call ledger for `next_sequence` before any record exists + (`main.rs:193-200`), so a start that fails between the two binds that sequence's cap + (Soul p5). +- **R8 Two output leaks.** `run_tick` swallows an operator-log error into zero acts + (`run.rs:631-639`). The binary prints errors with `{:#}` (`main.rs:46`), so + `open_inference`'s source chain reaches stderr (`main.rs:289-293`), and + `ControllerOpenError` echoes the endpoint, the model and the sidecar path + (`controllers.rs:2510-2530`). + +### Identity, lifecycle, authority + +| Kind | Identity | Lifecycle | Authority | +|---|---|---|---| +| Tick | (world, ordinal). Ordinal = number of `TimeAdvanced` commits in the world's journal when the tick opens: world-held, read through a driver-only mailbox door, injective and consecutive per world, independent of span, restored with a world snapshot. Not derived from `now`. | Opened: the run store writes `ghostlight.verse_tick.v1` (world, ordinal, opening now and revision, span, cover budget, oversubscribed, cells) before the first cell runs. Closed by its clock commit (the close names the run). Closed by the world when a later run finds the ordinal past it with the document still open (its clock landed and the close was lost, or another clock writer moved time). Never reopened or deleted. A resuming run with another span or budget finishes the open tick under the frozen ones; its own config applies from the next tick. | The kernel owns time and the count. The run store owns the tick document, and `run_tick` is its only writer. `TickIndex::of` is deleted; `derive_cover` takes the ordinal. Forbidden: deriving a tick from `now`; a run deriving a cover for a tick that has a document. | +| Cell | `CellId` = `(world, ordinal, size, lowest member)` under `ghostlight.cover.v1`, formula unchanged; frozen in the tick document with its kind and member subjects | derived once when the tick opens, frozen, read by every run that works in that tick; each member is bound to the world's current opportunity when its cell runs (the existing `refreshed`), and a member with none is refused, not dropped | `derive_cover` decides the partition, once per tick. Forbidden: re-deriving the partition on resume. | +| A cell's command ids | work row `for_cell(world, cell, ordinal)`; each member's `for_cell_constituent(world, cell, subject, ordinal)`. Derived from frozen identity only, so not stored twice. | issued when the member first runs; answered from the kernel's ledger on any retry; a persisted row whose command committed is answered with its receipt whatever the caller's digest; an in-flight row whose digest moved is refused before any inference | The controller organ, lookup-first by command id. Forbidden: the driver minting ids; a digest check deciding a committed row's outcome. | +| Clock advance | `derived(ghostlight.verse.clock, [world, ordinal])`, body `AdvanceTime { frozen span }` | submitted once, after every cell of the tick finished; a retry in the same tick has the same body and is answered `AlreadyApplied`; it is what closes the tick | The kernel admits; `run_tick` is the only verse caller. Pinned by a test that re-submits a closed tick's clock. | +| Run | (world, sequence), `ghostlight.verse_run.v1` | claimed (Running, no checkpoints) → per-tick checkpoints naming the ordinal → Completed, BudgetExhausted, Stopped or Failed. A run whose process died stays Running (known gap, report h1 undone). A claim abandoned before the run starts ends Failed(start_refused). | The run store claims; `run_manual` updates only the record its claim names. | +| Run sequence | per world, from 1, claimed by the store as the next free number with the record written in the same call (`RunClaim`, not constructible outside the store, consumed by `run_manual`) | claimed once; never reissued, even when the run never started | The run store. Forbidden: a caller choosing a number; `save` creating a record. | +| Call ledger | `call_run_id(sequence)` in `calls.cc`; cap and start digest fixed at first open | opened only after its sequence is claimed, so an orphaned ledger can never bind a later run's cap | The recording port owns cap and spend; the run record copies the cap, and `run_manual` refuses a port whose run id or cap differs from its claim. | + +### Questions + +- `verse-grouped-shared-room` (admitted, blocks no spec): in a shared room a grouped + cell lands at most one exercise per tick; the others are refused and, by R6, lead the + next tick's order. Recommended: record and carry. The alternative spends a second + inference in the same tick for the refused members. + +### Fix order (specs admitted 2026-10-11, all based on 06a8373) + +1. `verse-run` r3 (r2 superseded for a window split): tick identity (ordinal door, tick document, frozen cover, clock pin). +2. `verse-run-lookup`: controller lookup-first by command id; typed grouped refusals. +3. `verse-run-refusals` (after 1 and 2): every member of a tick's cover ends committed + or refused in the checkpoint. +4. `verse-run-claim` (after 1): run claims, the binary's start order, the operator-log + error and the lane error. + +### Research note: where a turn's identity lives (verse-run re-map, 2026-10-11) + +Imagination `imagination-gl-tick-identity`, session `self-2026-10-11-ag`. A short +prior-art check before settling tick identity for cut `verse-run` r2. Appended to the +map's "Verse run identity" section. + +## The question + +At Ghostlight `06a8373` a tick is named `now / span` (`cover.rs:46`), the span is not in +the name, and every cell id and command id is re-derived from the opportunities, +attention order, span and cover budget the resuming run holds at that moment +(`run.rs:557-565`). Soul s1 showed a different span or budget on resume yields new ids, +so a subject acts twice in one tick (p1, p2), and a different span reuses a closed +tick's index (p4). + +## Prior art + +- **Durable workflow engines (Temporal, Azure Durable Functions).** The identity of + each step is assigned when the step is first scheduled and recorded in the + execution's event history. On resume the code is replayed against that history and + every command it produces is compared with the recorded one; a difference is a + non-determinism error, not a second execution + ([Temporal: execution failures](https://docs.temporal.io/troubleshooting/execution-failures); + [community thread](https://community.temporal.io/t/non-determinism-error/8279)). + The forum cases are exactly Soul's: a timer duration or a child id computed + differently on replay. The lesson: resume reads the recorded decision; it does not + re-derive it from inputs that may have changed. +- **Deterministic lockstep games.** The turn (frame) number is simulation state, + advanced only by the simulation, and every command is tagged with the turn it runs + in, so all peers and every replay agree on which turn a command belongs to whatever + the wall-clock or turn length + ([Unity discussion](https://discussions.unity.com/t/deterministic-rts-game-engine/467205); + [Gaffer On Games](https://gafferongames.com/post/what_every_programmer_needs_to_know_about_game_networking/)). + The turn count, not elapsed time divided by turn length, is the name. +- **Idempotency keys (event-sourced command handlers, payment APIs).** A key answers a + retry with the stored result; the same key with a different request is refused, not + re-executed. Ghostlight's kernel already does this + (`lib.rs:1633-1644`: same caller and body, `AlreadyApplied`; otherwise + `CommandIdConflict`), which is why Soul's p4 clock under another span was refused. + +## What this decides for verse-run + +1. The tick ordinal is world-held: the number of `TimeAdvanced` commits in the world's + journal when the tick opens. It is span-independent, monotone, injective per world, + restored with a world snapshot (so ruling `replay-restored-snapshot` needs nothing + else for a run that starts on a tick boundary), and consecutive (which is what the + cover's rotation window wants, `cover.rs:340`). +2. What a tick decided when it opened (span, cover budget, the partition and each + cell's id and members) is recorded once, before the first cell runs, in a tick + document owned by the run store, and a resuming run reads it instead of deriving a + cover. This is Temporal's history rule at the granularity the verse needs. +3. Command ids stay derived, but only from that frozen identity (world, ordinal, cell + id, subject), never from live inputs, so there is no second copy of them to drift. +4. The controller answers a persisted row whose command the world committed with its + receipt whatever the caller's current scope digest, because the command id is the + idempotency key; a row still in flight keeps the digest check, so no tokens are spent + on a scope that moved. + +Not adopted: a world-state tick counter (a `STATE_SCHEMA` change, and the kernel would +learn ticks exist, which `cover.rs:1-12` forbids); a run-store-only ordinal (a replay +over a restored world with a fresh run store would number ticks from 1 and diverge). From f6ffe16fd2d915cf118173e00acbd0a7e8f2501d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 11 Oct 2026 20:55:52 +0200 Subject: [PATCH 5/5] Verse map: the identity table names the unsent awaiting row state Closes follow-up map-row-unsent-awaiting (ruling unsent-awaiting-row-is-awaiting). Co-Authored-By: Claude Opus 5.5 --- notes/ghostlight-verse-map.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notes/ghostlight-verse-map.md b/notes/ghostlight-verse-map.md index 0a187ba..1ab2b8c 100644 --- a/notes/ghostlight-verse-map.md +++ b/notes/ghostlight-verse-map.md @@ -666,7 +666,7 @@ the end of this section. |---|---|---|---| | Tick | (world, ordinal). Ordinal = number of `TimeAdvanced` commits in the world's journal when the tick opens: world-held, read through a driver-only mailbox door, injective and consecutive per world, independent of span, restored with a world snapshot. Not derived from `now`. | Opened: the run store writes `ghostlight.verse_tick.v1` (world, ordinal, opening now and revision, span, cover budget, oversubscribed, cells) before the first cell runs. Closed by its clock commit (the close names the run). Closed by the world when a later run finds the ordinal past it with the document still open (its clock landed and the close was lost, or another clock writer moved time). Never reopened or deleted. A resuming run with another span or budget finishes the open tick under the frozen ones; its own config applies from the next tick. | The kernel owns time and the count. The run store owns the tick document, and `run_tick` is its only writer. `TickIndex::of` is deleted; `derive_cover` takes the ordinal. Forbidden: deriving a tick from `now`; a run deriving a cover for a tick that has a document. | | Cell | `CellId` = `(world, ordinal, size, lowest member)` under `ghostlight.cover.v1`, formula unchanged; frozen in the tick document with its kind and member subjects | derived once when the tick opens, frozen, read by every run that works in that tick; each member is bound to the world's current opportunity when its cell runs (the existing `refreshed`), and a member with none is refused, not dropped | `derive_cover` decides the partition, once per tick. Forbidden: re-deriving the partition on resume. | -| A cell's command ids | work row `for_cell(world, cell, ordinal)`; each member's `for_cell_constituent(world, cell, subject, ordinal)`. Derived from frozen identity only, so not stored twice. | issued when the member first runs; answered from the kernel's ledger on any retry; a persisted row whose command committed is answered with its receipt whatever the caller's digest; an in-flight row whose digest moved is refused before any inference | The controller organ, lookup-first by command id. Forbidden: the driver minting ids; a digest check deciding a committed row's outcome. | +| A cell's command ids | work row `for_cell(world, cell, ordinal)`; each member's `for_cell_constituent(world, cell, subject, ordinal)`. Derived from frozen identity only, so not stored twice. | issued when the member first runs; answered from the kernel's ledger on any retry; a persisted row whose command committed is answered with its receipt whatever the caller's digest; an in-flight row whose digest moved is refused before any inference; a row awaiting the world that was never sent (`ReadyToSubmit` or `NoProposal`) is resumed as awaiting whatever the digest and submitted, the world refuses it if its scope moved, the narrative lane then re-lowers once (one provider call) and the operational lane ends refused, and the driver labels that refusal `ScopeMoved` in both lanes (Defaulted ruling `unsent-awaiting-row-is-awaiting`) | The controller organ, lookup-first by command id. Forbidden: the driver minting ids; a digest check deciding a committed row's outcome. | | Clock advance | `derived(ghostlight.verse.clock, [world, ordinal])`, body `AdvanceTime { frozen span }` | submitted once, after every cell of the tick finished; a retry in the same tick has the same body and is answered `AlreadyApplied`; it is what closes the tick | The kernel admits; `run_tick` is the only verse caller. Pinned by a test that re-submits a closed tick's clock. | | Run | (world, sequence), `ghostlight.verse_run.v1` | claimed (Running, no checkpoints) → per-tick checkpoints naming the ordinal → Completed, BudgetExhausted, Stopped or Failed. A run whose process died stays Running (known gap, report h1 undone). A claim abandoned before the run starts ends Failed(start_refused). | The run store claims; `run_manual` updates only the record its claim names. | | Run sequence | per world, from 1, claimed by the store as the next free number with the record written in the same call (`RunClaim`, not constructible outside the store, consumed by `run_manual`) | claimed once; never reissued, even when the run never started | The run store. Forbidden: a caller choosing a number; `save` creating a record. |