Preserve player intent and approval ownership
This commit is contained in:
parent
b77e68e1f7
commit
f9b0ffb90e
7 changed files with 39 additions and 14 deletions
|
|
@ -799,6 +799,14 @@ model tax. The next action is to commit this exact candidate, deploy its
|
|||
optimized immutable build, and repeat authenticated browser/restart/persistence
|
||||
checks against the served commit before the two-tester playtest.
|
||||
|
||||
The pre-human surface audit found that snapshot-bound assessment, destination,
|
||||
and fission approvals were appended to long-lived forms. Recompilation could
|
||||
therefore leave several stale controls visible even though the kernel rejected
|
||||
them safely. The receipt panel now owns exactly the current approval action.
|
||||
The same audit restored separate composer fields for the visible act or speech
|
||||
and its intended effect; the browser no longer duplicates one sentence into
|
||||
both assessment fields.
|
||||
|
||||
## Warnings
|
||||
|
||||
- Keep persistent state small. Git history and artifacts own chronology.
|
||||
|
|
|
|||
|
|
@ -338,6 +338,12 @@ Compiler, expansion, fission, fork, and reset routes follow the same projection
|
|||
rule. Approval previews expose the public decision surface—topology, cast,
|
||||
pressures, player role, source-use coverage, gaps, and assumptions—without raw
|
||||
campaign, evidence, model receipts, private goals, memories, or relationships.
|
||||
The browser composer preserves the assessment contract's distinction between
|
||||
the character's described act or speech and the effect the player wants it to
|
||||
cause. The receipt panel is the sole owner of the current assessment,
|
||||
destination, or fission approval control; rendering a newer receipt removes the
|
||||
older snapshot-bound control instead of accumulating stale confirmations in
|
||||
persistent forms.
|
||||
|
||||
Relationship documents in the schema catalog are revision-bound projections
|
||||
of actor-owned relationship maps; they are not a second relationship writer.
|
||||
|
|
|
|||
|
|
@ -94,4 +94,5 @@
|
|||
{"ts":"2026-08-18T18:35:30Z","type":"verification","status":"ok","branch":"codex/ghostlight-dungeon-mvp","note":"Exact release a92fb7a4a09b2ee2d80b8e3f8ecff3ab71b9ea06 passed the authenticated committed player journey. A generated campaign used a canonical player ID other than literal player; the server-projected ID drove a private admissible assessment, confirmed OS-random d20 commit, wait, daemon restart, fork, 1,056,768-byte .cc export, original re-selection, and reset. The original remained at revision 2 after fork and the session retained three isolated branches. Cleanup rotated exactly two fresh invitations. Witness: F:\\GameCult\\GhostlightDungeon\\acceptance\\player-journey-a92fb7a-20260818\\result.json."}
|
||||
{"ts":"2026-08-18T18:50:40Z","type":"verification","status":"ok","branch":"codex/ghostlight-dungeon-mvp","note":"Provider-backed 24-subject multiresolution smokes passed at active-cell budgets 1, 4, 8, and 32. Covers contained 1, 4, 8, and 24 cells respectively; compressed cross-faction cells remained arenas, every canonical subject appeared exactly once, material background consequences committed, and the absent player never moved. Elapsed times were 18.08s, 15.90s, 25.64s, and 63.22s. Witnesses: F:\\GameCult\\GhostlightDungeon\\acceptance\\gestalt-scale-a92fb7a-b1, -b4, -b8, and -b32."}
|
||||
{"ts":"2026-08-18T19:20:00Z","type":"verification","status":"ok","branch":"codex/ghostlight-dungeon-mvp","note":"Corrected the scale harness's conflation of active-cell budget with provider concurrency. GHOSTLIGHT_SCALE_PROVIDER_PARALLELISM now defaults to 8 and is recorded in the result. A provider-backed 24-subject budget-8 / parallelism-8 wave completed in 18.35s, covered every subject exactly once, produced ten material actions and one attributed inaction, made no retry, and left the player unmoved. The prior 25.64s budget-8 result used provider parallelism 4. Witness: F:\\GameCult\\GhostlightDungeon\\acceptance\\gestalt-scale-shakedown-b8-p8\\result.json."}
|
||||
{"ts":"2026-08-18T19:35: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-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-C0TXkdRr.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-DCxXqgqF.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DLoOYHpE.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>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<form id="suggest-form"><label for="setting">Generate three grounded openings</label><input id="setting" value="Aetheria" required><button type="submit">Retrieve and suggest</button></form>
|
||||
<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">Describe what you attempt</label><textarea id="attempt" name="attempt" required></textarea><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="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>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<form id="suggest-form"><label for="setting">Generate three grounded openings</label><input id="setting" value="Aetheria" required><button type="submit">Retrieve and suggest</button></form>
|
||||
<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">Describe what you attempt</label><textarea id="attempt" name="attempt" required></textarea><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="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>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -152,7 +152,10 @@ async function compilerPost(path: string, body: unknown) {
|
|||
status.textContent = "Retrieving evidence and compiling…";
|
||||
const response = await fetch(path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
||||
const value = response.headers.get("content-type")?.includes("json") ? await response.json() : await response.text();
|
||||
if (!response.ok) { status.textContent = typeof value === "string" ? value : "Compilation failed"; throw new Error(status.textContent); }
|
||||
if (!response.ok) {
|
||||
status.textContent = typeof value === "string" ? value : String(value?.error ?? "Compilation failed");
|
||||
throw new Error(status.textContent);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
@ -221,18 +224,25 @@ function installAssessment(result: any) {
|
|||
return;
|
||||
}
|
||||
const attempted = await send({ type: "attempt", assessment_digest: result.assessment.digest });
|
||||
button.remove();
|
||||
// A stale preview is compiled again against the new revision. It still
|
||||
// requires explicit confirmation; the server never rebases or auto-rolls it.
|
||||
installAssessment(attempted);
|
||||
});
|
||||
composer.append(button);
|
||||
// The receipt is the sole owner of a pending confirmation. A newer
|
||||
// assessment or any other command receipt replaces this button together
|
||||
// with the snapshot it represents.
|
||||
receipt.append(button);
|
||||
}
|
||||
composer.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
const text = document.querySelector<HTMLTextAreaElement>("#attempt")!.value.trim();
|
||||
if (!text) return;
|
||||
installAssessment(await send({ type: "assess", expected_revision: revision, intent: { actor_id: playerActorId, description: text, intended_effect: text } }));
|
||||
const description = document.querySelector<HTMLTextAreaElement>("#attempt")!.value.trim();
|
||||
const intendedEffect = document.querySelector<HTMLInputElement>("#intended-effect")!.value.trim();
|
||||
if (!description || !intendedEffect) return;
|
||||
installAssessment(await send({
|
||||
type: "assess",
|
||||
expected_revision: revision,
|
||||
intent: { actor_id: playerActorId, description, intended_effect: intendedEffect },
|
||||
}));
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#wait")!.addEventListener("click", () => void send({ type: "wait", expected_revision: revision, minutes: 60 }));
|
||||
document.querySelector<HTMLInputElement>("#active-cell-budget")!.addEventListener("input", event => {
|
||||
|
|
@ -280,7 +290,7 @@ 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();});destinationForm.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.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);});
|
||||
document.querySelector<HTMLFormElement>("#fission-form")!.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
const data = new FormData(event.currentTarget as HTMLFormElement);
|
||||
|
|
@ -303,7 +313,7 @@ document.querySelector<HTMLFormElement>("#fission-form")!.addEventListener("subm
|
|||
button.remove();
|
||||
await refresh();
|
||||
});
|
||||
(event.currentTarget as HTMLFormElement).append(button);
|
||||
receipt.append(button);
|
||||
});
|
||||
document.querySelector<HTMLFormElement>("#fork-form")!.addEventListener("submit",async event=>{event.preventDefault();await compilerPost("/api/campaigns/fork",Object.fromEntries(new FormData(event.currentTarget as HTMLFormElement)));await refresh();});
|
||||
document.querySelector<HTMLFormElement>("#reset-form")!.addEventListener("submit",async event=>{event.preventDefault();await compilerPost("/api/campaigns/reset",Object.fromEntries(new FormData(event.currentTarget as HTMLFormElement)));await refresh();});
|
||||
|
|
|
|||
Loading…
Reference in a new issue