Bind expansion previews to canonical location
This commit is contained in:
parent
b5483242e1
commit
5e198dfa87
8 changed files with 43 additions and 10 deletions
|
|
@ -57,6 +57,7 @@ pub fn player_surface(campaign: &Campaign, narrations: &[NarrationProjection]) -
|
|||
"providerKind":"narrative.simulation", "title":campaign.name, "version":interface_version,
|
||||
"world_revision":campaign.revision,
|
||||
"player_actor_id":campaign.player_actor_id,
|
||||
"player_location_id":player.location_id,
|
||||
"resolution":{
|
||||
"policy":campaign.resolution_policy,
|
||||
"effective_budget":effective_budget,
|
||||
|
|
@ -298,7 +299,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn player_surface_projects_the_canonical_player_actor_id() {
|
||||
fn player_surface_projects_canonical_player_and_location_ids() {
|
||||
let mut campaign = crate::resolution::tests::campaign(1, 1);
|
||||
let mut player = campaign.actors.remove("player").unwrap();
|
||||
player.id = "pilot-nyx".into();
|
||||
|
|
@ -308,5 +309,6 @@ mod tests {
|
|||
let surface = player_surface(&campaign, &[]);
|
||||
|
||||
assert_eq!(surface["player_actor_id"], "pilot-nyx");
|
||||
assert_eq!(surface["player_location_id"], "center");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -808,7 +808,10 @@ and its intended effect; the browser no longer duplicates one sentence into
|
|||
both assessment fields. The approval preview now exposes its full public
|
||||
decision surface in expandable sections, and a browser-local request lock
|
||||
prevents double-clicks from launching duplicate inference without claiming any
|
||||
world-state authority.
|
||||
world-state authority. Destination compilation no longer asks the player to
|
||||
transcribe an internal location ID: the canonical current ID is projected by
|
||||
the server and bound read-only, while destination and fission receipts expose
|
||||
the topology or child leaves being approved.
|
||||
|
||||
## Warnings
|
||||
|
||||
|
|
|
|||
|
|
@ -349,6 +349,11 @@ world clocks, and exact evidence-use rationales instead of asking for approval
|
|||
from summary counts. A browser-local interaction lock prevents duplicate paid
|
||||
requests while preserving the kernel mailbox and revision checks as the only
|
||||
world-mutation authority.
|
||||
The player surface also projects the canonical current location ID. The
|
||||
destination compiler binds that value into a read-only origin field and shows
|
||||
the proposed locations, containment, routes, persistent features, and material
|
||||
gaps before approval. Population fission approval similarly shows each named
|
||||
child leaf, partition value (including `other/unknown`), and home location.
|
||||
|
||||
Relationship documents in the schema catalog are revision-bound projections
|
||||
of actor-owned relationship maps; they are not a second relationship writer.
|
||||
|
|
|
|||
|
|
@ -97,4 +97,5 @@
|
|||
{"ts":"2026-08-18T19:05:00Z","type":"implementation","status":"ok","branch":"codex/ghostlight-dungeon-mvp","note":"Pre-human browser audit restored the fiction-first assessment boundary: the composer now sends distinct description and intended_effect fields, so spoken words are not conflated with persuasion or another desired consequence. The receipt panel now exclusively owns the current assessment, destination, or fission approval button; newer snapshot-bound receipts structurally replace stale controls. Vite production build succeeded."}
|
||||
{"ts":"2026-08-18T19:11:00Z","type":"deployment","status":"ok","branch":"codex/ghostlight-dungeon-mvp","note":"Starfire activated immutable release f9b0ffb90e6a8b3e1505d81995e81e09a9c58cd7 with executable SHA-256 66df149fbf548fc5da6658268e1bb53c62d9608ef6c2c306c6cfc9c909dabdfe and rollback 7916ab04936ba962720cfbf4af1b83f9aa8d8b24. Typed health, release manifest, active junction, PID 12212, four persisted campaigns, DeepSeek readiness, and served browser asset index-DCxXqgqF.js agree. Publication did not rotate or consume invitations."}
|
||||
{"ts":"2026-08-18T19:14:00Z","type":"implementation","status":"ok","branch":"codex/ghostlight-dungeon-mvp","note":"The world-seed preview now renders the full public approval surface—player capabilities/equipment/conditions/obligations, stable topology and routes, cast, institutions, populations, clocks, and exact evidence-use rationales—in compact expandable sections. A browser-local request lock disables only previously enabled controls during compilation or commands and restores those exact controls afterward, preventing accidental duplicate inference while leaving mailbox/revision authority server-side. Vite production build succeeded."}
|
||||
{"ts":"2026-08-18T19:18:00Z","type":"implementation","status":"ok","branch":"codex/ghostlight-dungeon-mvp","note":"The player surface now projects the canonical current location ID beside the canonical actor ID. The destination form binds that ID read-only rather than asking the player to transcribe internal state, and its approval receipt shows proposed containment, routes, persistent features, and material gaps. Fission approval shows each child leaf, partition value including other/unknown, and home location. Focused surface regression and Vite production build passed."}
|
||||
{"ts":"2026-08-18T18:52:00Z","type":"verification","status":"ok","branch":"codex/ghostlight-dungeon-mvp","note":"Exact release 7916ab04936ba962720cfbf4af1b83f9aa8d8b24 made every selected named member exception a mandatory Projector perspective owner, closing the seam where aggregation could erase an actionable individual before Persona choice. The strict provider-backed budget-one golden placed 24 rivals in one arena, attributed migration to member:mira-venn, moved her between depth-two population lineages, produced nine sustained material consequences including seven typed outcomes across six other background subjects, preserved her delta and the player, and automatically promoted the same Mira on return with zero rejected pulses. Witness: F:\\GameCult\\GhostlightDungeon\\acceptance\\gestalt-dynamics-7916ab0-strict\\result.json."}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
4
web/dist/index.html
vendored
4
web/dist/index.html
vendored
|
|
@ -1,5 +1,5 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GhostlightDungeon</title> <script type="module" crossorigin src="/assets/index-Cdu4csne.js"></script>
|
||||
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GhostlightDungeon</title> <script type="module" crossorigin src="/assets/index-DbDnq-RC.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B7Qh0xAi.css">
|
||||
</head>
|
||||
<body><header><p class="eyebrow">STARFIRE / PRIVATE VERSE</p><h1>GhostlightDungeon</h1><p id="status" role="status" aria-live="polite">Reading the world…</p></header><main>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<form id="custom-form"><p class="form-heading">Or describe your own beginning</p><label for="campaign-name">Campaign name</label><input id="campaign-name" name="campaign_name" required><label for="custom-who">Who are you?</label><input id="custom-who" name="who" required><label for="custom-where">Where are you?</label><input id="custom-where" name="where_" required><label for="custom-when">When?</label><input id="custom-when" name="when" required><label for="custom-goal">What do you want?</label><textarea id="custom-goal" name="goal" required></textarea><button type="submit">Compile custom start</button></form>
|
||||
</div><div id="compiler-results"></div></section><section id="surface"></section>
|
||||
<form id="composer"><label for="attempt">What does your character do or say?</label><textarea id="attempt" name="attempt" required></textarea><label for="intended-effect">What outcome are they trying to cause?</label><input id="intended-effect" name="intended_effect" required><p class="quiet">Speech can happen even when persuasion, deception, or intimidation fails.</p><div><button type="submit">Assess</button><button type="button" id="wait">Wait one hour</button></div></form><section id="receipt" class="card" hidden aria-live="polite"></section>
|
||||
<form id="destination-form"><p class="form-heading">Compile a destination without moving there</p><label for="destination-origin">Current location id</label><input id="destination-origin" name="origin_location_id" required><label for="destination-request">Where do you want the world to extend?</label><input id="destination-request" name="destination" required><button type="submit">Retrieve destination</button></form>
|
||||
<form id="destination-form"><p class="form-heading">Compile a destination without moving there</p><label for="destination-origin">Current location id</label><input id="destination-origin" name="origin_location_id" readonly required><label for="destination-request">Where do you want the world to extend?</label><input id="destination-request" name="destination" required><button type="submit">Retrieve destination</button></form>
|
||||
<section id="campaign-lab" hidden><h2>Campaign laboratory</h2><div id="campaign-list"></div>
|
||||
<form id="resolution-form"><label for="active-cell-budget">Active Persona-cell budget: <output id="active-cell-budget-value">8</output></label><input id="active-cell-budget" name="active_cell_budget" type="range" min="1" max="32" value="8"><p id="resolution-status"></p><button type="submit">Apply at safe boundary</button></form>
|
||||
<form id="pin-form"><label for="pin-kind">Resolution pin</label><select id="pin-kind" name="kind"><option value="minimum_individual_detail">Keep one subject individual</option><option value="keep_separate">Keep subjects separate</option><option value="keep_together">Keep subjects together</option></select><label for="pin-subjects">Exact subject IDs, comma separated</label><input id="pin-subjects" name="subject_ids" required><label for="pin-reason">Why preserve this cut?</label><input id="pin-reason" name="reason" required><button type="submit">Add pin</button><div id="pin-list"></div></form>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<form id="custom-form"><p class="form-heading">Or describe your own beginning</p><label for="campaign-name">Campaign name</label><input id="campaign-name" name="campaign_name" required><label for="custom-who">Who are you?</label><input id="custom-who" name="who" required><label for="custom-where">Where are you?</label><input id="custom-where" name="where_" required><label for="custom-when">When?</label><input id="custom-when" name="when" required><label for="custom-goal">What do you want?</label><textarea id="custom-goal" name="goal" required></textarea><button type="submit">Compile custom start</button></form>
|
||||
</div><div id="compiler-results"></div></section><section id="surface"></section>
|
||||
<form id="composer"><label for="attempt">What does your character do or say?</label><textarea id="attempt" name="attempt" required></textarea><label for="intended-effect">What outcome are they trying to cause?</label><input id="intended-effect" name="intended_effect" required><p class="quiet">Speech can happen even when persuasion, deception, or intimidation fails.</p><div><button type="submit">Assess</button><button type="button" id="wait">Wait one hour</button></div></form><section id="receipt" class="card" hidden aria-live="polite"></section>
|
||||
<form id="destination-form"><p class="form-heading">Compile a destination without moving there</p><label for="destination-origin">Current location id</label><input id="destination-origin" name="origin_location_id" required><label for="destination-request">Where do you want the world to extend?</label><input id="destination-request" name="destination" required><button type="submit">Retrieve destination</button></form>
|
||||
<form id="destination-form"><p class="form-heading">Compile a destination without moving there</p><label for="destination-origin">Current location id</label><input id="destination-origin" name="origin_location_id" readonly required><label for="destination-request">Where do you want the world to extend?</label><input id="destination-request" name="destination" required><button type="submit">Retrieve destination</button></form>
|
||||
<section id="campaign-lab" hidden><h2>Campaign laboratory</h2><div id="campaign-list"></div>
|
||||
<form id="resolution-form"><label for="active-cell-budget">Active Persona-cell budget: <output id="active-cell-budget-value">8</output></label><input id="active-cell-budget" name="active_cell_budget" type="range" min="1" max="32" value="8"><p id="resolution-status"></p><button type="submit">Apply at safe boundary</button></form>
|
||||
<form id="pin-form"><label for="pin-kind">Resolution pin</label><select id="pin-kind" name="kind"><option value="minimum_individual_detail">Keep one subject individual</option><option value="keep_separate">Keep subjects separate</option><option value="keep_together">Keep subjects together</option></select><label for="pin-subjects">Exact subject IDs, comma separated</label><input id="pin-subjects" name="subject_ids" required><label for="pin-reason">Why preserve this cut?</label><input id="pin-reason" name="reason" required><button type="submit">Add pin</button><div id="pin-list"></div></form>
|
||||
|
|
|
|||
|
|
@ -127,6 +127,9 @@ async function refresh() {
|
|||
revision = Number(surface.world_revision ?? surface.version ?? 0);
|
||||
playerActorId = String(surface.player_actor_id ?? "");
|
||||
if (!playerActorId) throw new Error("Campaign surface omitted its canonical player actor ID.");
|
||||
const playerLocationId = String(surface.player_location_id ?? "");
|
||||
if (!playerLocationId) throw new Error("Campaign surface omitted its canonical player location ID.");
|
||||
document.querySelector<HTMLInputElement>("#destination-origin")!.value = playerLocationId;
|
||||
resolutionEpoch = Number(surface.resolution?.policy?.resolution_epoch ?? 0);
|
||||
providerConfigurationEpoch = Number(surface.resolution?.policy?.provider_configuration_epoch ?? 0);
|
||||
resolutionPins = surface.resolution?.pins ?? [];
|
||||
|
|
@ -343,7 +346,26 @@ document.querySelector<HTMLFormElement>("#pin-form")!.addEventListener("submit",
|
|||
};
|
||||
await send({ type: "replace_resolution_pins", expected_revision: revision, expected_resolution_epoch: resolutionEpoch, pins: [...resolutionPins, pin] });
|
||||
});
|
||||
destinationForm.addEventListener("submit",async event=>{event.preventDefault();const result=await compilerPost("/api/compiler/destination",Object.fromEntries(new FormData(destinationForm)));const preview=result.preview;showSummary("Destination preview",[...(preview.locations ?? []).map((location:any)=>location.name),...(preview.gaps ?? []).map((gap:string)=>`Material gap: ${gap}`)]);status.textContent="Destination preview compiled; topology is unchanged until approval.";const button=node("button","Approve destination");button.type="button";button.addEventListener("click",async()=>{await compilerPost(`/api/compiler/destination/approve/${result.preview_id}`,{});button.remove();await refresh();});receipt.append(button);});
|
||||
destinationForm.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
const result = await compilerPost("/api/compiler/destination", Object.fromEntries(new FormData(destinationForm)));
|
||||
const preview = result.preview;
|
||||
showSummary("Destination preview", (preview.gaps ?? []).map((gap: string) => `Material gap: ${gap}`));
|
||||
appendDetailList(receipt, `Compiled topology · ${preview.locations?.length ?? 0} locations`, (preview.locations ?? []).map((location: any) => {
|
||||
const routes = Object.keys(location.routes ?? {});
|
||||
const features = [...(location.persistent_features ?? [])];
|
||||
return `${location.name} · ${location.id}${location.container_id ? ` · inside ${location.container_id}` : ""}${routes.length ? ` · routes to ${routes.join(", ")}` : " · no compiled route"}${features.length ? ` · ${features.join(", ")}` : ""}`;
|
||||
}));
|
||||
status.textContent = "Destination preview compiled; topology is unchanged until approval.";
|
||||
const button = node("button", "Approve destination");
|
||||
button.type = "button";
|
||||
button.addEventListener("click", async () => {
|
||||
await compilerPost(`/api/compiler/destination/approve/${result.preview_id}`, {});
|
||||
button.remove();
|
||||
await refresh();
|
||||
});
|
||||
receipt.append(button);
|
||||
});
|
||||
document.querySelector<HTMLFormElement>("#fission-form")!.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
const data = new FormData(event.currentTarget as HTMLFormElement);
|
||||
|
|
@ -354,9 +376,9 @@ document.querySelector<HTMLFormElement>("#fission-form")!.addEventListener("subm
|
|||
reason: data.get("reason"),
|
||||
});
|
||||
showSummary("Population fission preview", [
|
||||
...(result.preview?.children ?? []).map((child: any) => `${child.name} · ${child.id}`),
|
||||
...(result.preview?.gaps ?? []).map((gap: string) => `Material gap: ${gap}`),
|
||||
]);
|
||||
appendDetailList(receipt, `Canonical child leaves · ${result.preview?.children?.length ?? 0}`, (result.preview?.children ?? []).map((child: any) => `${child.name} · ${child.id} · ${child.partition_value ?? "other/unknown"} · at ${child.home_location_id}`));
|
||||
status.textContent = "Fission preview compiled. Canonical leaves are unchanged until approval.";
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
|
|
|
|||
Loading…
Reference in a new issue