Skip to content

Architecture Overview

M T edited this page Oct 4, 2026 · 1 revision

Architecture Overview

Master Chef (internal name Halo Vision) runs the original Halo: Combat Evolved for PC 1.10 natively on Apple Vision Pro. It does not emulate a PC. The x86 program is statically translated ahead of time into C, then compiled to ARM64. Everything the program expected from Windows is re-implemented natively: Win32, Direct3D 9, DirectSound, DirectInput, DirectDraw and Ogg Vorbis. A visionOS presenter shows the result as an immersive panorama around the player.

This page shows how the pieces fit together. Each section links to the page that covers that subsystem in depth. Source links are pinned to commit 9f915af (v1.0.3, build 103).


1. The big picture

flowchart TB
    subgraph Offline["Offline (on your Mac)"]
        EXE["halo.exe (retail PC 1.10, SHA-256 c9acf0c4...)"]
        LISTS["decompilation/c9acf0c46954 entry lists"]
        GEN["tools/generate_engine_reuse.py (XWA decoder + strict lifter)"]
        IMP["tools/export_engine_imports.py"]
        CHUNKS["native/build/engine-reuse/whole-exe: chunk_000..031.c, engine_bundle.c, engine_imports.c"]
        EXE --> GEN
        LISTS --> GEN
        EXE --> IMP
        GEN --> CHUNKS
        IMP --> CHUNKS
    end

    subgraph App["HaloVision.app (one visionOS process)"]
        direction TB
        subgraph Swift["EngineVision (SwiftUI, CompositorServices, ARKit)"]
            UI["Windows: launcher, settings, diagnostics"]
            PRES["Immersive presenter: curved panels, stereo, HUD"]
        end
        BRIDGE["EngineVisionRuntime.m (bridge, engine worker thread)"]
        subgraph Guest["Translated engine"]
            CODE["Generated C: one function per x86 function"]
            CPU["EngineReuse: EngineCPU, EFLAGS, x87, memory macros"]
        end
        subgraph Host["EngineHost (native runtime)"]
            W32["Win32 shims: files, registry, threads, heap, time"]
            D3D["Direct3D 9 to Metal renderer"]
            PANO["Panorama: 10 layers from one simulation step"]
            DS["DirectSound mixer to AudioQueue"]
            DI["DirectInput from GameController"]
        end
        UI --> BRIDGE
        BRIDGE -->|host_run| CODE
        CODE --> CPU
        CODE -->|imports and COM calls| Host
        PANO -->|GPU texture leases| PRES
        DS -->|haptic events| PRES
        PRES -->|head pose| PANO
    end

    CHUNKS -->|compiled into| CODE
    DATA["Your game files: maps, Strings.dll, halo.exe image"] -->|loaded at runtime| Host
Loading

There are three layers, and they communicate only through narrow interfaces:

  1. Guest code. This is the translated program, plus the small EngineReuse CPU model it is written against. It knows nothing about Apple platforms. It only reads and writes guest memory and calls engine_dispatch.
  2. EngineHost. This is a C and Obj-C runtime that plays the role of Windows. It is the same code on the Mac (halo-host, the desktop probe) and inside the app.
  3. EngineVision. This is the visionOS app. It imports game files, starts the engine thread, presents frames in an immersive space, feeds head pose and settings back, and records diagnostics.

2. Offline: translating the executable

Step Tool Output
Verify the executable setup_halo.py, prepare_engine_vision_device.py (HALO_SHA256) Rejects anything except retail PC 1.10. Custom Edition, Anniversary, MCC and Xbox are not supported.
Decode XWA pe_analyze and disasm (Capstone), plus tools/engine_reuse/decode.py Basic blocks for every known entry, plus entries found by --discover
Lift tools/engine_reuse/lift.py, flags.py, fpu.py, extra.py One strict C function per x86 function. Unsupported instructions either fail generation or, with --trap-unsupported, trap at runtime. There are no silent no-ops.
Chunk generate_engine_reuse.py --chunks 32 32 chunk_NNN.c files plus engine_bundle.c (dispatch table and PE metadata)
Imports export_engine_imports.py engine_imports.c (static import table and TLS directory)

The code bytes are translated. The executable's data is still needed at runtime: host_run maps a 4 GiB flat guest address space and copies the PE sections of your halo.exe into it (host.c:513-524). That is why the app needs the exact executable even though it never executes x86 instructions.

Details: Static Translation Pipeline, XWA Decoder and Lifter, Function Address Lists.


3. Guest execution model

  • CPU state. EngineCPU holds the 8 general registers, EFLAGS, pc, the x87 register file and control word, and the per-thread TEB base (fs_base). Each guest thread has its own EngineCPU. See EngineReuse Runtime.
  • Memory. One flat 4 GiB mmap. A guest address is an offset from engine_flat_base. The first 64 KiB is PROT_NONE to catch null pointers. The host carves out a guest heap, a page space for VirtualAlloc, thread stacks, TEBs and the PEB. See Guest Memory and Heap.
  • Calls. About 80% of translated call sites are emitted as ENGINE_DIRECT: a plain C call to the target function. Indirect calls, and calls to addresses listed in engine_hooks.h, go through engine_dispatch:
flowchart LR
    CALL["call target"] --> M{"target >= 0xFE000000?"}
    M -- yes --> EXT["engine_dispatch_external: Win32/COM shim"]
    M -- no --> OV{"hooked? (64K-bit interest filter)"}
    OV -- yes --> OVR["engine_dispatch_override: native replacement or observer"]
    OV -- no --> BS["binary search over the translated entry table"]
    BS --> FN["sub_XXXXXXXX(cpu)"]
    OVR -->|"may call the original"| FN
Loading
  • Floating point. The x87 stack is modeled in binary64 with a rotating register file. Rounding modes map onto the ARM64 FPCR (HALO_ARM64_FENV_FAST). App builds use -frounding-math -ffp-contract=off so the compiler does not fuse or reorder operations. See x87 Floating Point.

4. EngineHost: being Windows

Imports. At startup, setup_imports writes a magic procedure handle (0xFF000000 | index) into every import address table slot. When guest code does call [IAT], the call reaches engine_dispatch_external, which calls the named native shim. COM interfaces (D3D9, DirectSound, DirectInput, DirectDraw) use the same mechanism. Their vtables are guest memory filled with magic handles for names such as "IDirect3DDevice9::DrawIndexedPrimitive". See EngineHost Overview and Win32 Compatibility Layer.

Windows API Native implementation Page
KERNEL32 (files, memory, threads, sync, time, TLS, APC) shims_kernel32.c, threading.c (pthreads) Win32 Compatibility Layer, Threading and Synchronization
USER32, GDI32, ADVAPI32 (registry), WINMM, OLE32, VERSION, WSOCK shims_misc.c Win32 Compatibility Layer
Direct3D 9 d3d9.c, d3d9_render.inc, metalrenderer.m, metalshader.c with MojoShader Direct3D9 Bridge, Metal Renderer, Shader Translation
DirectDraw 7 (display probing only) ddraw.c Direct3D9 Bridge
DirectSound 8 directsound.c, directsound_mixer.c, then AudioQueue Audio System
VORBISFILE.dll vorbis_shim.c with stb_vorbis Audio System
DirectInput 8 dinput8.c with gamecontroller.m (Apple GameController) Input and Controllers

Overrides. Some engine functions are replaced or wrapped natively, either for speed or for presentation:

  • BSP visibility and surface gathers (visible_surfaces.h, native_gather*.h);
  • hot math leaves (native_leaves.h);
  • vertex skinning in ProcessVertices;
  • the renderer entry 0050BEA0, which the panorama runs once per bearing;
  • campaign unlock;
  • script (HSC) tracing.

Each native replacement keeps the translated original reachable through an environment switch for A/B comparison. See Engine Overrides and Hooks and Geometry Fast Paths.


5. One frame, end to end

sequenceDiagram
    participant G as Guest main loop
    participant P as Panorama hooks (0050BEA0)
    participant D as D3D9 bridge
    participant M as Metal renderer
    participant B as Bridge (GPU slot pool)
    participant S as Immersive presenter
    participant A as DirectSound mixer

    G->>P: render frame (one simulation time)
    P->>P: plan bearings (budget tier, gaze prediction, LOD)
    loop each scheduled bearing or eye
        P->>G: run original renderer with rotated camera
        G->>D: SetRenderState / SetTexture / Draw*
        D->>M: pipeline + sampler + depth state, draw into the layer target
    end
    G->>D: Present
    D->>D: pointer servo, audio watchdog, FP environment check
    D->>B: publish layers (zero-copy GPU lease), carry undrawn layers
    B-->>S: latest READY slot (10 layer textures + poses + epochs)
    S->>S: draw curved panels per eye, feather the seams, HUD layer
    S->>P: head yaw/pitch/roll for the next frame
    A-->>S: haptic onset events (mailbox)
Loading
  1. The game's main loop runs on the engine worker thread. The app creates this pthread with a 1.5 GiB stack at USER_INTERACTIVE QoS (EngineVisionRuntime.m:733-751).

  2. When the game asks its renderer to draw the frame, the panorama hook runs the original renderer several times from the same simulation state. Each run uses the camera turned to a different bearing. The sphere is covered by 10 layers:

    • a ring of six bearings;
    • zenith and nadir caps;
    • the HUD and interface layer;
    • a right-eye centre view for stereo.

    A per-frame budget decides which layers are redrawn and which keep an older picture. See Panorama System and Panorama Budget and LOD.

  3. Each D3D9 draw is translated into Metal state. Pipelines, samplers and depth/stencil states are cached under bounded caches, and shaders are converted by MojoShader. Texture content is hashed with XXH3. The bundled .hvt and .hvs packs can replace textures (by TexMod CRC) and shaders. See Textures and Texture Packs.

  4. At Present, the host does its per-frame chores:

    • FP environment repair;
    • the gaze-pointer servo;
    • the audio watchdog;
    • honoring a stop request.

    It then publishes the frame. On the headset this is a zero-copy hand-off of layer textures into a 3-slot pool owned by the bridge. Optional Frame Pacing can delay the commit to an even 90 Hz rung.

  5. The immersive presenter (CompositorServices, Metal and an ARKit world-tracking provider; no RealityKit) leases the newest complete slot. It draws each layer on curved panels with feathered joins, separately per eye, and draws the HUD on its own layer. It writes the head pose back for the next engine frame. See Immersive Presenter and Layer Alignment.

  6. Audio runs independently. Sound data is copied out of guest memory at Unlock, mixed as float stereo with head-relative panning, and played through an AudioQueue at 48 kHz. Low-frequency onsets in the mix become controller Haptics.


