Hi, I'm *squid*

Moongate v2 Dev Log #2 — Three Bugs That Forced an Architecture Upgrade

moongate_logo

Last week I wrote about architecture lying to me. This week, architecture fought back.

I hit three issues that looked unrelated:

  1. AOT build crashing around persistence/serialization
  2. Doors opening by “moving” one tile
  3. Server collapsing under load because networking and game logic shared one hot thread

Different symptoms, same root mistake: assumptions I didn’t validate early enough.

1) MemoryPack + AOT: the crash I earned

Symptom

Under Native AOT, runtime exploded during persistence load/save paths with serializer-related failures. Normal JIT runs looked fine, so the issue stayed hidden until AOT execution.

Root cause

I relied on behavior that needed explicit AOT-safe generation. That’s on me: I didn’t follow the serialization docs strictly enough for AOT constraints.

Failed attempts

I first tried patching call sites and flow ordering. Wrong direction: the problem was generation compatibility, not call order.

Fix

I moved to generator-driven serialization paths and removed runtime assumptions that break in AOT. After that, persistence became stable under AOT and startup/login stopped crashing in this area.

Rule I keep now

If a system targets AOT, serialization strategy is decided on day one. No reflection-dependent assumptions. No “works in JIT, so it’s fine”.

2) The door bug: opens fine, teleports sideways

Symptom

Double-click a door, and instead of rotating/opening correctly, it appears shifted. Functional sometimes, visually wrong always. Screenshot 2026-03-06 at 12

Root cause

Facing/item-id mapping was inconsistent. Directional semantics and item-id progression didn’t agree.

Fix

I aligned behavior with the canonical door model, normalized facing conversion, and added targeted tests per facing variant.

Result

Doors now open/close without fake translation artifacts.

Rule I keep now

For tile/direction systems, I lock canonical mapping first and write regression tests before touching behavior.

3) Single-thread nightmare: one loop to bottleneck them all

Symptom

Under stress, slow ticks exploded. Inbound packet dispatch, game logic, and outbound sending were all contending in the same execution lane.

Root cause

Too much work serialized on one hot thread.

Fix

I split responsibilities into dedicated threads:

Game logic no longer waits behind network pressure, and hot-path contention drops significantly under load.

Rule I keep now

I profile queue/tick timing before guessing. If the loop owns everything, latency owns the loop.

What changed in my engineering rules

These were not cosmetic fixes. They were architecture corrections disguised as bugs.


#aot #architecture #dotnet #gamedev #moongate #postmortem #ultima-online