Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6ffe16fd2 | ||
|
|
35d0fd2797 | ||
|
|
5f5f23f909 | ||
|
|
d8179506dd | ||
|
|
3a61eab3da |
3 changed files with 700 additions and 34 deletions
61
docs/research/verse-tick-identity-prior-art.md
Normal file
61
docs/research/verse-tick-identity-prior-art.md
Normal file
|
|
@ -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).
|
||||
|
|
@ -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-<release>` | 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
|
||||
|
|
@ -256,3 +366,386 @@ 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.md` (revision 3, which absorbed the draft). 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/<file>`.
|
||||
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. **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"). |
|
||||
| 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, 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 (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: Aetheria record or lore page ↔ Ghostlight id | `(schema id, record key)` for a catalog record (`aetheria.faction/<CultRecordKey>`); `(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. 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) | 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)`, 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. |
|
||||
| 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
|
||||
|
||||
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.
|
||||
|
||||
## 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; 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. |
|
||||
| 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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue