feat(render): Campaign OVERHAUL S2 chunk 5 + closeout — registry is the only render membership owner
Chunk 5 (consumer cutover): WalkProductionWorldData's per-cell views are borrowed from ShadowObjectRegistry.GetRetailPartEntriesInCell and resolved through RenderSceneQuery.TryGetByLocalEntityId; every render-side sweep, bucket, parent-cell and root-position fallback is deleted (AD-116 for the one-frame registry→scene window, counted in UnregisteredRenderMembershipCount). A live entity with visual parts but no collision geometry registers render-only (LiveEntityCollisionBuilder computes the part array before the empty-shapes gate). Closeout fixes found while landing it: - RefloodOwnerForLandblock forwards the retained part array — a reflood is retail's recalc_cross_cells over the SAME CPartArray; without it every owner touched by a landblock replacement commit lost its render membership. - Non-colliding DAT statics register render-only from BOTH publishers (LandblockPhysicsPublisher.PublishStaticEntity, LandblockPhysicsContentBuilder.RegisterRenderOnlyStatic). The G2 self-gate pixel diff caught them vanishing (Facility Hub wall panels): retail floods every object regardless of collision (CEnvCell::init_static_objects 0x0052c350, add_shadows_to_cells 0x00514ae0). - S2 dual review fix batch (arch + retail lens, lead-verified): Suspend clears the retail product (remove_shadows_from_cells 0x00511230 is one transaction); AttachChild/DetachChild advance the mutation revision so a prepared SetPosition cannot clobber a child's rows; an attached child never floods on its own re-registration; RemoveLandblock and the non-rooted RetireOwnerFromLandblock prune retail rows (render-only statics end with their landblock); a render-only owner's no-cell-array commit republishes at its destination cell (AD-117); an empty non-null part array is treated as null; per-move closures/LINQ replaced by index loops; EnvCell shells stay out of the scene's LocalEntityId index (payload-less records); the index predicate compares the id; the dead per-cell scene indices are deleted. Register: AD-116 (chunk 5), AD-117 (four residual Contract A/B readings). Evidence: s2-membership-ownership-map.md §8 (chunk 5) and §9 (closeout). Gates (Release): Core 4,984/4,984; Content 214/214; Runtime 1,884/1,884; App hermetic lane 6,760/6,760; App InstalledDat lane 217 pass / 1 skip / 2 pre-existing #383 layout-fixture failures; App Windows lane 1/1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
f30039d3d9
commit
c94a1a407e
20 changed files with 2141 additions and 1026 deletions
|
|
@ -155,6 +155,78 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
Assert.Equal(part, shape.GfxObjId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign OVERHAUL S2 chunk 5 (item B): a short-lived spell/visual
|
||||
/// effect object's Setup can carry a visual part with no physics BSP and
|
||||
/// no CylSpheres/Spheres — retail's <c>FindObjCollisions</c> synthesizes
|
||||
/// no COLLISION shape for such a part (this file's own
|
||||
/// <see cref="ShapelessSetupWithRadius_ProducesNoRegistration"/> remark),
|
||||
/// but the part is still a real visual part
|
||||
/// <c>CPartArray::AddPartsShadow</c> must register for render (Contract
|
||||
/// B). <see cref="LiveEntityCollisionBuilder.Build"/> must not collapse
|
||||
/// this case to "no registration at all" just because the COLLISION
|
||||
/// dispatch came up empty — it must still return a registration whose
|
||||
/// <c>RenderParts</c> carries the part, and
|
||||
/// <see cref="LiveEntityCollisionBuilder.Register"/> (the exact call
|
||||
/// <c>DatLiveEntityProjectionMaterializer</c> makes) must register it
|
||||
/// render-only: a real retail CELLARRAY with per-cell
|
||||
/// <c>RetailPartEntry</c> rows, but NO collision row anywhere.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Build_PartWithNoCollisionButResolvableVisualBounds_RegistersRenderOnly()
|
||||
{
|
||||
const uint part = 0x0100BEEFu;
|
||||
var setup = new Setup();
|
||||
setup.Parts.Add(part);
|
||||
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
WorldEntity entity = Entity();
|
||||
record.WorldEntity = entity;
|
||||
var builder = new LiveEntityCollisionBuilder(
|
||||
physicsBspBounds: _ => null,
|
||||
PoseResolver(),
|
||||
getGfxObj: _ => null,
|
||||
getVisualBounds: id => id == part
|
||||
? new GfxObjVisualBounds
|
||||
{
|
||||
Min = new Vector3(-1f, -1f, -1f),
|
||||
Max = new Vector3(1f, 1f, 1f),
|
||||
Center = Vector3.Zero,
|
||||
Radius = 1.5f,
|
||||
HalfExtents = new Vector3(1f, 1f, 1f),
|
||||
}
|
||||
: null);
|
||||
|
||||
LiveEntityCollisionRegistration registration = Assert.IsType<LiveEntityCollisionRegistration>(
|
||||
builder.Build(
|
||||
entity,
|
||||
setup,
|
||||
[part],
|
||||
spawn,
|
||||
record.ServerGuid,
|
||||
record.Generation,
|
||||
record.WorldEntity!,
|
||||
record.FinalPhysicsState,
|
||||
Vector3.Zero));
|
||||
|
||||
Assert.Empty(registration.Shapes);
|
||||
ShadowShape renderPart = Assert.Single(registration.RenderParts);
|
||||
Assert.Equal(part, renderPart.GfxObjId);
|
||||
|
||||
var registry = new ShadowObjectRegistry();
|
||||
LiveEntityCollisionBuilder.Register(registry, registration);
|
||||
|
||||
Assert.True(
|
||||
registry.TryGetRetailCellArray(entity.Id, out IReadOnlyList<uint> cells));
|
||||
Assert.NotEmpty(cells);
|
||||
Assert.Empty(registry.GetOwnerCells(entity.Id));
|
||||
var entries = registry.GetRetailPartEntriesInCell(cells[0])
|
||||
.Where(e => e.EntityId == entity.Id)
|
||||
.ToList();
|
||||
Assert.Single(entries);
|
||||
Assert.Equal(part, entries[0].GfxObjId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AP-152. Every other fixture in this file is primitive-only or BSP-only,
|
||||
/// so nothing at the App layer used to exercise the CylSphere+BSP
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue