acdream/tests/AcDream.Runtime.Tests/NoWindowGameRuntimeHostTests.cs
Erik 34d8a3c0e7 fix(chat): CH1 review fixes — sbb-idiom channel catch-all, command-output typing
Applies the Opus review findings on CH1 (172c6f9a), the exact retail chat
color table. Two blockers plus should-fixes/nits, one commit:

BLOCKER 1 — LegacyChannelChatType.Resolve's channel-bit table was wrong.
Binary Ninja renders retail's `neg esi; sbb esi, esi` idiom (a branchless
select between Channel 0x08 and Channel_Send 0x09) as the trivial pseudo-C
`esi - esi` (always 0), hiding the real values. Corrected by decoding the
raw bytes at the PDB-paired binary: HEAR sbb site VA 0x00570F0A (mask -6 ->
0x08), SEND sbb site VA 0x00570D4F (mask -5 -> 0x09). The generic
admin/audit/sentinel catch-all is Channel/Channel_Send, NOT Abuse (0x0E) —
Abuse is retail's ONLY 0x0E producer (bit 0x0001). The unnamed
FellowBroadcast bit (0x4000000) is hear=Channel(0x08)/send=Fellowship(0x13),
not a flat 0x13. ACE's PDB-sourced Channel enum corroborates. Introduces
`RetailLogTextType`, the 34-value named enum for the wire LogTextType space
(values only, no color — Core stays presentation-free).

BLOCKER 2 — three ChatLog.OnSystemMessage sinks (ChatVM.ShowSystemMessage,
LiveSessionRuntimeFactory's ShowSystemMessage delegate,
HeadlessGameplayOperations.DisplayMessage) were typing ALL
ClientCommandController output 0x1A (bright red), including informational
command output (@version, /loc, friends list, usage lines). Retail types
the great majority of that output 0x00 Default (green) and reserves 0x1A
for genuine refusals/errors. Reverted to 0x00 with a comment noting the
refusal-vs-info split lands with CH2's SpewBox producer rewiring. The five
App composition sites that pass 0x1A for actual refusal text
(InteractionRetainedUiComposition, SessionPlayerComposition) were already
correct and are untouched (aside from converting the literal to the new
enum).

Also: AP-176 divergence-register row for OnWeenieError/OnCombatLine's
single-type approximation of retail's per-code/per-message dispatch; a
carry-forward test for the out-of-range LogTextType color fallback in
ChatWindowController; decomp-confirmed anchors replacing ACE-inferred
citations in CombatChatTranslator and ChatLog.OnPlayerKilled; required
(non-optional) logTextType parameters on OnLocalSpeech/OnTellReceived/
OnCombatLine/OnSelfSent since no production caller relied on a default;
LegacyChannelChatType.Resolve's parameter renamed channelBit -> channelId
with a doc note on multi-bit ids; corrections to the color-table research
doc's §3.3 wire tables; and issue #359 for the pre-existing (not
CH1-introduced) 0x019E PlayerKilled participant-suppression gap retail has
and acdream lacks.

dotnet build clean; full Release suite 11,835 passed / 4 skipped / 0 failed
(11,839 total), up from the CH1 baseline of 11,833/4/0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:03:13 +02:00

653 lines
25 KiB
C#

using System.Numerics;
using AcDream.Core.Items;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Tests.Support;
using AcDream.Runtime.World;
namespace AcDream.Runtime.Tests;
public sealed class NoWindowGameRuntimeHostTests
{
[Fact]
public void RootHostRunsPopulatedGenerationWithoutPresentationAssembly()
{
var host = new NoWindowGameRuntimeHost();
RuntimeSessionStartResult started = host.Start();
Assert.Equal(RuntimeSessionStartStatus.Connected, started.Status);
Assert.Equal(0x50000001u, host.Runtime.PlayerIdentity.ServerGuid);
Assert.True(host.Deliver(runtime =>
{
runtime.EntityObjects.RegisterEntity(
Spawn(0x70000001u, 1));
runtime.EntityObjects.Objects.AddOrUpdate(new ClientObject
{
ObjectId = 0x80000001u,
Name = "fixture",
ContainerId = runtime.PlayerIdentity.ServerGuid,
});
runtime.CommunicationOwner.Chat.OnSystemMessage(
"fixture",
1u);
}));
host.Advance(1d / 30d);
RuntimeStateCheckpoint populated =
host.Runtime.CaptureCheckpoint();
Assert.Equal(1, populated.EntityCount);
Assert.Equal(1, populated.InventoryObjectCount);
Assert.Equal(1, populated.ChatCount);
RuntimeTeardownAcknowledgement stopped = host.Stop();
Assert.True(stopped.IsComplete);
Assert.Equal(0u, host.Runtime.PlayerIdentity.ServerGuid);
Assert.Equal(0, host.Runtime.Entities.Count);
Assert.Equal(0, host.Runtime.Inventory.ObjectCount);
Assert.False(host.Deliver(_ =>
throw new InvalidOperationException("old route leaked")));
Assert.False(host.Execute(_ =>
throw new InvalidOperationException("old command leaked")));
Assert.Equal(1, host.ProjectionRetirementCount);
Assert.Equal(2, host.ProjectionDrainCount);
Assert.Equal(2, host.ProjectionCompletionCount);
string[] loaded = AppDomain.CurrentDomain.GetAssemblies()
.Select(static assembly =>
assembly.GetName().Name ?? string.Empty)
.ToArray();
Assert.DoesNotContain(loaded, static name =>
name.StartsWith(
"AcDream.App",
StringComparison.OrdinalIgnoreCase)
|| name.StartsWith(
"AcDream.UI.",
StringComparison.OrdinalIgnoreCase)
|| name.StartsWith(
"Silk.NET",
StringComparison.OrdinalIgnoreCase)
|| name.StartsWith(
"OpenAL",
StringComparison.OrdinalIgnoreCase)
|| name.StartsWith(
"Arch",
StringComparison.OrdinalIgnoreCase)
|| name.StartsWith(
"ImGui",
StringComparison.OrdinalIgnoreCase));
host.Dispose();
Assert.True(host.Runtime.CaptureOwnership().IsConverged);
}
[Fact]
public void ReconnectReusesRootButNotPriorGuidIncarnationOrRoutes()
{
const uint guid = 0x70000001u;
var host = new NoWindowGameRuntimeHost();
RuntimeSessionStartResult first = host.Start();
RuntimeGenerationToken firstGeneration = first.Generation;
Assert.True(host.Deliver(runtime =>
runtime.EntityObjects.RegisterEntity(Spawn(guid, 1))));
Assert.True(host.Runtime.EntityObjects.Entities.TryGetActive(
guid,
out RuntimeEntityRecord firstRecord));
RuntimeSessionStartResult second = host.Reconnect();
RuntimeGenerationToken secondGeneration = second.Generation;
Assert.Equal(RuntimeSessionStartStatus.Connected, second.Status);
Assert.NotEqual(firstGeneration, secondGeneration);
Assert.False(host.Runtime.EntityObjects.Entities.TryGetActive(
guid,
out _));
Assert.True(host.Deliver(runtime =>
runtime.EntityObjects.RegisterEntity(Spawn(guid, 2))));
Assert.True(host.Runtime.EntityObjects.Entities.TryGetActive(
guid,
out RuntimeEntityRecord secondRecord));
Assert.NotSame(firstRecord, secondRecord);
Assert.Equal((ushort)2, secondRecord.Incarnation);
Assert.Equal(0, host.Runtime.EntityObjects.Entities
.PendingTeardownCount);
Assert.Contains(
$"events-:{firstGeneration.Value}",
host.LifecycleTrace);
Assert.Contains(
$"commands-:{firstGeneration.Value}",
host.LifecycleTrace);
host.Dispose();
Assert.True(host.Runtime.CaptureOwnership().IsConverged);
}
[Fact]
public void DeterministicLifecycleUsesOneRootAcrossGameplayPortalAndReconnect()
{
const uint localGuid = 0x50000001u;
const uint reusedGuid = 0x70000001u;
const uint usableGuid = 0x70000002u;
const uint containerGuid = 0x80000001u;
const uint itemGuid = 0x80000002u;
const string passwordMarker = "credential-must-not-appear";
var host = new NoWindowGameRuntimeHost(
password: passwordMarker);
RuntimeStateCheckpoint empty = host.CaptureCheckpoint();
Assert.Equal(RuntimeLifecycleState.Constructed, empty.Lifecycle);
Assert.Equal(0, empty.EntityCount);
Assert.Equal(0, empty.InventoryObjectCount);
Assert.Equal(0, empty.ChatCount);
RuntimeSessionStartResult first = host.Start();
RuntimeGenerationToken firstGeneration = first.Generation;
Assert.Equal(RuntimeSessionStartStatus.Connected, first.Status);
Assert.True(host.Deliver(firstGeneration, runtime =>
{
runtime.EntityObjects.RegisterEntity(Spawn(localGuid, 1));
runtime.EntityObjects.RegisterEntity(Spawn(reusedGuid, 1));
runtime.EntityObjects.RegisterEntity(Spawn(usableGuid, 1));
runtime.EntityObjects.Objects.AddOrUpdate(new ClientObject
{
ObjectId = containerGuid,
Name = "fixture pack",
ContainerId = localGuid,
});
runtime.EntityObjects.Objects.AddContainer(new Container
{
ObjectId = containerGuid,
Capacity = 24,
});
runtime.EntityObjects.Objects.AddOrUpdate(new ClientObject
{
ObjectId = itemGuid,
Name = "fixture item",
ContainerId = localGuid,
StackSize = 3,
StackSizeMax = 10,
});
Assert.True(runtime.EntityObjects.Objects.ApplyServerMove(
itemGuid,
containerGuid,
newWielderId: 0u,
newSlot: 0));
runtime.CommunicationOwner.Chat.OnSystemMessage(
"fixture system",
1u);
runtime.CommunicationOwner.Chat.OnLocalSpeech(
"Runtime",
"fixture speech",
localGuid,
isRanged: false,
logTextType: 0x02u);
runtime.CharacterOwner.LocalPlayer.OnVitalUpdate(
vitalId: 1u,
ranks: 100u,
start: 100u,
xp: 50u,
current: 95u);
runtime.EnvironmentOwner.Initialize(
new RuntimeWorldEnvironmentDefinition(
originOffsetTicks: 0d,
sourceTickSize: 1d,
lightTickSize: 1d,
dayGroups: null));
runtime.EnvironmentOwner.SynchronizeFromServer(1234d);
}));
Assert.True(host.Move());
Assert.True(host.Use(usableGuid));
Assert.True(host.Attack());
Assert.True(host.CastFixtureSpell());
Assert.True(host.AdvanceProjectile());
Assert.True(host.CompletePortal());
host.Advance(1d / 30d);
RuntimeStateCheckpoint populated = host.CaptureCheckpoint();
Assert.Equal(RuntimeLifecycleState.InWorld, populated.Lifecycle);
Assert.Equal(4, populated.EntityCount);
Assert.Equal(2, populated.InventoryObjectCount);
Assert.Equal(1, populated.InventoryContainerCount);
Assert.Equal(2, populated.ChatCount);
Assert.Equal(1, populated.Character.LearnedSpellCount);
Assert.True(populated.Movement.AutoRunActive);
Assert.True(populated.Portal.Completed);
Assert.True(populated.Portal.WorldViewportObserved);
Assert.Equal(0, populated.TransitOwnership.HostProjectionCount);
Assert.Equal(
[
"movement:run",
$"use:{usableGuid:X8}",
"attack:prepare",
"attack:Medium:0.5",
"cast:stop",
"cast:1",
"cast:busy",
"projectile:ack",
],
host.GameplayTrace);
RuntimeTraceEntry[] firstTrace = host.Trace.Entries
.Where(entry =>
entry.Stamp.Generation == firstGeneration)
.ToArray();
Assert.Contains(
firstTrace,
static entry => entry.Kind == RuntimeTraceKind.Lifecycle);
Assert.Contains(
firstTrace,
static entry => entry.Kind == RuntimeTraceKind.Inventory
&& entry.Code == (int)RuntimeInventoryChange.Moved);
Assert.Contains(
firstTrace,
static entry => entry.Kind == RuntimeTraceKind.Movement);
Assert.Contains(
firstTrace,
static entry => entry.Kind == RuntimeTraceKind.Command
&& entry.Code >> 16
== (int)RuntimeCommandDomain.Magic);
Assert.Equal(
5,
firstTrace.Count(static entry =>
entry.Kind == RuntimeTraceKind.Portal));
AssertStrictlyOrdered(firstTrace);
Assert.True(host.Runtime.EntityObjects.Entities.TryGetActive(
reusedGuid,
out RuntimeEntityRecord firstRecord));
RuntimeSessionStartResult second = host.Reconnect();
RuntimeGenerationToken secondGeneration = second.Generation;
Assert.Equal(RuntimeSessionStartStatus.Connected, second.Status);
Assert.NotEqual(firstGeneration, secondGeneration);
Assert.False(host.Deliver(
firstGeneration,
_ => throw new InvalidOperationException(
"retired inbound route ran")));
Assert.False(host.Execute(
firstGeneration,
_ => throw new InvalidOperationException(
"retired command route ran")));
RuntimeStateCheckpoint reset = host.Runtime.CaptureCheckpoint();
Assert.Equal(0, reset.EntityCount);
Assert.Equal(0, reset.InventoryObjectCount);
// Retail's end-character path resets active communication targets and
// identity but does not issue ClearChatBuffer; bounded chat history is
// therefore retained by this reusable root.
Assert.Equal(2, reset.ChatCount);
Assert.Equal(0, reset.Character.LearnedSpellCount);
Assert.Equal(0u, reset.Actions.SelectedObjectId);
Assert.Equal(RuntimePortalSnapshot.Idle, reset.Portal);
Assert.False(reset.Movement.AutoRunActive);
Assert.True(reset.EnvironmentOwnership.IsInitialized);
Assert.True(host.Deliver(secondGeneration, runtime =>
runtime.EntityObjects.RegisterEntity(
Spawn(reusedGuid, 2))));
Assert.True(host.Runtime.EntityObjects.Entities.TryGetActive(
reusedGuid,
out RuntimeEntityRecord secondRecord));
Assert.NotSame(firstRecord, secondRecord);
Assert.Equal((ushort)2, secondRecord.Incarnation);
RuntimeStateCheckpoint secondCheckpoint =
host.CaptureCheckpoint();
Assert.Equal(1, secondCheckpoint.EntityCount);
Assert.Equal(0, secondCheckpoint.InventoryObjectCount);
Assert.Equal(2, secondCheckpoint.ChatCount);
Assert.True(host.Stop().IsComplete);
AssertStrictlyOrdered(
host.Trace.Entries
.Where(entry =>
entry.Stamp.Generation == secondGeneration)
.ToArray());
Assert.DoesNotContain(
host.LifecycleTrace.Concat(host.GameplayTrace),
line => line.Contains(
passwordMarker,
StringComparison.Ordinal));
host.Dispose();
Assert.True(host.Runtime.CaptureOwnership().IsConverged);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void PendingPortalCancellationRetiresHostAcknowledgementsBeforeReconnect(
bool destinationReady)
{
var host = new NoWindowGameRuntimeHost();
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
RuntimeWorldTransitState transit = host.Runtime.TransitOwner;
const uint destinationCell = 0x8A020164u;
const ushort sequence = 7;
Assert.True(transit.TryQueueTeleportStart(sequence));
Assert.True(transit.ActivateQueuedTeleport());
Assert.True(transit.OfferTeleportDestination(
new RuntimeTeleportDestination(
host.Runtime.PlayerIdentity.ServerGuid,
InstanceSequence: 1,
PositionSequence: 1,
TeleportSequence: sequence,
ForcePositionSequence: 1,
Position: new Position(
destinationCell,
Vector3.Zero,
Quaternion.Identity)),
teleportTimestampAdvanced: true));
Assert.True(transit.TryBeginPortalReveal(
sequence,
destinationCell,
out long revealGeneration));
Assert.True(transit.TryRegisterHostProjection(
revealGeneration,
destinationCell,
out RuntimeWorldHostProjectionToken projection));
Assert.True(transit.AcknowledgeHostProjection(
new RuntimeWorldHostAcknowledgement(
projection,
RuntimeWorldHostAcknowledgementStage
.ProjectionRegistered)));
if (destinationReady)
{
Assert.True(transit.AcknowledgeDestinationReadiness(
new RuntimeDestinationReadiness(
revealGeneration,
destinationCell,
IsIndoor: true,
IsUnhydratable: false,
RequiredRenderRadius: 0,
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true)));
}
Assert.True(transit.Cancel(revealGeneration));
DrainPendingPortal(transit, projection);
Assert.Equal(0, transit.Ownership.HostProjectionCount);
RuntimeSessionStartResult reconnected = host.Reconnect();
Assert.Equal(
RuntimeSessionStartStatus.Connected,
reconnected.Status);
Assert.Equal(RuntimePortalSnapshot.Idle, transit.Snapshot);
host.Dispose();
Assert.True(host.Runtime.CaptureOwnership().IsConverged);
}
[Fact]
public void ConcurrentRootsKeepCredentialsClockStateAndFailuresIsolated()
{
const string firstPassword = "first-password-marker";
const string secondPassword = "second-password-marker";
const uint sharedGuid = 0x70000055u;
var first = new NoWindowGameRuntimeHost(
user: "first-user",
password: firstPassword,
characterId: 0x50000011u,
characterName: "First");
var second = new NoWindowGameRuntimeHost(
user: "second-user",
password: secondPassword,
characterId: 0x50000022u,
characterName: "Second");
Assert.Equal(
RuntimeSessionStartStatus.Connected,
first.Start().Status);
Assert.Equal(
RuntimeSessionStartStatus.Connected,
second.Start().Status);
Assert.True(first.Deliver(runtime =>
{
runtime.EntityObjects.RegisterEntity(Spawn(sharedGuid, 1));
runtime.EntityObjects.Objects.AddOrUpdate(new ClientObject
{
ObjectId = 0x80000011u,
Name = "first item",
ContainerId = runtime.PlayerIdentity.ServerGuid,
});
runtime.CommunicationOwner.Chat.OnSystemMessage(
"first chat",
1u);
}));
Assert.True(second.Deliver(runtime =>
{
runtime.EntityObjects.RegisterEntity(Spawn(sharedGuid, 1));
runtime.EntityObjects.Objects.AddOrUpdate(new ClientObject
{
ObjectId = 0x80000022u,
Name = "second item",
ContainerId = runtime.PlayerIdentity.ServerGuid,
});
runtime.CommunicationOwner.Chat.OnSystemMessage(
"second chat",
1u);
}));
Assert.True(first.CastFixtureSpell());
Assert.True(second.CastFixtureSpell());
Assert.True(first.AdvanceProjectile(0x70000011u));
Assert.True(second.AdvanceProjectile(0x70000022u));
Assert.True(first.CompletePortal(0x8A020164u, sequence: 1));
Assert.True(second.CompletePortal(0x11340021u, sequence: 2));
first.Advance(0.1d);
first.Advance(0.2d);
second.Advance(0.4d);
Assert.True(first.Runtime.EntityObjects.Entities.TryGetActive(
sharedGuid,
out RuntimeEntityRecord firstRecord));
Assert.True(second.Runtime.EntityObjects.Entities.TryGetActive(
sharedGuid,
out RuntimeEntityRecord secondRecord));
Assert.NotSame(firstRecord, secondRecord);
Assert.Null(first.Runtime.EntityObjects.Objects.Get(0x80000022u));
Assert.Null(second.Runtime.EntityObjects.Objects.Get(0x80000011u));
Assert.Equal(
"first chat",
Assert.Single(
first.Runtime.CommunicationOwner.Chat.Snapshot()).Text);
Assert.Equal(
"second chat",
Assert.Single(
second.Runtime.CommunicationOwner.Chat.Snapshot()).Text);
Assert.Equal(2UL, first.Runtime.Clock.FrameNumber);
Assert.Equal(1UL, second.Runtime.Clock.FrameNumber);
Assert.NotEqual(
first.Runtime.Clock.SimulationTimeSeconds,
second.Runtime.Clock.SimulationTimeSeconds);
Assert.Equal(
0x8A020164u,
first.Runtime.TransitOwner.Snapshot.DestinationCell);
Assert.Equal(
0x11340021u,
second.Runtime.TransitOwner.Snapshot.DestinationCell);
Assert.DoesNotContain(
first.LifecycleTrace.Concat(first.GameplayTrace),
line => line.Contains(
firstPassword,
StringComparison.Ordinal)
|| line.Contains(
secondPassword,
StringComparison.Ordinal));
Assert.DoesNotContain(
second.LifecycleTrace.Concat(second.GameplayTrace),
line => line.Contains(
firstPassword,
StringComparison.Ordinal)
|| line.Contains(
secondPassword,
StringComparison.Ordinal));
first.Dispose();
second.Dispose();
Assert.True(first.Runtime.CaptureOwnership().IsConverged);
Assert.True(second.Runtime.CaptureOwnership().IsConverged);
}
[Fact]
public void ThrowingRootObserverIsContainedAndDoesNotBlockTeardown()
{
var host = new NoWindowGameRuntimeHost();
using IDisposable failure =
host.Runtime.Subscribe(new ThrowingObserver());
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
Assert.True(host.Deliver(runtime =>
{
runtime.EntityObjects.RegisterEntity(
Spawn(0x70000077u, 1));
runtime.CommunicationOwner.Chat.OnSystemMessage(
"observer fixture",
1u);
}));
Assert.True(host.Move());
Assert.True(host.CompletePortal());
Assert.True(
host.Runtime.CaptureOwnership()
.Events.DispatchFailureCount >= 4);
Assert.True(host.Stop().IsComplete);
failure.Dispose();
host.Dispose();
Assert.True(host.Runtime.CaptureOwnership().IsConverged);
}
private static void AssertStrictlyOrdered(
IReadOnlyList<RuntimeTraceEntry> entries)
{
for (int index = 1; index < entries.Count; index++)
{
Assert.True(
entries[index].Stamp.Sequence
> entries[index - 1].Stamp.Sequence,
$"trace sequence {entries[index].Stamp.Sequence} "
+ $"did not follow {entries[index - 1].Stamp.Sequence}");
}
}
private static void DrainPendingPortal(
RuntimeWorldTransitState transit,
RuntimeWorldHostProjectionToken projection)
{
while (transit.TryGetHostProjection(
projection,
out RuntimeWorldHostProjectionSnapshot pending))
{
RuntimeWorldHostAcknowledgementStage stages =
pending.PendingAcknowledgements;
RuntimeWorldHostAcknowledgementStage stage =
(stages & RuntimeWorldHostAcknowledgementStage
.SimulationReleaseProjected) != 0
? RuntimeWorldHostAcknowledgementStage
.SimulationReleaseProjected
: (stages & RuntimeWorldHostAcknowledgementStage
.DestinationReservationReleased) != 0
? RuntimeWorldHostAcknowledgementStage
.DestinationReservationReleased
: RuntimeWorldHostAcknowledgementStage
.TerminalProjected;
Assert.NotEqual(
RuntimeWorldHostAcknowledgementStage.None,
stages);
Assert.True(transit.AcknowledgeHostProjection(
new RuntimeWorldHostAcknowledgement(
projection,
stage)));
}
}
private sealed class ThrowingObserver : IRuntimeEventObserver
{
public void OnLifecycle(in RuntimeLifecycleDelta delta) =>
throw new InvalidOperationException("observer");
public void OnCommand(in RuntimeCommandDelta delta) =>
throw new InvalidOperationException("observer");
public void OnEntity(in RuntimeEntityDelta delta) =>
throw new InvalidOperationException("observer");
public void OnInventory(in RuntimeInventoryDelta delta) =>
throw new InvalidOperationException("observer");
public void OnChat(in RuntimeChatDelta delta) =>
throw new InvalidOperationException("observer");
public void OnMovement(in RuntimeMovementDelta delta) =>
throw new InvalidOperationException("observer");
public void OnPortal(in RuntimePortalDelta delta) =>
throw new InvalidOperationException("observer");
public void OnCombat(in RuntimeCombatDelta delta) =>
throw new InvalidOperationException("observer");
}
private static WorldSession.EntitySpawn Spawn(
uint guid,
ushort incarnation)
{
var position = new CreateObject.ServerPosition(
0x01010001u,
10f,
10f,
5f,
1f,
0f,
0f,
0f);
var timestamps = new PhysicsTimestamps(
Position: 1,
Movement: 1,
State: 1,
Vector: 1,
Teleport: 0,
ServerControlledMove: 1,
ForcePosition: 0,
ObjDesc: 1,
Instance: incarnation);
var physics = new PhysicsSpawnData(
RawState: (uint)PhysicsStateFlags.ReportCollisions,
Position: position,
Movement: null,
AnimationFrame: null,
SetupTableId: 0x02000001u,
MotionTableId: null,
SoundTableId: null,
PhysicsScriptTableId: null,
Parent: null,
Children: null,
Scale: null,
Friction: null,
Elasticity: null,
Translucency: null,
Velocity: null,
Acceleration: null,
AngularVelocity: null,
DefaultScriptType: null,
DefaultScriptIntensity: null,
Timestamps: timestamps);
return new WorldSession.EntitySpawn(
guid,
position,
0x02000001u,
Array.Empty<CreateObject.AnimPartChange>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.SubPaletteSwap>(),
null,
null,
guid.ToString("X8"),
null,
null,
null,
PhysicsState: physics.RawState,
InstanceSequence: incarnation,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: physics);
}
}