PATCH  ·  v1.0.7  ·  2026-05-24

NPC Z handling — third time's the charm (this one's data-driven)

Third NPC physics patch in two days. The previous two tried to be clever with heightmap snapping and broke as much as they fixed. This one comes from logging the actual NPC spawn data and looking at what's really there.

What the v1.0.6 fix broke

v1.0.6 classified NPCs as "indoor" if their spawn JSON Z was more than 3m above the heightmap, and only then preserved the authored position. That rule has a fatal blind spot: it doesn't handle NPCs whose spawn Z is below the heightmap.

The Two Crowns mansion sits in a basin. The mansion's interior floor is at Z=202. The surrounding hilltop heightmap reads Z=219. Under v1.0.6, every Kyrios Cultist inside the mansion got its Z "corrected" from 202 to 219 — pulled 17 metres up into the ceiling. From below, you could see them floating in the ceiling tile; from above, the bodies were inside the floor. Same story for ocean-floor mobs (snapped 400m up to the cliff above the water) and any NPC whose authored elevation happened to be lower than nearby terrain.

What the data actually shows

I ran a diagnostic build that logged every NPC spawn's JSON Z, heightmap Z, and delta. 20,860 spawn events. The distribution:

  • 62% of NPCs spawn on terrain (delta within ±0.5m). Heightmap matches authored data; either path works.
  • 5% are slightly buried (delta -2 to 0). These are genuine drift cases — small terrain edits dipped an outdoor mob below the dirt. Snap-up corrects them.
  • 15% are intentionally below the surrounding terrain (delta -∞ to -2). Mansions in basins, dungeons, ocean floors, indoor spaces where heightmap reads the roof above. Snap-up would be catastrophic (and it was).
  • 18% are intentionally above the heightmap (delta > 0.5). Building upper floors, towers, skybox content, NPCs at Z=1100 on flying ships. Snap-down would be catastrophic (and it would have been).

There is no reliable code-only signal to tell "this 1m of drift is a bug" from "this 1m of elevation is a deck". The JSON values authored by AAEmu are right the vast majority of the time. The server has no business inventing replacements.

The rule, finalized

Snap NPC Z to heightmap only when the NPC is slightly buried — delta in [-2, 0). Trust the JSON in every other case.

Same rule applies both at spawn and during chase movement. The chase code also no longer lerps Z toward the chase target — it preserves the NPC's current Z, then re-applies the narrow-band snap. So an NPC walking across a hill in pursuit re-snaps if its Z dips slightly below the terrain, but otherwise rides the terrain it spawned on.

What this fixes

  • Mansion ground- AND upper-floor NPCs visible at their authored positions. Two Crowns Kyrios Cultists, Paupers, Outlaw Spearmen — all back on the marble.
  • Ocean-floor / dungeon NPCs stay where authored. No more 400m yanks.
  • Tower and skybox NPCs stay at altitude. Anyone spawned at Z=1100 stays at Z=1100.
  • Chase Z preservation kept. Running upstairs still works as an escape — mob doesn't rise through the floor.
  • Slight outdoor drift still corrected. A wolf that walked into a terrain seam and sank 1.5m gets re-snapped on its next movement tick.

What this no longer attempts

Floating bears with spawn-data drift above terrain. If npc_spawns.json places a bear 3m above where the terrain actually is, the server now trusts that data and the bear floats. This is honest — the data is wrong, and the server has been making bad guesses by trying to fix it. When a floater is spotted in-game, the fix is a one-line edit to npc_spawns.json for that specific entry.

If you find one, ping me with the mob name + zone and I'll correct the data.

Apologies for the v1.0.5 → v1.0.6 → v1.0.7 churn

Each iteration was directionally right for some cases and catastrophically wrong for others. v1.0.5 fixed bears and broke indoor. v1.0.6 fixed upper indoor and broke lower indoor + dungeons + oceans + towers. v1.0.7 is the one that's actually backed by data instead of guesswork.

Behind the scenes

Two server-source patches (Npc.cs, NpcSpawnerNpc.cs), unified rule. The IsIndoor property added yesterday is gone. Memory inventory entry #15 rewritten to reflect the empirical rule and link to the diagnostic data. No client patch — same .pak, no download.

« All Patch Notes