Skip to content

Build System

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

Build System

Halo Vision has three build products, all produced from the same generated engine translation and the same host sources: the visionOS app HaloVision.app (built either through a generated Xcode project or by a direct SDK-compiler route), and the desktop diagnostic host halo-host for macOS (built by a Makefile). The build sits between engine generation (which writes 32 C translation units plus a dispatch bundle into native/build/engine-reuse/whole-exe/) and device preparation and signing. The command-line app build is always unsigned and source-only: game data is only added by the setup wizard or by Xcode when a private payload has been staged.

Source files

File Role
tools/build_engine_vision.py App build driver: generated-source inventory, visual-pack check, XcodeGen, xcodebuild, the direct xcrun clang/swiftc route, Info.plist synthesis, build receipt.
native/EngineVision/project.yml XcodeGen spec for the EngineVision target (sources, resources, flags, frameworks, signing, versions).
native/EngineVision/Info.plist Base Info.plist merged with Xcode's generated keys (controller keys, file sharing).
native/EngineHost/Makefile Desktop macOS host halo-host (Cocoa window via metalwin.m, main.c).
tools/generate_engine_reuse.py, tools/export_engine_imports.py Produce the generated inputs (owned by Static Translation Pipeline).
tools/visual_assets.py ensure_visual_assets() and bundle_visual_assets() used by the build. See Visual Mods Pipeline.
requirements-development.txt Pinned Python dependencies for generation and tooling.
docs/BUILDING.md User-facing build and signing guide.

Pipeline overview

flowchart LR
    subgraph Inputs
        EXE["game/halo.exe (SHA-256 c9acf0c4...)"]
        AL["decompilation/c9acf0c46954/*.txt"]
        TR["tools/engine_reuse + third_party/xwa"]
        HOST["native/EngineHost/*.c *.m"]
        REUSE["native/EngineReuse/*.h"]
        SW["native/EngineVision/Sources (24 Swift, 2 ObjC)"]
        MOJO["third_party/mojoshader (4 C files)"]
        VM[".setup/VisualMods (verified packs)"]
    end
    EXE --> GEN["generate_engine_reuse.py --chunks 32"]
    AL --> GEN
    TR --> GEN
    GEN --> OUT["native/build/engine-reuse/whole-exe: chunk_000..031.c, engine_bundle.c, engine_functions.h, generation.json"]
    EXE --> IMP["export_engine_imports.py"] --> OUT2["engine_imports.c"]
    OUT --> BEV["build_engine_vision.py"]
    OUT2 --> BEV
    HOST --> BEV
    REUSE --> BEV
    SW --> BEV
    MOJO --> BEV
    VM --> BEV
    BEV -->|"default"| XG["xcodegen generate"] --> XB["xcodebuild CODE_SIGNING_ALLOWED=NO"]
    XB -->|"visionOS platform runtime missing"| DIR
    BEV -->|"--direct"| DIR["direct xros clang + swiftc"]
    XB --> APP1[".build/DerivedData/.../HaloVision.app"]
    DIR --> APP2[".build/DirectXROS/HaloVision.app"]
    XB --> RCPT["build-receipt.json"]
    OUT --> MK["make -C native/EngineHost"] --> HH["native/EngineHost/halo-host"]
    OUT2 --> MK
    HOST --> MK
Loading

Prerequisites

  • Apple Silicon Mac, Xcode with the visionOS SDK. The app's deployment target is visionOS 26.0; the release build used SDK 26.5 (BUILDING.md). "Older toolchain combinations have not been qualified."
  • Python 3.12 virtual environment with requirements-development.txt: capstone==5.0.6, pefile==2024.8.26, numpy==2.4.2, Pillow==11.3.0, unicorn==2.1.4.
  • XcodeGen for the Xcode route (not needed for --direct).
  • The generated engine sources (see below). Without them, every build_engine_vision.py mode, including --generate-only, fails.
  • The verified visual packs in .setup/VisualMods (or a Complete bundle, or network access to download them). ensure_visual_assets() runs on every invocation.

