Skip to content

Write live positions back into the spatial index cache during a query - #473

Merged
Frotty merged 2 commits into
masterfrom
spatial-index-annulus-writeback
Sep 3, 2026
Merged

Write live positions back into the spatial index cache during a query#473
Frotty merged 2 commits into
masterfrom
spatial-index-annulus-writeback

Conversation

@Frotty

@Frotty Frotty commented Sep 3, 2026

Copy link
Copy Markdown
Member

What

In spatialIndexBeginQuery, the annulus branch (cached distance between certainlyIn and certainlyOut) reads the unit's live position to decide the match. That position was discarded. It is now written back into lastX/lastY with the current sweep tick, so the next query in the same frame decides that unit from the cache and skips the two natives.

Per-entry padding

The stamp is only worth writing if something reads it. certainlyIn/certainlyOut come from the global maxDisp, the worst staleness of any entry in the registry, so on its own the write-back changed nothing: a stationary boundary unit fell into the annulus again on the next query with the same center and radius and read both natives a second time.

The annulus branch therefore re-tests against the padding the entry itself needs, tickDisplacement() * (age + 1), before deciding to read. The extra tick covers the cache write and the query sitting at opposite ends of their own ticks, which is the slack displacementBoundTicks already carries over requiredSweepCycleTicks(); currentMaxDisplacement() is now that same expression at the worst age, so the two bounds cannot drift apart.

After a write-back the age is 0, so the band is one tick of movement wide rather than a whole sweep cycle. At 500 units and 128 per tick displacementBoundTicks is 5, so the second query in the frame sees an annulus a fifth as wide, about ±16 world units instead of ±82.

The cell window and the outer bands stay on the global bound: the window is chosen before any entry is known, and the outer bands are the cheap reject that keeps most entries out of this arithmetic. spatialIndexBeginBoxQuery has the same split and the same discarded live read but no write-back, so giving it one is a separate change.

Why not relink

Calling refreshUnit here would be wrong: a relink can move the unit into a cell the walk has not visited yet, and it would be visited and pushed twice. The write-back therefore happens only while the unit is still in its linked cell. Displacement is bounded well under one cell edge, so this covers nearly every case, and the invariant that a cached position lies in its linked cell holds.

Context

Found while reading the emitted Lua for the synchronized-cast case (12+ range queries over 500+ units in one frame). The larger costs there are compiler-side and are specified in LUA_HOT_PATH_SPEC.md in the WurstScript repo (wurstscript/WurstScript#1284). This is the one stdlib-side change that is independent of those.

Checks

  • grill typecheck: succeeded
  • grill test (full suite): all tests succeeded
  • Soundness of the narrowed band is argued above rather than tested: the interpreter has no units, so UnitSpatialIndexTests covers grid arithmetic only and every query path is asserted in StdlibIngameTests
  • The ingame parity tests (StdlibIngameTests) need a running map and were not run here.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-03T20:27:35.534444Z 40a9c25 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8767364c23

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wurst/util/UnitSpatialIndex.wurst Outdated
The annulus branch of spatialIndexBeginQuery already pays GetUnitX and
GetUnitY for a unit whose cached position is stale by up to maxDisp. It
used the live position for the distance test and threw it away, so every
further query in the same frame read it again. Twelve synchronized range
queries over the same crowd paid the two natives twelve times per boundary
unit.

The live position is now stored back, together with the current sweep tick,
but only while the unit is still in its linked cell. Relinking mid-walk is
not safe: the unit could move into a cell this walk has not reached yet and
be visited and pushed a second time. The same-cell case covers nearly
everything, since displacement is bounded well under one cell edge.
@Frotty
Frotty force-pushed the spatial-index-annulus-writeback branch from 8767364 to b00ec53 Compare September 3, 2026 19:49
The write-back stamps lastSweepTick, but nothing read it back: certainlyIn
and certainlyOut come from the global maxDisp, which is the worst staleness
of any entry in the registry. A stationary unit in the annulus therefore
landed in the annulus again on the next query of the same center and radius,
and paid GetUnitX and GetUnitY once more. The write-back saved nothing in
precisely the case it was written for.

The annulus branch now re-tests against the padding the entry itself needs,
tickDisplacement times age plus one, before deciding to read. The extra tick
covers the write and the query sitting at opposite ends of their own ticks,
the same slack displacementBoundTicks already carries over a sweep cycle.
Both directions stay conservative: within radius minus that padding the true
position cannot be outside, beyond radius plus it cannot be inside.

For an entry written back a moment earlier the age is zero, so the band is
one tick of movement wide instead of a whole sweep cycle. With 500 units at
128 per tick the bound is five ticks, so the second query's annulus is a
fifth as wide and nearly every boundary unit is decided from the cache.

The refinement sits inside the annulus branch alone. The cell window and the
outer bands stay on the global bound: the window has to be chosen before any
entry is known, and the outer bands are the cheap reject that keeps entries
out of this arithmetic entirely.
@Frotty

Frotty commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 40a9c2590c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Frotty
Frotty merged commit ea3f8c4 into master Sep 3, 2026
2 checks passed
@Frotty

Frotty commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ea3f8c49e2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wurst/util/UnitSpatialIndex.wurst
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant