Refresh what a switch changes, not what a turn changes
A branch switch does not change the length of the story. It changes which story it is. Four panels keyed on actions.length and so could not tell the difference: the Branches panel drew one branch while the reader was already on a second, Insights showed the prompt built for the path just left, the script-state drawer kept the other line's numbers, and the Memory Bank did not notice a deleted branch taking its memories with it. Only the world-state drawer was right, and only because it happened to carry stateKey already. They all key on the pair now, and deleting a branch bumps it too — that is the one operation that changes what is stored without a turn being played and without the story on the current path moving by a single action. tools/branch_fixture.py is the thing that could show it. The stress fixture's world state is empty, so it cannot answer whether a switch puts the scoreboard back, and its story is one branch. This builds a small bootable adventure with a stat schema, a gold script, two takes on one turn that differ by 35 hit points, a fork, and a memory on each side — with both branches the same length on purpose, because equal length is precisely the case a length-based key cannot see. Verified in a browser with the drawer open: hp 60 to 95 and back, the bar redrawn, the story swapped to the other take, Insights carrying the scratch and not the beating. The Memory Bank deliberately does not change on a switch: the drawer is adventure-wide so a memory is always findable to delete, and retrieval is the path-scoped half. 396 tests, build clean, no new lint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015H5qiyiR7gtFQaoDphHZ3g
This commit is contained in:
committed by
Parth
co-authored by
Claude Opus 5
parent
84827f0f37
commit
811368d048
@@ -1091,7 +1091,7 @@ function orderBranches(branches) {
|
||||
// Delete is here rather than in some later subphase because nothing prunes a
|
||||
// tree on its own — this panel is the first place a fork can be made, so it
|
||||
// has to be the first place one can be unmade.
|
||||
function BranchPanel({ advId, refreshKey, onSwitched, onError }) {
|
||||
function BranchPanel({ advId, refreshKey, onSwitched, onTreeChanged, onError }) {
|
||||
const [branches, setBranches] = useState(null)
|
||||
const [failed, setFailed] = useState(null)
|
||||
const [busyId, setBusyId] = useState(null)
|
||||
@@ -1113,6 +1113,10 @@ function BranchPanel({ advId, refreshKey, onSwitched, onError }) {
|
||||
try {
|
||||
await work()
|
||||
setTick((t) => t + 1)
|
||||
// Deleting a branch takes its memories with it, and nothing else on the
|
||||
// screen would hear about that — no turn is played, and the story on the
|
||||
// current path does not change by a single action.
|
||||
onTreeChanged()
|
||||
} catch (err) {
|
||||
onError(err.message)
|
||||
} finally {
|
||||
@@ -1841,8 +1845,12 @@ export default function Play() {
|
||||
|
||||
return (
|
||||
<div className={`play-layout ${panel ? 'with-panel' : ''}`}>
|
||||
{/* Both drawers read per-adventure state that a branch switch puts back,
|
||||
so neither can key on the story's length alone: switching between two
|
||||
branches whose windows are both full changes every number in here
|
||||
without changing `actions.length` by one. */}
|
||||
<WorldStateDrawer advId={id} refreshKey={`${actions.length}:${stateKey}`} />
|
||||
<StatusDrawer advId={id} refreshKey={actions.length} />
|
||||
<StatusDrawer advId={id} refreshKey={`${actions.length}:${stateKey}`} />
|
||||
<div className="page play-page">
|
||||
<div className="page-header">
|
||||
<h1>{adventure.title}</h1>
|
||||
@@ -2021,7 +2029,10 @@ export default function Play() {
|
||||
onWorldStateChanged={() => setStateKey((k) => k + 1)} />
|
||||
) : panel === 'memory' ? (
|
||||
<MemoryPanel adventure={adventure} setAdventure={setAdventure}
|
||||
refreshKey={actions.length} />
|
||||
// The bank is adventure-wide, so a switch does not change it —
|
||||
// but deleting a branch deletes the memories that hung off it,
|
||||
// and that happens without a turn being played.
|
||||
refreshKey={`${actions.length}:${stateKey}`} />
|
||||
) : panel === 'scripts' ? (
|
||||
<ScriptsPanel advId={id} />
|
||||
) : panel === 'branches' ? (
|
||||
@@ -2035,11 +2046,16 @@ export default function Play() {
|
||||
// the two operations that move the head.
|
||||
refreshKey={`${actions.length}:${stateKey}`}
|
||||
onSwitched={adoptWindow}
|
||||
onTreeChanged={() => setStateKey((k) => k + 1)}
|
||||
onError={(message) => setToast({ text: message, isError: true })}
|
||||
/>
|
||||
) : (
|
||||
// Insights is the prompt as it would be sent *now*, which is built
|
||||
// from the story on the current path — so of everything on this
|
||||
// screen it is the panel a branch switch changes most completely.
|
||||
<InsightsPanel advId={id} inspectActionId={inspectActionId}
|
||||
onClearInspect={() => setInspectActionId(null)} refreshKey={actions.length} />
|
||||
onClearInspect={() => setInspectActionId(null)}
|
||||
refreshKey={`${actions.length}:${stateKey}`} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user