Step 1: generate the engine sources

From the repository root, with the venv active and game/halo.exe (or HALO_EXE) pointing at the supported executable:

python tools/generate_engine_reuse.py \
  @decompilation/c9acf0c46954/function-addresses.txt \
  @decompilation/c9acf0c46954/extra-function-entries.txt \
  --label whole-exe --max-functions 10000 --trap-unsupported --discover --chunks 32
python tools/export_engine_imports.py

Output directory native/build/engine-reuse/whole-exe/:

File Content
sub_XXXXXXXX.c One lifted original function each.
chunk_000.c .. chunk_031.c 32 translation units; each #includes an equal slice of the sub_*.c files (generate_engine_reuse.py:208-213). Stale chunks from a larger chunk count are deleted so they cannot define functions twice.
engine_functions.h Prototypes for every sub_* plus the dispatch API; includes engine_hooks.h.
engine_bundle.c Sorted entry table, ENGINE_FN_COUNT, and engine_dispatch() (binary search, override and external-import hooks), plus engine_reuse_entry().
engine_imports.c Import thunk table from export_engine_imports.py.
generation.json Generation receipt (function counts, unresolved boundaries, trap annotations).

The 1.0.3 validation recorded 8,336 functions in 32 chunks (VALIDATION.md). The setup wizard runs these two commands automatically and skips them when a fingerprint of the inputs is unchanged (Setup Wizard, stage 9).

Step 2: build the app with build_engine_vision.py

Options

Parsed in main() (build_engine_vision.py:218-224):

Option Default Effect
--configuration {Debug,Release} Debug Selects -O0/-Onone (Debug) or -O2/-O (Release) on the direct route, and the Xcode configuration otherwise.
--clean off Xcode route: deletes .build/DerivedData before building. Direct route: deletes all of .build/DirectXROS (objects and product).
--generate-only off Run XcodeGen and stop. Prints Generated native/EngineVision/EngineVision.xcodeproj.
--direct off Skip XcodeGen and xcodebuild; compile and link with the SDK compilers via xcrun --sdk xros. Produces an unsigned device app.

Common preamble

Every invocation, before branching (build_engine_vision.py:226-235):

  1. Tool check. --direct needs only xcrun; all other modes (including --generate-only) need xcodegen, xcodebuild and xcrun on PATH. A missing tool raises required tool not found.
  2. Generated inventory. generated_inventory() (build_engine_vision.py:37-52) requires exactly 32 chunk_*.c files plus engine_bundle.c, engine_imports.c and engine_functions.h, else whole-engine source incomplete: chunks=N, missing=[...]. It returns chunkCount, functionCount (parsed from ENGINE_FN_COUNT = N in engine_bundle.c), generatedCBytes (sum of all *.c sizes), and the SHA-256 of engine_bundle.c and engine_imports.c.
  3. Visual packs. ensure_visual_assets() makes sure .setup/VisualMods exists and verifies (downloading the pinned archive if necessary). The Xcode project references these files as required resources, so this also applies to --generate-only.
  4. .build/ is created; --clean removes .build/DerivedData.
  5. Prints Whole engine: N functions in 32 chunks, X generated C bytes.

Any exception escaping main() is printed as build_engine_vision.py: <error> and the process exits 2 (build_engine_vision.py:299-304).