6. Threads

Thread Created by Runs
Main (UI) thread visionOS SwiftUI windows, settings, asset import, diagnostics timer (200 ms)
Compositor render thread CompositorServices Immersive presenter frame loop and haptics pump
Engine worker enginevision_start host_run, then Halo's main loop, D3D9, panorama, Present
Guest threads CreateThread shim Halo's own cache-reader and save-writer threads. Each is a real pthread with its own TEB and guest stack.
Metal pipeline workers metalrenderer.m Asynchronous pipeline compilation (HALO_PIPELINE_WORKERS, default 3, max 6)
AudioQueue callback AudioToolbox Pulls mixed PCM. It never touches guest memory.
Audio session queue audio_session.inc Activation, interruption, and media-reset recovery
Report writer queue EngineDiagnosticReportWriter report.json, timeline and history writes off the main thread

See Threading and Synchronization.


7. Settings and switches

Three mechanisms configure the runtime:

  1. Compiled launch defaults. On visionOS, engine_worker sets a fixed list of HALO_* variables with setenv(..., 0), so an explicit value always wins. The list includes the panorama, dense projection, world-space first-person view, native gathers, the draw fast path, pad-to-key, campaign unlock and telemetry. mods/runtime-settings.json records this list.
  2. Live settings (halo_settings.c). These are lock-free atomics, seeded from the environment and changed from the SwiftUI settings window while the game runs. They cover stereo separation, panorama vertical FOV, haptics strength, backdrop brightness, target FPS, own-weapon gain, gaze pointer, layer alignment and frame pacing. Only the render resolution persists across launches.
  3. Diagnostic switches. More than 150 default-off HALO_* variables for traces, captures and A/B comparisons.

See Runtime Settings and Environment Variables.


8. Data on the device

Data Location Notes
Game payload Bundled GamePayload (staged by setup), installed to Application Support/HaloVision/PackagedGames/<payloadID>, or a folder imported to Application Support/HaloVision/Game Checked against a manifest; halo.exe checked by SHA-256
Installation values (registry) halo-vision-registry.txt in the game root Private; rewritten on RegSetValueExA
Saves and profiles <game root>/Users/Player/Documents/My Games/Halo Win32 paths are mapped case-insensitively by host_path.inc
Visual packs TextureMods.hvt, ShaderMods.hvs in the bundle Paths are passed to the host through HALO_TEXTURE_PACK and HALO_SHADER_PACK
Diagnostics App Documents/Diagnostics/ report.json, timeline, bounded history, deep-*.jsonl, live.json

See visionOS App and Diagnostics and Telemetry.


9. Build and distribution

flowchart LR
    ISO["Retail ISO or existing install"] --> SETUP["setup.sh / setup_halo.py"]
    SETUP --> GAME["game/ + .setup/GamePayload"]
    SETUP --> PACKS[".setup/VisualMods (checksum-pinned)"]
    SETUP --> GENR["generate + export imports"]
    GENR --> XCG["XcodeGen: project.yml"]
    GAME --> XCG
    PACKS --> XCG
    XCG --> XCODE["Xcode: your Team, your bundle ID"]
    XCODE --> AVP["Apple Vision Pro"]
Loading

The public repository contains only source. Generated engine code, game files, packs, registrations and signing material are produced locally or downloaded as release assets. They are never committed. See Setup Wizard, Build System, Device Preparation and Signing and Release Process and Hygiene.


10. Design principles visible in the code

  • The original engine stays in charge. Halo's own limiter, game time, scripts and renderer run unmodified. The port only re-runs the renderer for extra bearings and observes the engine at function boundaries.
  • Exactness first, then speed. Native replacements reproduce the translated routine's float operation order and guest stack stores. They are checked differentially against generated code when that code is available.
  • Everything optional can be switched off. Each optimization or presentation feature has an environment switch that restores the original path. This keeps headset A/B runs possible without a rebuild.
  • Bounded resources. Every cache, trace, capture and history file has a cap.
  • Honest evidence. Docs and comments separate source checks, desktop checks, builds, installs and gameplay measurements. docs/KNOWN_ISSUES.md lists what has not been established, including stable combat FPS, seamless stitching and full-campaign qualification.

Related pages

Clone this wiki locally