The hand-written is_alphanumeric neighbour rule in SourceSpan::locate got
several boundary cases wrong: it accepted "I can" inside "I can't go" (an
apostrophe is not alphanumeric, so it read as a boundary even though it
binds the contraction into one word), accepted "the cafe" ending right
before a combining acute mark, accepted "run" inside "run_fast", and
falsely refused every CJK quote because every ideograph counts as
alphanumeric and CJK prose carries no spaces.
Replace the rule with real UAX #29 word segmentation from the
unicode-segmentation crate: collect the boundary byte offsets
split_word_bound_indices() reports for the source, plus source.len(), and
accept an occurrence only when both its start and end land in that set.
Delete starts_on_word_boundary/ends_on_word_boundary; the crate is the
authority now, not a hand rule.
unicode-segmentation is pinned to 1.x in the workspace
[workspace.dependencies] table (Cargo.lock gains only that one package)
and added to ghostlight-persona-projection's own Cargo.toml.
Verified against UAX #29 with new tests: contractions (straight and curly
apostrophe) are refused, a quote ending before a combining mark is
refused, an underscore-joined word is refused, a leading-CJK two-character
quote is accepted, a quote with its own leading space is accepted, a
hyphen-bounded quote is accepted, and "Stop." in "Stop.Now" is refused
(the crate glues a MidNumLet full stop between two letters into one word,
so this is the crate's called shot, not a special case carved into the
rule).
Callers (Interpreter capture, Dungeon's span_is_exact) are unchanged; the
lib and Dungeon bin suites both still pass in full (618 lib tests with the
one known PA.f77 flake, 63 Dungeon bin tests).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PlayTurnStore (one row, over service/play-turn-v1.cc) and PlayTable run
a role-playing turn over Dungeon's PLAY_TOOLS subset of the library's
authoring catalog plus dispatch/advance_time/ask_player/end_turn. The
agent's conversation re-derives each round from opening_prompt, the
recorded InferenceOutputs, and each call's persisted body/result, so
nothing but world state crosses a turn boundary. Authoring calls commit
in maximal per-round runs through one commit_authoring_run seam (fix
batch 7a's batching rule); each call still submits as its own one-item
patch until the library ships a batch decoder. Actor calls quote-check
against the player's own texts or a dispatched Persona's recorded turn
before submitting through submit_principal or ControllerPort, and a
kernel Invariant from any commit poisons the table until restart.
Wiring into AppState, GHOSTLIGHT_PLAY_MODEL, world.play routing, and
the Eve schema is Cut 8b.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A third `InferencePort` beside the connector and the SDK sidecar: one
loopback-only OpenAI-compatible `POST /v1/chat/completions` call per
prepared request, tool calls returned inert for Rust's own evaluator to
drive the next round, exactly as the connector lane already does. No
credential is read or sent; `open_inference` refuses a non-loopback
`LocalBinding` and a model prefix two transports both claim.
`RoutedInferencePort` gains the local slot and routes by longest claimed
prefix, refusing rather than falling back to the connector when a claimed
prefix has no configured port behind it (the same rule the SDK prefix
already had, now made explicit for both). `ControllerModels::each` is
`pub` and `open_inference` takes `models: &[&str]` instead of the whole
struct, so a caller can extend the list with a model the struct does not
carry.
Dungeon's runtime reads `GHOSTLIGHT_LOCAL_ENDPOINT` and
`GHOSTLIGHT_LOCAL_MODEL_PREFIX` and wires the binding through; no other
Dungeon behavior changes in this cut.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Completes the move begun in the previous commit, which does not build on its
own. `ghostlight` is now a library crate with an explicit public surface;
`ghostlight-dungeon` is a consumer that reaches the kernel through it and
cannot reach past it.
Deleted first: `mod world;`, the nine unused re-exports, the `#[cfg(test)]`
re-export block, `fixture_controller_opportunities`, the five inference
`fixture_*` items, Dungeon's own `VerifiedPrincipalEvidence`, and Dungeon's
direct `ghostlight-persona-projection` dependency.
The seal. `WorldKernel`, `WorldState`, `reduce`, `apply_effect`,
`CommandEnvelope`, `AuthenticatedCaller`, the journal writer, every ID
`issue()`, and `ScopeDigest::fixture` carry no `pub` of any kind and no
feature gate; the crate declares no `[features]`, so no build configuration
can lift them. Ten `compile_fail` doc-tests stand outside the crate and pin
each one to its error code.
The principal seam. The library owns `VerifiedPrincipalEvidence` with a public
`new`; `mailbox` still bounds it by `valid_until` at ingress. A Dungeon test
counts the minters in production source and fails at anything but one.
The port contract (Q1-7). `PreparedInference::prepare` (renamed from the free
`prepare_invocation`), `InferenceOutput::new`, and
`InferenceFault::{retryable, recovery_required, integrity_violation}` are now
the port's public constructors, so a consumer-implemented `InferencePort` can
answer without test-only wrappers. `TracingInferencePort` is an ungated
decorator.
The tick-ordering test (Q1-8) derives its cover from a real active world
through `derive_cover` and asserts two cells; its ordering and shared-tick
assertions are unchanged.
Behaviour is unchanged. The library's 420 unit tests are the base `world::`
set with the prefix stripped — the names diff empty. Dungeon keeps all 49 of
its names and gains two Soul tests the boundary requires. No schema string,
digest preimage, or store layout is touched.
Windows verification only; the Linux release is Idunn's build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
vendor/README.md declared cultcache-rs, cultnet-rs and cultmesh-rs "a locally
owned compatible protocol generation". That charter is the defect. A consumer
holding its own generation of the wire stack sits outside the only harness that
checks cross-runtime agreement, and the fork behaved exactly as that predicts:
runtime_authority_contracts.rs drifted to e542225 while this repo's own
authority map named f4ab237, and the Rust CultMesh implementation existed
nowhere else in GameCult.
All three now come from CultLib pinned at 85f7024, and 19,604 lines of forked
substrate are deleted. Nothing was lost on the way out. The vendored cultnet
tree was compared file by file against CultLib: ten of twelve matched, lib.rs
differed only in module declarations, and transport.rs carried one guard that
existed nowhere else -- a framed length checked against the advertised maximum
before allocating. That guard is now CultLib 85f7024 with a test.
The migration this was expected to need does not exist. RuntimePresenceCorrelation
changing from enum to struct is Odin's side of the contract and no file here
touches it; with line endings normalised the two contract copies differ by 132
lines, and the only production change that reaches a provider is
IdunnExpectedRoute requiring rudp:// schemes for rudp transport, which this
service does not use. The code compiles unchanged.
89 tests pass across the workspace against the pinned rev.
vendor/ still holds the cultcache-py and eve submodules. cultcache-py is the
same pattern -- a runtime-specific repo CultLib also carries as
packages/cultcache-py -- but it backs the state tooling rather than the
daemon, so it is left for its own change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>