Skip to content

Fix zone batch update sync causing floor item ghosting - #1135

Merged
GregHib merged 5 commits into
GregHib:mainfrom
HarleyGilpin:bugs/fix-floor-item-ghosting
Aug 6, 2026
Merged

Fix zone batch update sync causing floor item ghosting#1135
GregHib merged 5 commits into
GregHib:mainfrom
HarleyGilpin:bugs/fix-floor-item-ghosting

Conversation

@HarleyGilpin

@HarleyGilpin HarleyGilpin commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Closes #1000, #1087

Root cause is a set of sync gaps in the zone batch update pipeline rather than floor items specifically, items were just the visible symptom since spawns constantly re-queue additions.

Screencast_20260805_143328.webm

Problems

  1. Private updates applied to the wrong zone. All floor item packets are private and sent as bare single packets, which the client applies relative to its active zone, set by whatever clearZone/batch header happened to come last. When that pointer pointed at a neighbouring zone, the item rendered one zone (8 tiles) off in the direction of travel.
  2. Out-of-map offsets clamped instead of skipped. getZoneOffset used safeMinus, so zones south/west of the loaded map base clamped to offset 0 and rendered 8 tiles north/east of their true tile (reproducible with the logs at 3155 in zone 389,394 after logging in at 3114,3209).
  3. Stale batch replay. The encoded map was never cleared, so mid-tick sends (region change/batch zone change) replayed the previous tick's batch, re-applying object add/removes (door flicker on the Wizards' Tower bridge) and duplicating item additions. This affects objects just the same as items, as you noted — items only made it visible.
  4. Updates lost during map load. updateRegion sent zone contents immediately after the map region packet, but the client loads maps asynchronously and drops zone updates in the window before FinishRegionLoad, viewport.loaded was tracked but never gated anything, so items vanished after every region reload.
  5. Duplicate region updates per tick. checkReload and DynamicZones.reloadCallback could both call updateRegion in the same tick with no in-progress check, and two send paths with different anchors (lastBatchZone vs steps.previous.zone) cleared and resent the same entered zones twice per tick.

Changes

  • Send the active zone (UPDATE_ZONE) before individual private updates so they always apply to the correct zone (matches partial-follows behaviour before each bare update)
  • Skip zones outside the loaded map instead of clamping their offsets
  • Clear encoded alongside batches each tick and skip encoding empty batches (also fixes unbounded growth)
  • Single tick-end send path anchored on lastBatchZone; removed the steps.previous-anchored duplicate
  • Full zone content resend moved to the FinishRegionLoad handler, with sends gated on viewport.loaded so nothing is fired into the load window
  • DynamicZones.version stamped on the viewport per region update so crossedDynamicBoarder no longer force-reloads players already updated for the current dynamic state

HarleyGilpin and others added 2 commits August 5, 2026 14:31
- Send active zone before individual updates so they can't apply to the wrong zone
- Clear encoded batches each tick to stop last tick's updates being resent on zone change
- Skip zones outside the loaded map instead of clamping offsets
- Defer zone content resend until the client confirms the map has loaded
- Version dynamic zone changes to prevent duplicate region updates in one tick

Closes GregHib#1000

@GregHib GregHib left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Found a bug:

  1. Login
  2. Run > 8 tiles away (i.e. west)
  3. Drop an item

Expected:

  • Item spawns on floor

Actual:

  • Item is removed from inventory but not seen on floor by owner until turned visible to all

Client reads packet 41 as y (inverse), level (add), x (add); the unused
encoder wrote x first, offsetting private updates by a zone whenever the
player wasn't diagonal-symmetric to the map centre.
@HarleyGilpin

HarleyGilpin commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Found and fixed in a99c656. the UPDATE_ZONE (41) encoder had x/y swapped. It was previously unused so it had never been exercised: the client reads packet 41 as y (byte inverse), level (byte add), x (byte add), but the encoder wrote x first. Dropping in place worked because the player sits at the symmetric centre offset (6,6) where the swap is invisible; running 8+ tiles west makes x=5/y=6, so the active zone landed one zone off and the private item add rendered in the wrong zone until the public reveal took the (correct) batch path.

Verified against the deob: opcode 33 and 48 read orders match the existing clearZone/sendBatch encoders.

@GregHib GregHib left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Tested the original scenario along with common situations with multiple players and instances. LGTM!

Golden bytes were recorded from the unverified encoder; y-first order now
matches the client and the clear zone packet convention.
@GregHib
GregHib merged commit 8422285 into GregHib:main Aug 6, 2026
2 checks passed
@HarleyGilpin
HarleyGilpin deleted the bugs/fix-floor-item-ghosting branch August 6, 2026 13:31
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.

Fix floor item ghosting

2 participants