acdream/src/AcDream.App/AcDream.App.csproj
Erik 713bec256b feat(net+app): WorldSession class + GameWindow live-mode wiring (Phase 4.7e/f)
The end-to-end pipeline. acdream can now connect to a live ACE server,
complete the full handshake + character-select + enter-world flow, and
stream CreateObject messages straight into the existing IGameState and
static mesh renderer. Gated behind ACDREAM_LIVE=1 so the default
offline run path is untouched.

Added:
  - AcDream.Core.Net.WorldSession: high-level session type that owns a
    NetClient, drives the 3-leg handshake, parses CharacterList, sends
    CharacterEnterWorldRequest + CharacterEnterWorld, and converts the
    post-login fragment stream into C# events. State machine:
    Disconnected → Handshaking → InCharacterSelect → EnteringWorld →
    InWorld (or Failed). Public API:
      * Connect(user, pass)  — blocks until CharacterList received
      * EnterWorld(user, characterIndex) — blocks until ServerReady
      * Tick() — non-blocking, call per game-loop frame
      * event EntitySpawned
      * event StateChanged
      * Characters property (populated after Connect)

  - NetClient.TryReceive: non-blocking variant that returns immediately
    with null if the kernel buffer is empty. Enables draining packets
    per frame from the main thread without stalling.

  - GameWindow live-mode hookup:
      * AcDream.Core.Net project reference
      * TryStartLiveSession() called after dat hydration, gated behind
        ACDREAM_LIVE=1 + ACDREAM_TEST_USER/ACDREAM_TEST_PASS env vars
      * Subscribes EntitySpawned to OnLiveEntitySpawned
      * Calls Connect() then EnterWorld(0) synchronously on startup
      * OnLiveEntitySpawned hydrates mesh refs from the Setup dat
        (same SetupMesh.Flatten + GfxObjMesh.Build + StaticMesh.EnsureUploaded
        path used by scenery), publishes a WorldEntitySnapshot via
        _worldGameState.Add + _worldEvents.FireEntitySpawned, and
        appends to _entities so the next frame picks it up
      * OnUpdate calls _liveSession?.Tick() each frame
      * OnClosing disposes the session
      * Position translation: server sends (LandblockId, local XYZ +
        quaternion); we map landblock to world origin relative to the
        rendered 3x3 center, add local XYZ, translate AC's (W,X,Y,Z)
        quaternion wire order to System.Numerics.Quaternion (X,Y,Z,W)

LIVE RUN OUTPUT (ACDREAM_LIVE=1 against localhost ACE, testaccount):

  [dats loaded, 1133 static entities hydrated]
  live: connecting to 127.0.0.1:9000 as testaccount
  live: entering world as 0x5000000A +Acdream
  live: in world — CreateObject stream active (so far: 0 received, 0 hydrated)
  live: spawned guid=0x5000000A setup=0x02000001 world=(104.9,15.1,94.0)
  live: spawned guid=0x7A9B4013 setup=0x0200007C world=(135.7,9.9,97.0)
  live: spawned guid=0x7A9B4014 setup=0x0200007C world=(132.5,9.9,97.0)
  live: spawned guid=0x7A9B4015 setup=0x020019FF world=(132.6,17.1,94.1)
  live: spawned guid=0x7A9B4016 setup=0x020019FF world=(136.3,5.2,94.1)
  live: spawned guid=0x7A9B4017 setup=0x020019FF world=(104.1,31.0,94.1)
  live: spawned guid=0x7A9B4037 setup=0x02000975 world=(109.7,33.0,95.0)
  live: spawned guid=0x7A9B4018 setup=0x020019FF world=(110.9,31.0,94.1)
  live: spawned guid=0x7A9B4019 setup=0x020019FF world=(107.5,31.5,94.1)
  live: spawned guid=0x7A9B403B setup=0x02000B8E world=(150.5,17.9,94.0)
  live: (suppressing further spawn logs)

First line: +Acdream himself. setup=0x02000001 is ACE's default humanoid
player mesh. world coords match Holtburg (landblock 0xA9B4 local
space). Subsequent spawns are weenies at various setup ids — likely
the foundry statue, street lamps, drums, etc. The 0x7A9B4xxx GUID
pattern is ACE's convention: scenery-type (0x7) + landblock (0xA9B4) +
per-object index.

All spawns flow through the SAME SetupMesh/GfxObjMesh/StaticMeshRenderer
pipeline used by scenery and interiors today. The plugin system's
EntitySpawned event fires on every new entity, so plugins can see
them without any networking awareness.

Tests: 160 passing offline (77 core + 83 net). The live handshake and
enter-world tests are gated and still pass when ACDREAM_LIVE=1.

User visual verification is the final acceptance for Phase 4. Run
with ACDREAM_DAT_DIR + ACDREAM_LIVE=1 + ACDREAM_TEST_USER=testaccount
+ ACDREAM_TEST_PASS=testpassword and look for +Acdream's model + the
foundry statue standing on top of the Holtburg foundry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 15:25:41 +02:00

50 lines
2.3 KiB
XML

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RootNamespace>AcDream.App</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Silk.NET.OpenGL" Version="2.23.0" />
<PackageReference Include="Silk.NET.Windowing" Version="2.23.0" />
<PackageReference Include="Silk.NET.Input" Version="2.23.0" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AcDream.Core\AcDream.Core.csproj" />
<ProjectReference Include="..\AcDream.Core.Net\AcDream.Core.Net.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Rendering\Shaders\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<!-- Build the smoke plugin first and copy it into plugins/AcDream.Plugins.Smoke/ -->
<ProjectReference Include="..\AcDream.Plugins.Smoke\AcDream.Plugins.Smoke.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
</ProjectReference>
</ItemGroup>
<Target Name="CopySmokePluginToPluginsDir" AfterTargets="Build">
<PropertyGroup>
<_SmokePluginSourceDir>..\AcDream.Plugins.Smoke\bin\$(Configuration)\net10.0</_SmokePluginSourceDir>
<_SmokePluginDestDir>$(OutputPath)plugins\AcDream.Plugins.Smoke</_SmokePluginDestDir>
</PropertyGroup>
<MakeDir Directories="$(_SmokePluginDestDir)" />
<Copy
SourceFiles="$(_SmokePluginSourceDir)\AcDream.Plugins.Smoke.dll"
DestinationFolder="$(_SmokePluginDestDir)"
SkipUnchangedFiles="true" />
<WriteLinesToFile
File="$(_SmokePluginDestDir)\plugin.json"
Overwrite="true"
Lines="{ &quot;id&quot;: &quot;acdream.smoke&quot;, &quot;displayName&quot;: &quot;Smoke Plugin&quot;, &quot;version&quot;: &quot;0.1.0&quot;, &quot;entryDll&quot;: &quot;AcDream.Plugins.Smoke.dll&quot;, &quot;apiVersion&quot;: 1 }" />
</Target>
</Project>