Ghostlight/crates
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Meta Vens 43b6af61a5 Land the client round trip (Cut 10, PA.f148-PA.f160)
Cut 9 finished the play loop and gave the player an Eve card. Soul's
own probe over the vendored browser lowering found the player could
not actually use any of it: every bound command submitted
{"bindings":{}}, the card never woke after the first refresh, and one
play commit poisoned the version the kernel's compare-and-swap is
derived from.

One owner per version (PA.f148/PA.f160). The surface document's own
`version` names world.revision alone -- the one meaning
routeHint.sourceVersion and the kernel's compare-and-swap require of
it. The play row's own revision travels beside it as its own
`playRevision` field instead of folded in. The SSE wake signal
(`state.revisions`, `/api/eve/events`) is the one place the combined
value still lives, renamed `authenticated_wake_version` -- it is
never echoed back by a client, so folding is exactly its job.

The client round trip (PA.f149). `eve::local_draft` now emits the
vendored lowering's own EveStateBindingDescriptor shape -- copied
field-for-field from the vendor's own test fixture
(test/host-isolation.test.mjs), which builds exactly this under a
`ghostlight.play` surface id for its own local-draft binding -- so
`bindingName` actually matches what captureBindings names, instead of
falling back to the control's own node id. runtime.rs now unwraps the
lowering's own payload.bindings envelope before deserializing (the
documented gamecult.eve.command_invocation.v1 shape), and JSON-typed
textarea fields (targets/jurisdictions/lens_weights) accept the JSON
string a real textarea actually captures, not only an already-parsed
value. world.create's own form was also missing a control for
`brief`, a required field CREATE_BINDINGS had always advertised --
found by driving the real lowering over the real surface
(tools/eve_client_bridge.mjs), which no hand-built payload could ever
have caught.

The card wakes (PA.f150). publish_projection now runs after a play
turn's own execution finishes, on the same door eve_command's Ok arm
uses, not only at startup and after a world commit.

The question id never reaches the player (PA.f151). The lowering has
no authored, non-editable binding value ("hidden" is not a prop any
renderer reads), so the answers control is deleted outright --
server-side, execute_world's world.play arm resolves the open
question itself, and refuses a stale routeHint.sourceVersion (one
that predates the question) instead of trusting a client-named id.
PA.f84's guarantee survives: an answer cannot land on a question the
player never saw.

Play failures reach the player (PA.f153/PA.f154). PlayTable::run is
split into admit (replay/stale-answer/empty-answer/busy-turn/empty-
opening, synchronous) and execute (the round loop, spawned). The
route now awaits admit and answers denied with the reason instead of
discarding every refusal behind an already-sent accepted response.

Ownership (PA.f152/PA.f159). The play card, its controls, and the
world.play descriptor are owner-gated like world.advance_time and
world.seed, on both the surface and the route. player_subject now
resolves by human_controller matching the calling principal, not
merely by ControllerMode::Human alone. world.summary drops the
subject census; phase and clock stay.

Cleanup (PA.f155-PA.f158): stale doc references to the deleted
world.speak/story surface; PlayTurnView::turn_id and .state given
production readers (an error log line, and the admission gate's own
condition); the play row's revision surviving a restart pinned by a
new assertion; mutation M9.1 retired with the reason it can no longer
be expressed (the parameter it needed was deleted in Cut 9).

Tests: Dungeon bin 139 -> 145 (6 new), lib 622 -> 622 (untouched).

Discrepancies and known gaps, reported rather than resolved silently:
- The envelope-unwrap fix extends past the literal "envelope" wording
  into payload *shape*: the JSON-textarea deserializer
  (deserialize_json_capture) is an extrapolation beyond what the map
  named, needed because the lowering has no JSON-typed control and so
  can only ever capture such a field as text.
- The stale-answer refusal (execute_world's source_version check) has
  no dedicated runtime.rs test: reaching AwaitingPlayer needs a
  working inference backend, and the crate's only scripted one
  (ScriptedPort) is private to play.rs's own test module, not
  reachable from runtime.rs. The round-trip test's own "answer" leg is
  scoped down to create/seed/activate/play for the same reason; PA.f151's
  server-resolved-answer mechanism is proven instead at the play-table
  layer, in play.rs's own (updated)
  a_question_is_answered_through_the_cards_own_binding_and_the_turn_closes_with_narration.
- world.play's own route now checks world ownership explicitly before
  admission; this was not named as an explicit sub-item but follows
  directly from "owner-gated like world.advance_time and world.seed."

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 00:36:43 +02:00
..
ghostlight Fix batch 8a-fix4: library id printer/parser PA.f113 2026-09-22 20:07:48 +02:00
ghostlight-dungeon Land the client round trip (Cut 10, PA.f148-PA.f160) 2026-09-23 00:36:43 +02:00
ghostlight-persona-projection Fix batch 8a-fix5: dispatch call body durable before its effects PA.f125-PA.f132 2026-09-22 21:31:30 +02:00