refactor(streaming): compose landblock presentation transaction

Own render, physics, DAT-static presentation, and retained static-resource rebinds behind retryable receipts so a failed publication resumes its exact unfinished suffix without replaying retail create-time defaults.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-21 21:45:16 +02:00
parent e1110f7e54
commit ea0da8c8ae
15 changed files with 1744 additions and 89 deletions

View file

@ -64,6 +64,42 @@ public sealed class RetailStaticAnimatingObjectSchedulerTests
Assert.Equal(2, posePublishes);
}
[Fact]
public void Rebind_RetainsSequencerAndPublishesIntoReplacementEntity()
{
var loader = new Loader();
loader.Add(AnimationId, TwoFrameAnimation());
var scheduler = new RetailStaticAnimatingObjectScheduler(
loader,
(_, _) => { },
(_, _, _) => { });
Setup setup = MakeSetup();
WorldEntity first = MakeEntity();
ScriptActivationInfo info = new(
ScriptId: 0,
PartTransforms: first.IndexedPartTransforms,
PartAvailability: first.IndexedPartAvailable,
Setup: setup,
DefaultAnimationId: AnimationId,
UsesStaticAnimationWorkset: true);
scheduler.Register(first, info);
scheduler.Tick(1f / 60f);
float phaseBeforeRebind = first.MeshRefs[0].PartTransform.Translation.X;
WorldEntity replacement = MakeEntity();
replacement.Position = new Vector3(4f, 5f, 6f);
scheduler.Rebind(replacement, info with
{
PartTransforms = replacement.IndexedPartTransforms,
PartAvailability = replacement.IndexedPartAvailable,
});
scheduler.Tick(1f / 60f);
Assert.Equal(1, scheduler.Count);
Assert.True(
replacement.MeshRefs[0].PartTransform.Translation.X > phaseBeforeRebind);
}
[Fact]
public void ShortStaticAnimFrame_RetainsCompleteTrailingPartState()
{

View file

@ -282,6 +282,7 @@ public sealed class EntityScriptActivatorTests
registerCount++;
registeredEntity = entity;
Assert.Same(info, actual);
return true;
},
unregisterDatStaticAnimationOwner: ownerId =>
{
@ -313,7 +314,11 @@ public sealed class EntityScriptActivatorTests
ScriptId: 0,
PartTransforms: Array.Empty<Matrix4x4>(),
DefaultAnimationId: 0x03000001u),
registerDatStaticAnimationOwner: (_, _) => registerCount++);
registerDatStaticAnimationOwner: (_, _) =>
{
registerCount++;
return true;
});
activator.OnCreate(MakeEntity(0x70000420u, Vector3.Zero));
@ -335,7 +340,11 @@ public sealed class EntityScriptActivatorTests
PartTransforms: Array.Empty<Matrix4x4>(),
DefaultAnimationId: 0x03000001u,
UsesStaticAnimationWorkset: true),
registerDatStaticAnimationOwner: (_, _) => registerCount++,
registerDatStaticAnimationOwner: (_, _) =>
{
registerCount++;
return true;
},
unregisterDatStaticAnimationOwner: _ => unregisterCount++);
WorldEntity entity = MakeEntity(0x70000421u, Vector3.Zero);
@ -378,6 +387,7 @@ public sealed class EntityScriptActivatorTests
animationAttempts++;
if (animationAttempts == 1)
throw new InvalidOperationException("animation acquire fault");
return true;
});
Assert.Throws<InvalidOperationException>(() => activator.OnCreate(entity));
@ -424,7 +434,7 @@ public sealed class EntityScriptActivatorTests
if (effectReleaseAttempts == 1)
throw new InvalidOperationException("effect release fault");
},
registerDatStaticAnimationOwner: (_, _) => { },
registerDatStaticAnimationOwner: (_, _) => true,
unregisterDatStaticAnimationOwner: _ =>
{
animationReleaseAttempts++;
@ -465,6 +475,97 @@ public sealed class EntityScriptActivatorTests
Assert.Equal(first.Id, GameWindow.ParticleEntityKey(first));
}
[Fact]
public void DatStaticRebind_RetriesOnlyUnfinishedOwnersAndDoesNotReplayDefault()
{
var p = BuildPipeline(
(0xAAu, BuildScript((1.0, new CreateParticleHook { EmitterInfoId = 100 }))));
var setup = new DatReaderWriter.DBObjs.Setup();
ScriptActivationInfo info = new(
ScriptId: 0xAAu,
PartTransforms: Array.Empty<Matrix4x4>(),
EffectProfile: EntityEffectProfile.CreateDatStatic(setup),
Setup: setup,
DefaultAnimationId: 0x03000001u,
UsesStaticAnimationWorkset: true);
int effectUpdates = 0;
int animationRegistrations = 0;
int animationRebindAttempts = 0;
var activator = new EntityScriptActivator(
p.Runner,
p.Sink,
p.Poses,
_ => info,
registerDatStaticEffectOwner: (_, _, _) => effectUpdates++,
registerDatStaticAnimationOwner: (_, _) =>
{
animationRegistrations++;
return true;
},
rebindDatStaticAnimationOwner: (_, _) =>
{
animationRebindAttempts++;
if (animationRebindAttempts == 1)
throw new InvalidOperationException("animation rebind fault");
});
WorldEntity first = MakeDatStatic(0x80A9B400u, Vector3.One);
WorldEntity replacement = MakeDatStatic(
first.Id,
new Vector3(7f, 8f, 9f));
activator.OnCreate(first);
Assert.Throws<InvalidOperationException>(() =>
activator.OnRebindStatic(first, replacement));
activator.OnRebindStatic(first, replacement);
activator.OnCreate(replacement);
Assert.Equal(1, p.Runner.ActiveOwnerCount);
Assert.Equal(2, effectUpdates);
Assert.Equal(1, animationRegistrations);
Assert.Equal(2, animationRebindAttempts);
Assert.True(p.Poses.TryGetRootPose(first.Id, out Matrix4x4 root));
Assert.Equal(replacement.Position, root.Translation);
p.Runner.Tick(1.1);
Assert.Single(p.Recording.Calls);
Assert.Equal(replacement.Position, p.Recording.Calls[0].Pos);
}
[Fact]
public void DatStaticRebind_MissingDefaultAnimationNeverClaimsWorksetOwner()
{
var p = BuildPipeline();
var setup = new DatReaderWriter.DBObjs.Setup();
ScriptActivationInfo info = new(
ScriptId: 0,
PartTransforms: Array.Empty<Matrix4x4>(),
Setup: setup,
DefaultAnimationId: 0x03000001u,
UsesStaticAnimationWorkset: true);
int registerAttempts = 0;
int rebindAttempts = 0;
var activator = new EntityScriptActivator(
p.Runner,
p.Sink,
p.Poses,
_ => info,
registerDatStaticAnimationOwner: (_, _) =>
{
registerAttempts++;
return false;
},
rebindDatStaticAnimationOwner: (_, _) => rebindAttempts++);
WorldEntity first = MakeDatStatic(0x80A9B400u, Vector3.Zero);
WorldEntity replacement = MakeDatStatic(first.Id, Vector3.One);
activator.OnCreate(first);
activator.OnRebindStatic(first, replacement);
activator.OnCreate(replacement);
Assert.Equal(2, registerAttempts);
Assert.Equal(0, rebindAttempts);
Assert.True(p.Poses.TryGetRootPose(first.Id, out _));
}
[Fact]
public void LiveParticleFilterUsesCanonicalLocalEffectOwnerNotServerGuid()
{