Route A: Xcode project (default)

  1. xcodegen generate --spec native/EngineVision/project.yml --project native/EngineVision (build_engine_vision.py:242-247). With --generate-only, stop here.
  2. xcodebuild -project EngineVision.xcodeproj -scheme EngineVision -configuration <cfg> -sdk xros -destination generic/platform=visionOS -derivedDataPath native/EngineVision/.build/DerivedData -jobs 2 CODE_SIGNING_ALLOWED=NO COMPILER_INDEX_STORE_ENABLE=NO build (build_engine_vision.py:252-257). Output is streamed through run_stream() into native/EngineVision/.build/build.log; only lines containing CompileC , SwiftCompile , Ld , BUILD SUCCEEDED, error: or warning: are echoed.
  3. Automatic fallback. If xcodebuild fails and the log contains both platform:visionOS and is not installed (the SDK is present but Xcode's generic-device platform runtime is not), the script reports "xcodebuild unavailable: Xcode SDK is installed, but its generic-device platform runtime is absent; using direct xros toolchain" and runs Route B (build_engine_vision.py:266-270).
  4. Build receipt. Written to native/EngineVision/build-receipt.json and printed (build_engine_vision.py:271-296). The exit status is the build result.

The Xcode route is unsigned on the command line (CODE_SIGNING_ALLOWED=NO), but the project itself has CODE_SIGNING_ALLOWED: YES and CODE_SIGN_STYLE: Automatic, so pressing Run in Xcode signs with the user's selected Team.

Build receipt fields

Field Meaning
builtAtUTC ISO timestamp
configuration Debug or Release
sdk xcrun --sdk xros --show-sdk-version
jobs always 2
optimization O2 generated C (Release) or O0 generated C
swiftOptimization O or Onone
exitCode final result
builder xcodebuild or direct-xros-toolchain
xcodebuildExitCode, xcodebuildFallbackReason the original xcodebuild status and fallback reason (or null)
elapsedSeconds wall time from the xcodebuild start
project, log, directLog repository-relative paths (directLog only when the fallback ran)
products every HaloVision.app under DerivedData/Build/Products, plus the direct product if built
chunkCount, functionCount, generatedCBytes, engineBundleSHA256, engineImportsSHA256 the generated inventory
executableSHA256 SHA-256 of HaloVision in the last listed product (only on success)

build-receipt.json is listed in .gitignore. The --direct route does not write a receipt; it prints a JSON object {exitCode, product, ...inventory} instead (build_engine_vision.py:237-240).

Route B: direct SDK build (--direct)

direct_xros_build() (build_engine_vision.py:69-215) "Build[s] an unsigned device bundle without Xcode's separately installed platform runtime." It is the route used by setup.sh --stage build and by the release builds.

What gets compiled

Group Files Language flags
EngineHost C (17) host.c, shims_kernel32.c, shims_misc.c, directsound.c, directsound_mixer.c, vorbis_shim.c, d3d9.c, texture_decode.c, metalshader.c, dinput8.c, ddraw.c, resources.c, overrides.c, threading.c, pointer.c, haptics.c, halo_settings.c common
MojoShader (4) third_party/mojoshader/mojoshader.c, mojoshader_common.c, profiles/mojoshader_profile_common.c, profiles/mojoshader_profile_metal.c common
Generated (34) chunk_000.c .. chunk_031.c, engine_bundle.c, engine_imports.c common
Objective-C (4) native/EngineVision/Sources/EngineVisionRuntime.m, EngineDiagnosticsBridge.m, native/EngineHost/gamecontroller.m, metalrenderer.m common plus -fobjc-arc -fblocks
Swift (24) every native/EngineVision/Sources/*.swift see below

That is 59 native translation units and 24 Swift sources, matching the counts recorded in VALIDATION.md. native/EngineReuse contributes headers only (engine_cpu.h, engine_hooks.h, engine_flags.h, engine_registers.h, engine_arm64_fenv_prototype.h) via -I; native/EngineReuse/engine_runtime.c is not compiled into the app by either route (it is exercised by native/Tests/EngineRuntimeContract.c, see Testing and Source Checks). The desktop-only main.c and metalwin.m are not part of the app.

Common C/Objective-C flags

From build_engine_vision.py:78-86:

Flag Purpose
-target arm64-apple-xros26.0 -isysroot <xros SDK> visionOS 26.0 device target
-ffile-prefix-map=<repo>=. Rewrites the absolute checkout path in debug info and __FILE__ to . so build machine paths do not leak into binaries
-DENGINE_FLAT_MEMORY=1 Flat guest memory model (see Guest Memory and Heap)
-DHALO_ARM64_FENV_FAST=1 Fast ARM64 floating-point environment helpers (see x87 Floating Point)
-frounding-math -ffp-contract=off Honour dynamic rounding modes and forbid FMA contraction so translated x87 arithmetic stays bit-exact
-DMOJOSHADER_NO_VERSION_INCLUDE=1 and -DSUPPORT_PROFILE_{D3D,BYTECODE,HLSL,GLSL120,GLSLES,GLSLES3,GLSL,ARB1,ARB1_NV,SPIRV,GLSPIRV}=0 Build MojoShader with only the Metal profile (see Shader Translation)
-I third_party/mojoshader -I native/EngineVision/Sources -I native/EngineHost -I native/EngineReuse -I <GEN> include paths
-O2 (Release) / -O0 (Debug) optimisation

Compilation runs in a two-worker ThreadPoolExecutor ("jobs=2"), printing native K/N every eight units. Objects go to .build/DirectXROS/objects/native_NNN_<stem>.o. Diagnostics are appended to .build/direct-xros-build.log. The first compile error aborts with return code 1.

Swift

One swiftc invocation compiles all Swift sources (build_engine_vision.py:132-157): -target arm64-apple-xros26.0 -sdk <sdk>, -O/-Onone, -parse-as-library -swift-version 5 -Xfrontend -strict-concurrency=minimal, -import-objc-header native/EngineVision/Sources/EngineVision-Bridging-Header.h, -Xcc -I for Sources and EngineHost, -module-name EngineVision -emit-module, an output file map at .build/DirectXROS/swift-output-map.json (objects swift_NN_<stem>.o), and -j 2.

Link

swiftc links all native and Swift objects into .build/DirectXROS/HaloVision.app/HaloVision with -rpath @executable_path/Frameworks, -lm, and the frameworks Foundation, SwiftUI, UIKit, Metal, MetalKit, QuartzCore, CompositorServices, ARKit, GameController, CoreHaptics, AudioToolbox, AVFAudio (build_engine_vision.py:159-174).

Bundle assembly

The direct route writes a binary Info.plist itself (build_engine_vision.py:176-207):

Key Value
CFBundleIdentifier org.example.halovision (public placeholder)
CFBundleExecutable / CFBundleName HaloVision
CFBundleDisplayName Halo Vision
CFBundleShortVersionString / CFBundleVersion 1.0.3 / 103
HaloBuildID the UTC build timestamp; reported as buildID by the app's diagnostics (EngineDiagnostics.swift:178), which fall back to unknown when the key is absent; see Diagnostics and Telemetry
MinimumOSVersion 26.0
CFBundleSupportedPlatforms / DTPlatformName / UIDeviceFamily ["XROS"] / xros / [7]
GCSupportsControllerUserInteraction, GCSupportedGameControllers (ExtendedGamepad), GCRequiresControllerUserInteraction (visionOS: true) game controller declarations
NSHandsTrackingUsageDescription, NSWorldSensingUsageDescription permission strings
UIApplicationPreferredDefaultSceneSessionRole and UIApplicationSceneManifest launch directly into a CPSceneSessionRoleImmersiveSpaceApplication scene with UIImmersionStyleFull

It then writes PkgInfo (APPL????), copies Resources/ThirdPartyNotices.txt and Resources/PrivacyInfo.xcprivacy, and calls bundle_visual_assets(product, ensure_visual_assets()), which clone-copies TextureMods.hvt, ShaderMods.hvs, CEnshineSources.zip and VisualModsManifest.json into the bundle root and re-verifies them. If an existing product already holds different pack bytes, it raises "Existing app has different visual assets; rebuild with --clean".

Without --clean, the product directory is reused across builds. This is what lets setup.sh --stage build add GamePayload/ to the app after the build and detect a mismatched payload on the next run.

The XcodeGen spec (project.yml)

Section Setting Notes
options bundleIdPrefix: org.example.halovision, deploymentTarget.visionOS: "26.0"
settings.base SWIFT_VERSION 5.0, SWIFT_STRICT_CONCURRENCY minimal, CODE_SIGNING_ALLOWED YES, CODE_SIGN_STYLE Automatic, ENABLE_USER_SCRIPT_SANDBOXING YES, CLANG_ENABLE_MODULES YES, GCC_OPTIMIZATION_LEVEL 0, COMPILER_INDEX_STORE_ENABLE NO Release overrides GCC_OPTIMIZATION_LEVEL 2.
targets.EngineVision.sources Sources/; resources ThirdPartyNotices.txt, PrivacyInfo.xcprivacy; ../../.setup/VisualMods/{TextureMods.hvt,ShaderMods.hvs,CEnshineSources.zip,VisualModsManifest.json}; optional ../../.setup/GamePayload (folder) and ../../.setup/GamePayloadManifest.json; the same 17 EngineHost C files plus gamecontroller.m and metalrenderer.m; the four MojoShader files; and ../build/engine-reuse/whole-exe filtered to chunk_*.c, engine_bundle.c, engine_imports.c The payload entries carry the comment "Created locally by setup.sh; never included in public source releases."
dependencies the same 12 SDK frameworks as the direct link
settings.base (target) PRODUCT_BUNDLE_IDENTIFIER org.example.halovision, PRODUCT_NAME HaloVision, PRODUCT_MODULE_NAME EngineVision, GENERATE_INFOPLIST_FILE YES with INFOPLIST_FILE Info.plist, display name, controller and usage-description keys, scene manifest generation, TARGETED_DEVICE_FAMILY 7, CURRENT_PROJECT_VERSION 103, MARKETING_VERSION 1.0.3, bridging header, HEADER_SEARCH_PATHS (Sources, mojoshader, EngineHost, EngineReuse, generated dir), OTHER_CFLAGS (same defines as the direct route), OTHER_LDFLAGS -lm
scheme gatherCoverageData: false

See project.yml:1-108.

Differences between the two app routes

These are visible in the source and worth knowing when comparing builds:

Aspect Xcode route Direct route
Signing Automatic signing when run from Xcode; unsigned from the script Always unsigned
Info.plist Generated keys merged with native/EngineVision/Info.plist, which also sets UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace Written by the script; does not set those two keys
Hands-tracking string "A pinch chooses the menu item you are looking at; hands are never recorded." "A pinch selects the menu item you are looking at."
HaloBuildID not set by project.yml (diagnostics report buildID as unknown) UTC build timestamp
Game payload Included automatically if .setup/GamePayload exists (optional resource) Never added by the build; setup.sh --stage build copies it in afterwards
Receipt build-receipt.json JSON on stdout only (unless reached through the fallback)

Output paths

Path Content
native/EngineVision/EngineVision.xcodeproj Generated project (ignored by Git).
native/EngineVision/.build/DerivedData/Build/Products/*/HaloVision.app Xcode-route product.
native/EngineVision/.build/build.log Full xcodebuild log.
native/EngineVision/.build/DirectXROS/HaloVision.app Direct-route product (the path printed by setup and referenced by prepare_engine_vision_device.py as DEFAULT_APP).
native/EngineVision/.build/DirectXROS/objects/ Direct-route objects and EngineVision.swiftmodule.
native/EngineVision/.build/direct-xros-build.log Direct-route diagnostics.
native/EngineVision/build-receipt.json Xcode-route receipt.
native/EngineHost/halo-host Desktop host binary.
native/build/engine-reuse/whole-exe/host-obj/ Desktop host objects.

All are ignored by .gitignore.

Desktop diagnostic host (native/EngineHost/Makefile)

make -C native/EngineHost -j2

The Makefile builds halo-host, a macOS arm64 runner for the same translated engine with a Cocoa/Metal window. It is used for desktop probes (for example tools/probe_engine_menu_input.py) and is described further on EngineHost Overview.

Variable Value
VISION repository root (../..)
GEN $(VISION)/native/build/engine-reuse/whole-exe
OBJ $(GEN)/host-obj
CFLAGS -O2 -w -std=c11 -DENGINE_FLAT_MEMORY=1 -DHALO_ARM64_FENV_FAST=1 -I. -I$(VISION)/native/EngineReuse -I$(GEN)
HOST_SRCS the 17 app host C files plus main.c
HOST_OBJC metalwin.m, metalrenderer.m, gamecontroller.m (built with -O2 -ObjC -fobjc-arc)
MOJO_OBJS the four MojoShader objects, built with the Metal-only SUPPORT_PROFILE_* defines
Link -lm -framework Cocoa -framework Metal -framework QuartzCore -framework GameController -framework CoreHaptics -framework AudioToolbox

Notable rules:

  • Per-chunk dependency tracking. Line 12 uses sed to extract every "sub_*.c" include from each generated chunk and makes the chunk object depend on those files, so editing or regenerating a single lifted function rebuilds only its chunk, even for objects cached before dependency files existed (Makefile:10-12).
  • Header dependencies. Explicit prerequisites tie overrides.o, d3d9.o, host.o, pointer.o and others to their .inc/.h files (texture/shader mod runtime, panorama, native gather, model capture, process-vertices helpers).
  • Generation rules. $(GEN)/engine_bundle.c and $(GEN)/engine_imports.c have recipes that run the generator and import exporter with the system python3, so make can regenerate from scratch when the dependencies and game/halo.exe (or HALO_EXE) are available.
  • Floating-point flags. The Makefile's CFLAGS do not add -frounding-math -ffp-contract=off, unlike both app routes. run_source_checks.py correspondingly runs its native-leaf comparisons twice: "with the release flags and with the headset's (-frounding-math, the FPSR helper)" (run_source_checks.py:129-137).
  • make clean removes host-obj/ and halo-host.

The repository's tests are not built by this Makefile; they are compiled ad hoc by tools/run_source_checks.py.

Versioning in the build

The public version appears in several places, all of which must be updated together for a release (see Release Process and Hygiene):

Location Value at this commit
VERSION 1.0.3
project.yml:101-102 CURRENT_PROJECT_VERSION 103, MARKETING_VERSION 1.0.3
build_engine_vision.py:184,186 CFBundleShortVersionString 1.0.3, CFBundleVersion 103
mods/visual-assets.json, mods/runtime-settings.json, SOURCE_MANIFEST.json "version": "1.0.3"

The public build-number series (100, 101, 102, 103) is separate from the owner's private development build numbers (for example Build91); RELEASING.md asks that both be identified when comparing results.

Failure modes

Message Cause Fix
required tool not found: xcodegen (or xcodebuild, xcrun) toolchain missing Install XcodeGen/Xcode, or use --direct.
whole-engine source incomplete: chunks=N, missing=[...] generation not run, failed, or used a different chunk count Re-run generation with --chunks 32.
Visual asset is missing or changed: ... / Visual archive checksum mismatch packs absent or modified See Visual Mods Pipeline.
Existing app has different visual assets; rebuild with --clean stale direct product --direct --clean.
direct xros native compile error: <file> ... a C/ObjC compile error See .build/direct-xros-build.log.
xcodebuild unavailable: ... using direct xros toolchain visionOS platform runtime not installed Informational; the direct route is used automatically.

Testing

There is no dedicated test of build_engine_vision.py itself. Relevant coverage:

  • test_public_project_includes_optional_payload_and_signing in tools/test_setup_halo.py asserts that project.yml keeps CODE_SIGNING_ALLOWED: YES, the ../../.setup/GamePayloadManifest.json resource and optional: true.
  • tools/check_core_telemetry_xros.py (run by run_source_checks.py on macOS) compiles and links a probe for arm64-apple-xros26.0 with -Werror -Wl,-fatal_warnings, checks that the telemetry APIs are strong libSystem imports, and that LC_BUILD_VERSION says platform 11 (xrOS) minos 26.0. It prints SKIP when no xros SDK is installed.
  • Release validation (recorded in VALIDATION.md) records an unsigned Release build of 59 native units and 24 Swift sources with SDK 26.5, and XcodeGen projects containing every visual pack in Copy Bundle Resources.

Related pages

Clone this wiki locally