phase(N.4) Adjustment 6: add PartOverrides + HiddenPartsMask to WorldEntity
Resolves Adjustment 4 (Option A): WorldEntity now carries the server- sent AnimPartChange data as PartOverrides and a HiddenPartsMask bitmask. EntitySpawnAdapter.OnCreate populates AnimatedEntityState from these fields at spawn time. GameWindow's CreateObject handler converts the network-layer AnimPartChange records into lightweight PartOverride structs. This unblocks Task 22: the WbDrawDispatcher can now resolve per-part GfxObj overrides and hidden-part suppression from entity state. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
16a36dda8f
commit
5b4fd4b61d
4 changed files with 70 additions and 14 deletions
|
|
@ -992,6 +992,31 @@ behavior (does the adapter call the cache with the right args?). The
|
||||||
decode-byte conformance is structural: same function = same output.
|
decode-byte conformance is structural: same function = same output.
|
||||||
Mark Task 20 ✅ structurally; no separate test file.
|
Mark Task 20 ✅ structurally; no separate test file.
|
||||||
|
|
||||||
|
### Adjustment 6 (2026-05-08): Resolved Adjustment 4 — Option A (fields on WorldEntity)
|
||||||
|
|
||||||
|
**Context.** Adjustment 4 deferred the `HiddenPartsMask` + `AnimPartChanges`
|
||||||
|
plumbing decision to Task 22. Two options:
|
||||||
|
- **A**: add fields to `WorldEntity`, populate at spawn time
|
||||||
|
- **B**: thread as separate args into `EntitySpawnAdapter.OnCreate`
|
||||||
|
|
||||||
|
**Decision: Option A.** Reasoning:
|
||||||
|
1. The data is already computed at spawn time in GameWindow's CreateObject
|
||||||
|
handler — adding two fields is a 4-line change.
|
||||||
|
2. Option B would spread network-layer types across the streaming subsystem,
|
||||||
|
violating the same separation-of-concerns principle as Adjustment 2.
|
||||||
|
3. The 0xF625 ObjDescEvent (appearance update) replays through the same
|
||||||
|
spawn path, so WorldEntity fields work automatically for hot-swap updates.
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- `WorldEntity` gains `PartOverrides: IReadOnlyList<PartOverride>` (default
|
||||||
|
empty) and `HiddenPartsMask: ulong` (default 0).
|
||||||
|
- `PartOverride(byte PartIndex, uint GfxObjId)` is a lightweight record struct
|
||||||
|
in Core.World that decouples from the network-layer `CreateObject.AnimPartChange`.
|
||||||
|
- `EntitySpawnAdapter.OnCreate` now calls `state.HideParts(entity.HiddenPartsMask)`
|
||||||
|
and `state.SetPartOverride(...)` for each override.
|
||||||
|
- GameWindow's CreateObject handler builds the `PartOverride[]` from the
|
||||||
|
server-sent `AnimPartChanges` list.
|
||||||
|
|
||||||
### Task 6 (original — kept for history)
|
### Task 6 (original — kept for history)
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
|
||||||
|
|
@ -2407,6 +2407,19 @@ public sealed class GameWindow : IDisposable
|
||||||
SubPalettes: ranges);
|
SubPalettes: ranges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AcDream.Core.World.PartOverride[] entityPartOverrides;
|
||||||
|
if (animPartChanges.Count == 0)
|
||||||
|
{
|
||||||
|
entityPartOverrides = Array.Empty<AcDream.Core.World.PartOverride>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entityPartOverrides = new AcDream.Core.World.PartOverride[animPartChanges.Count];
|
||||||
|
for (int i = 0; i < animPartChanges.Count; i++)
|
||||||
|
entityPartOverrides[i] = new AcDream.Core.World.PartOverride(
|
||||||
|
animPartChanges[i].PartIndex, animPartChanges[i].NewModelId);
|
||||||
|
}
|
||||||
|
|
||||||
var entity = new AcDream.Core.World.WorldEntity
|
var entity = new AcDream.Core.World.WorldEntity
|
||||||
{
|
{
|
||||||
Id = _liveEntityIdCounter++,
|
Id = _liveEntityIdCounter++,
|
||||||
|
|
@ -2416,6 +2429,7 @@ public sealed class GameWindow : IDisposable
|
||||||
Rotation = rot,
|
Rotation = rot,
|
||||||
MeshRefs = meshRefs,
|
MeshRefs = meshRefs,
|
||||||
PaletteOverride = paletteOverride,
|
PaletteOverride = paletteOverride,
|
||||||
|
PartOverrides = entityPartOverrides,
|
||||||
};
|
};
|
||||||
|
|
||||||
var snapshot = new AcDream.Plugin.Abstractions.WorldEntitySnapshot(
|
var snapshot = new AcDream.Plugin.Abstractions.WorldEntitySnapshot(
|
||||||
|
|
|
||||||
|
|
@ -39,15 +39,10 @@ namespace AcDream.App.Rendering.Wb;
|
||||||
/// </para>
|
/// </para>
|
||||||
///
|
///
|
||||||
/// <para>
|
/// <para>
|
||||||
/// <b>Adjustment 4</b>: <see cref="WorldEntity"/> does not currently expose
|
/// <b>Adjustment 6</b> (resolved Adjustment 4): <see cref="WorldEntity"/> now
|
||||||
/// <c>HiddenPartsMask</c> or <c>AnimPartChanges</c> as direct fields (those
|
/// carries <see cref="WorldEntity.PartOverrides"/> and
|
||||||
/// live on the network-layer spawn record and are consumed upstream before
|
/// <see cref="WorldEntity.HiddenPartsMask"/>. <see cref="OnCreate"/> applies
|
||||||
/// the <see cref="WorldEntity"/> is built). When those fields are promoted to
|
/// both to the created <see cref="AnimatedEntityState"/>.
|
||||||
/// <see cref="WorldEntity"/>, <see cref="OnCreate"/> should call
|
|
||||||
/// <see cref="AnimatedEntityState.HideParts"/> and
|
|
||||||
/// <see cref="AnimatedEntityState.SetPartOverride"/> here. For now the mask
|
|
||||||
/// stays at 0 (no parts hidden) and no part overrides are set — the draw
|
|
||||||
/// dispatcher falls through to Setup defaults for every part.
|
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class EntitySpawnAdapter
|
public sealed class EntitySpawnAdapter
|
||||||
|
|
@ -125,11 +120,10 @@ public sealed class EntitySpawnAdapter
|
||||||
var sequencer = _sequencerFactory(entity);
|
var sequencer = _sequencerFactory(entity);
|
||||||
var state = new AnimatedEntityState(sequencer);
|
var state = new AnimatedEntityState(sequencer);
|
||||||
|
|
||||||
// Adjustment 4 placeholder: when WorldEntity gains HiddenPartsMask +
|
// Adjustment 6: WorldEntity now carries PartOverrides + HiddenPartsMask.
|
||||||
// AnimPartChanges fields, apply them here:
|
state.HideParts(entity.HiddenPartsMask);
|
||||||
// state.HideParts(entity.HiddenPartsMask);
|
foreach (var po in entity.PartOverrides)
|
||||||
// foreach (var apc in entity.AnimPartChanges)
|
state.SetPartOverride(po.PartIndex, po.GfxObjId);
|
||||||
// state.SetPartOverride(apc.PartIndex, apc.NewModelId);
|
|
||||||
|
|
||||||
_stateByGuid[entity.ServerGuid] = state;
|
_stateByGuid[entity.ServerGuid] = state;
|
||||||
return state;
|
return state;
|
||||||
|
|
|
||||||
|
|
@ -55,4 +55,27 @@ public sealed class WorldEntity
|
||||||
/// visible trunk, producing "partial passthrough" bugs.
|
/// visible trunk, producing "partial passthrough" bugs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float Scale { get; init; } = 1.0f;
|
public float Scale { get; init; } = 1.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-sent part-swap overrides from <c>AnimPartChange</c>. Each entry
|
||||||
|
/// replaces a Setup part's GfxObj with an alternate model (clothing, weapons,
|
||||||
|
/// helmets). Carried on the entity so <c>EntitySpawnAdapter</c> can populate
|
||||||
|
/// <c>AnimatedEntityState</c>'s override map at spawn time. Empty for atlas-
|
||||||
|
/// tier entities.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<PartOverride> PartOverrides { get; init; } = Array.Empty<PartOverride>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bitmask of hidden Setup parts. Bit <c>i</c> set hides part <c>i</c> at
|
||||||
|
/// draw time. Sourced from the server's <c>CreateObject</c> record when
|
||||||
|
/// present. Zero (no parts hidden) is the default.
|
||||||
|
/// </summary>
|
||||||
|
public ulong HiddenPartsMask { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lightweight value type for a server-sent <c>AnimPartChange</c> (part index
|
||||||
|
/// → replacement GfxObj id). Decouples <c>WorldEntity</c> (Core) from the
|
||||||
|
/// network-layer <c>CreateObject.AnimPartChange</c> type.
|
||||||
|
/// </summary>
|
||||||
|
public readonly record struct PartOverride(byte PartIndex, uint GfxObjId);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue