CN·IOS
TH·EN
DOCUMENTATION · THEORY + PLAYBOOK

Porting Patterns + Playbook

สรุปจาก docs/port/PORTING_PATTERNS.md + PORTING_PLAYBOOK.md — ทั้งคู่เป็นงานเขียนที่ดีที่สุดเรื่อง legacy game porting ที่เคยมีใน open source

Strategy selection — one decision per subsystem

The single most important architectural insight: a port is not one decision, it's one decision per subsystem, chosen by API surface size and determinism risk.

StrategyWhenInstance
TranslateHuge embedded API; bit-faithful behavior requiredDirectX 8 → DXVK → Vulkan (→ Metal)
ShimMany small scattered calls, simple semanticsCompatLib: 28 headers reimpl of Win32 POSIX
Swap behind interfaceCodebase already has device/manager abstractionsSDL3GameEngine beside Win32GameEngine
Stub now, impl laterNon-gameplay-criticalWWStub lib (screenshots, file dialogs)
RewriteLast resortOne fork rewrote D3D8→Metal directly — rejected upstream as unreviewable

Rule of thumb: translate for the renderer (fidelity), swap for audio/video (natural seams), shim for OS plumbing, stub for periphery.

Universal porting sequence (5 phases)

  1. Analysis & references — before code: map the architecture, find every reference implementation (even partial ones).
  2. Make it compile with stubs — compiler/ABI fixes, whole-function stubbing of Windows code.
  3. Graphics + windowing + input first among real subsystems — longest pole, only then can you test.
  4. Audio, then video/cutscenes — natural manager seams, lower risk.
  5. Polish & hardening — paths, lifecycle, packaging, CI, upstream sync.
  6. New-architecture pass (64-bit/ARM) — only after same-arch works.

Compat-shim pattern, done right

  • One shim layer, included first. Stubs scattered inline conflict with third-party headers.
  • Wrap whole functions, not lines. When 50%+ Windows API, #ifdef the entire function.
  • Never wrap gameplay logic. That's how determinism dies invisibly.
  • Keep Win32 signatures in the shim so game code doesn't change — diffable against upstream.
  • Expect ~dozens of headers, a few hundred lines total.

Portability bug taxonomy (check proactively)

ClassBugDetection
ABI: integer widthlong = 4 bytes Win32, 8 bytes LP64 → binary file parsing breaksFixed-width types + static_assert(sizeof(X)==N)
ABI: wchar_t2 bytes Windows, 4 bytes elsewhere → Unicode desyncHard-code disk format (UTF-16LE)
Exit semanticsExitProcess skips dtors; POSIX return runs them_exit() after cleanup
Case sensitivityAsset paths working on NTFS fail on case-sensitive FSCase-normalizing VFS layer
Path separators\\ literals in data-driven pathsNormalize in FS layer
MSVC builtins__max, __int64, __forceinlineOne compat-types header + grep audit
High-DPIWindow points ≠ drawable pixels → wrong input/renderPer-API scaling decision + corner-tap test
Dual package rootsIntel + ARM Homebrews → wrong arch pickedPin PKG_CONFIG_PATH

Determinism as the master gate

The deepest practice, directly transferable to any engine with replays/lockstep:

  • Replay determinism testing in CI — run recorded games headless, assert frame-exact outcomes.
  • Both platforms built in every session — even when focused on one.
  • Artifact verification over exit codesstrings/nm/otool into the binary, not whether commands succeeded.
  • Behavioral acceptance criteria per phase ("menu renders", "10-min stability"), not "it compiles".
If the target has replays, demos, or lockstep networking, make this gate #1 before touching anything.

Translation-layer integration (DXVK chapter)

When you adopt a translation layer (DXVK, MoltenVK, ANGLE, Wine pieces, Rosetta shims):

  • Pin it to an immutable commit of your own fork; integrate via ExternalProject/submodule.
  • Translation layers stack: D3D8 → Vulkan → Metal worked, but each boundary has capability mismatches.
  • Configuration is part of the port: ship the layer's config (dxvk.conf) with tuned options.
  • Learn the layer's conventions before patching — DXVK loads SDL via function-pointer table, not linking.

Process patterns (meta-engineering)

  • Session handoff documents — every session ends with blockers, achievements, "next session first task".
  • Lessons library — every painful debugging session becomes a permanently citable artifact.
  • Source annotation// ProjectName @bugfix author date description.
  • Minimal-diff discipline — one PR per category, platform never mixed with logic.
  • Agent-assisted dev with guardrails — agents must research reference repos before coding.
  • Reproducible build environments — Docker for Linux targets.
  • Progress metric honesty — track error categories resolved, not LOC.

De-risking ladder for arbitrary legacy codebases

  1. Ecosystem sweep — hunt for partial references, abandoned branches, similar-engine ports.
  2. Choose per-subsystem strategies + write architecture-decision log before coding.
  3. Compile-with-stubs milestone — stub Windows-only functions whole.
  4. Graphics translate + windowing swap → first pixels (half the total effort).
  5. Audio/video swaps behind existing manager seams.
  6. Determinism gate online as early as replays can run; both-platform builds every session.
  7. New-architecture pass (64-bit/ARM) as its own phase.
  8. Platform-specific delivery last (packaging, signing, lifecycle, input paradigm).
  9. Document as you go — documentation is the velocity.

Calibration: with full references available, platform delivery ≈ days. Without ≈ months. With neither (true rewrite) ≈ everyone abandons.

Source: Full documents at github.com/ammaarreshi/Generals-Mac-iOS-iPad/docs/port — PORTING_PATTERNS.md (13 KB) + PORTING_PLAYBOOK.md (30 KB) + RELEASE_CHECKLIST.md.