fix(movement+anim+session): clothing dedup, motion wire format, jump-skill default
Three separate fixes landed today, each addressing a specific bug the
user observed during live play:
1. NPC clothing changes by camera angle (InstancedMeshRenderer)
- Group key was (GfxObjId) only, so every humanoid NPC using the
same body mesh piled into one instance group; only the first
instance's texture was used for the entire DrawInstanced batch,
so which NPC's palette "won" changed as frustum culling and
iteration order shuffled entries.
- Now keyed by (GfxObjId, PaletteHash ^ SurfaceOverridesHash)
so only compatible instances batch; each unique appearance gets
its own draw call. Perf hit is small (humanoid NPCs each emit
one more draw call); visually every NPC is now stable.
2. GpuWorldState dedup on respawn
- Server re-sends CreateObject for the same guid on visibility
refresh / landblock crossing / appearance update. AppendLiveEntity
was blindly appending each time, so GpuWorldState accumulated
multiple copies of the same entity, each with its own
PaletteOverride / MeshRefs. That alone wasn't the clothing bug
(that was #1) but it would have caused other overlap problems
downstream.
- Added RemoveEntityByServerGuid + WorldGameState.RemoveById;
OnLiveEntitySpawnedLocked calls both before creating the new
entity so respawns replace cleanly.
3. Motion wire format — run animation sync with retail observers
- ACE's MovementData constructor only computes interpState.ForwardSpeed
on the WalkForward/WalkBackwards branch; every other ForwardCommand
falls into `else` and passes through WITHOUT speed set, giving
observers speed=0. Sending RunForward directly meant retail
clients saw us "run in place" while position drifted forward.
- Wire: always WalkForward + HoldKey.Run for running. ACE
auto-upgrades to RunForward with creature.GetRunRate() for
broadcast — correct command + correct speed at observers.
- Added per-axis FORWARD_HOLD_KEY / SIDE_STEP_HOLD_KEY /
TURN_HOLD_KEY so every active axis carries HoldKey.Run when
running (matches holtburger's build_motion_state_raw_motion_state).
- Added LocalAnimationCommand to MovementResult so our own
client still plays the RunForward cycle locally while the wire
stays WalkForward. Wire vs. local animation command are now
decoupled.
- Walk-backward wire command changed from WalkForward@-0.65 to
WalkBackward@1.0 (holtburger pattern).
- Strafe speed changed from 0.5 to 1.0 on wire AND local physics
(matches retail sidestep pace).
4. Jump height default + env-var tuning
- Default jumpSkill bumped from 100 → 200 (jump ≈ 3m at full
charge, closer to retail feel for a mid-level character).
- ACDREAM_RUN_SKILL and ACDREAM_JUMP_SKILL env vars now override
the defaults so the user can tune per-character until we parse
PlayerDescription and plumb real skill values through.
5. JustLanded signal on MovementResult
- Tracks airborne→grounded transition so future animation code
can fire the landing cycle when we land. Just a bool flag for
now — no consumer yet (the proper action-queue path will use it).
Not in this commit: jump animation itself. An earlier attempt to
SetCycle(Jump=0x2500003b) fed an Action-type motion into the SubState
cycle resolver, which produced a "torso" mis-render. Reverted. The
proper fix is porting the retail motion action-queue semantics into
AnimationSequencer — see docs/research/deepdives/r03-motion-animation.md
for the spec. That's the next session's work.
470 tests pass, build clean.
This commit is contained in:
parent
d951304875
commit
3308cddda7
6 changed files with 272 additions and 31 deletions
|
|
@ -201,6 +201,50 @@ public sealed class GpuWorldState
|
|||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove every entity with the given <paramref name="serverGuid"/> from
|
||||
/// all loaded landblocks AND all pending buckets, then rebuild the flat
|
||||
/// view. Used by the live <c>CreateObject</c> handler to de-duplicate
|
||||
/// when the server re-sends a spawn (visibility refresh, landblock
|
||||
/// crossing, etc.). Without this, multiple copies of the same NPC
|
||||
/// accumulate in the renderer, each with its own <c>PaletteOverride</c>
|
||||
/// and <c>MeshRefs</c> — producing "NPC clothing flickers as I turn the
|
||||
/// camera" because the depth test picks different duplicates frame-to-frame.
|
||||
///
|
||||
/// Safe to call with a server guid that's not currently present — no-op.
|
||||
/// </summary>
|
||||
public void RemoveEntityByServerGuid(uint serverGuid)
|
||||
{
|
||||
if (serverGuid == 0) return;
|
||||
|
||||
bool rebuiltLoaded = false;
|
||||
|
||||
// Scan loaded landblocks. ToArray() so we can mutate _loaded inside.
|
||||
foreach (var kvp in _loaded.ToArray())
|
||||
{
|
||||
var lb = kvp.Value;
|
||||
int foundCount = 0;
|
||||
for (int i = 0; i < lb.Entities.Count; i++)
|
||||
if (lb.Entities[i].ServerGuid == serverGuid) foundCount++;
|
||||
|
||||
if (foundCount == 0) continue;
|
||||
|
||||
var newList = new List<WorldEntity>(lb.Entities.Count - foundCount);
|
||||
foreach (var e in lb.Entities)
|
||||
if (e.ServerGuid != serverGuid) newList.Add(e);
|
||||
|
||||
_loaded[kvp.Key] = new LoadedLandblock(lb.LandblockId, lb.Heightmap, newList);
|
||||
rebuiltLoaded = true;
|
||||
}
|
||||
|
||||
// Scrub pending buckets too — a duplicate CreateObject may arrive
|
||||
// while the landblock is still loading.
|
||||
foreach (var kvp in _pendingByLandblock)
|
||||
kvp.Value.RemoveAll(e => e.ServerGuid == serverGuid);
|
||||
|
||||
if (rebuiltLoaded) RebuildFlatView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append an entity to a specific landblock's slot. Used by the live
|
||||
/// CreateObject path where the server spawns entities at a server-side
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue