Resolve the head once a flush, not once a node

The identity map holds weak references. A branch row nobody keeps a strong
reference to is collected between two nodes, so resolving the head inside the
placement loop read it back from the database for every node in the flush: 201
SELECTs on `branches` to write 200 actions, and 36 s -> 45 s on the same 297
tests. Nothing about any result changed, which is why only a stopwatch found
it, and why there is now a test counting the reads.

Also: the two reads left un-pathed on purpose say so where they live —
`max_action_index` allocates the legacy `index` and must stay adventure-wide
or two branches issue the same number, and export is a flat v1 bundle whose
reader has no idea branches exist. And `Adventure.actions` keeps its `index`
ordering, because ordering the collection by depth would not make it a story:
it is every branch's actions, and a path is a selection out of it.

318 tests green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Dvvqn9ZDR4ixeFPHNbww7
This commit is contained in:
parththakkar106
2026-08-18 19:14:07 +05:30
committed by Parth
co-authored by Claude Opus 5
parent 563b9af9cf
commit 05a2a77e4c
6 changed files with 154 additions and 20 deletions
+6
View File
@@ -1144,6 +1144,12 @@ def export_adventure(
# Export is the one read that genuinely wants every attempt, so it asks for
# the deferred `variants` column up front — iterating adv.actions instead
# would lazy-load it one row at a time.
#
# And the one read deliberately left un-pathed: a backup wants the whole
# adventure, not the branch its owner happens to be standing on. `index`
# orders it because the v1 bundle is a flat list keyed on index and its
# reader has no idea branches exist — which is exactly why SP6 replaces the
# format rather than quietly widening this query.
exported_actions = (
db.query(models.Action)
.filter(models.Action.adventure_id == adv.id)