Skip to content

fix(dispatch): make world actions no-ops once the player is dead - #207

Open
nnunley wants to merge 1 commit into
mainfrom
fix/dispatch-after-death
Open

nnunley wants to merge 1 commit into
mainfrom
fix/dispatch-after-death

Conversation

@nnunley

@nnunley nnunley commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Problem

A replay whose action log continues past the player's death crashes the game:

FATAL ERROR: cannot add nil and let-go.lang.Int
  play-replay → dispatch → update-world → autoexplore-step → bfs-path

When the player dies, update-world removes the player entity. Any later :autoexplore, :descend or :ascend then reads a nil position. :wait and :rest don't crash, but they keep advancing turns on a dead world.

Live play never dispatches after death, because the death screen takes the input. Replays can:

Fix

dispatch returns the world unchanged for replay actions once the player is dead. It uses the same test update-world uses to raise the death screen. There is no seed fold and no log entry, so existing replay codes stay byte-identical: a valid code never contains actions after death.

Verification

  • New actions-after-death-are-noops test covers both dead shapes (player entity removed, and player at 0 HP) across autoexplore, stairs, wait, rest, move, pickup, fire and item actions. It failed with the production crash before the fix and passes after.
  • Full suite passes: lg xsofy/test/run.lg → 334 tests, 2930 assertions, 0 failures.
  • End to end: played a code with actions past death through XSOFY_REPLAY=… lg main.lg. Before the fix it hit FATAL ERROR with the trace above; after, playback ends on the normal death screen.

🤖 Generated with Claude Code

A replay whose action log outlives the player crashed the game: after
death the player entity is removed, and the next :autoexplore/:descend/
:ascend hit nil positions in bfs-path/stairs code ("cannot add nil and
let-go.lang.Int"). :wait/:rest didn't crash but kept advancing turns on
a dead world.

Live play never dispatches after death (the death screen owns input), so
this only bites replays: a ?replay= code that diverges on a newer build,
or a balance-harness log replayed on the wrong starting world. Reproduced
via XSOFY_REPLAY with a code that has actions past death: FATAL ERROR
before, death screen after.

dispatch now returns the world unchanged for replay actions when the
player is dead, using the same test update-world uses to raise the death
screen. No seed fold, no log entry, so existing codes stay byte-identical.
@nnunley
nnunley requested a review from mparrett September 22, 2026 19:37
Comment thread xsofy/dispatch.lg
(ignored-action? action)
world

(and (replay-action? action) (player-dead? world))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-parented DAG node after death (xsofy.dag)

This branch returns the world unchanged, :seed included. dag/dispatch-gs still calls commit for every replay action (dag.lg ~L229-231). commit-id is just (:seed world), so the new node's id equals the current HEAD id. commit then runs (assoc-in gs [:nodes id] {:parent (:head gs) ...}), which overwrites HEAD's own node with {:parent id :action a}. The node is now its own parent, and its real parent link back to the origin is lost.

Scenario: (dag/replay-gs w h seed log) with a log that continues past death (the case this PR targets). Or any dispatch-gs of :wait on a dead world. After that:

  • actions-from-origin / materialize / branch on that id never terminate. cur never changes and acc grows without bound.
  • walk-parents stops at the same id forever.
  • History before the death turn can't be reached from that node.

Before this PR the seed always advanced, so ids stayed distinct. Fix: in dispatch-gs, skip commit when (identical? w' (:world gs)) (or when the id doesn't change). Another option is to apply the same dead-player guard there, and in start-replay-action, which still folds and logs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the skipping on unchanged. Note that there should probably still be mutation to the turn counter.

I wonder if we need a proxy for the dead player - ideally the world aim should carry on without them

@mparrett mparrett added the deploy-preview Publish a gh-pages WASM preview for this PR label Sep 23, 2026
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://nooga.github.io/xsofy/pr-preview/pr-207/

Built to branch gh-pages at 2026-09-23 16:02 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@mparrett
mparrett self-requested a review September 23, 2026 16:19

@mparrett mparrett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good. One small ask on the inline thread before merging.

@mparrett

Copy link
Copy Markdown
Collaborator

@nnunley No rush, following up on this one in case it got buried. Small ask here when you can - #207 (comment)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deploy-preview Publish a gh-pages WASM preview for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants