-
Notifications
You must be signed in to change notification settings - Fork 5
Runtime Settings
The port has a small set of its own presentation settings (stereo depth, panorama field of view, haptics strength, surround brightness, spatial menu, frame-rate target, own-weapon gain, head pointer, layer alignment, frame pacing) that the original game knows nothing about. They live in one lock-free C structure, halo_settings.c, shared between the engine thread that renders and the SwiftUI settings panel that adjusts them. Each value is seeded from an environment variable when first read, so scripted desktop runs behave as before, and each can then be changed live from the headset without restarting the engine. A few other startup choices (render resolution, the device's default environment) are made by the app before the engine starts. mods/runtime-settings.json records the shipped defaults for the release; it is documentation, not a loader.
| File | Role |
|---|---|
native/EngineHost/halo_settings.h |
HaloSettings struct, accessor and head-pose API. |
native/EngineHost/halo_settings.c |
Atomic storage, environment seeding (ensure), clamping, halo_settings_get/set, radial-fog switch. |
native/EngineVision/Sources/EngineSettingsView.swift |
EngineSettingsModel (reads with halo_settings_get, writes every change with halo_settings_set) and the SwiftUI panel; the HaloRenderResolution picker. |
native/EngineVision/Sources/EngineVisionRuntime.m |
Device environment defaults (engine_worker), render resolution and command line (enginevision_start), HALO_CORE_TELEMETRY. |
native/EngineVision/Sources/EngineImmersive.swift |
Publishes head pose (halo_settings_set_head, halo_settings_set_head_roll); reads layer alignment, head pointer, backdrop brightness. |
mods/runtime-settings.json |
Release record of defaults. |
native/EngineHost/tests/test_halo_settings_layer_align.c |
Layer-alignment default and live toggle. |
flowchart LR
env["process environment (HALO_*)"] -->|"first read: ensure()"| store["halo_settings.c atomics"]
ui["EngineSettingsView (SwiftUI)"] -->|"init: halo_settings_get"| model["EngineSettingsModel"]
store --> model
model -->|"didSet: halo_settings_set (clamped)"| store
store -->|"clamped accessors, read at frame boundaries"| eng["engine thread: panorama_hooks.inc, frame_pacing_hooks.inc, pointer.c"]
store --> mix["audio thread: directsound_mixer.c, haptics.c"]
store --> pres["presenter: EngineImmersive.swift"]
pres -->|"halo_settings_set_head / _roll every frame"| store
defaults["UserDefaults HaloRenderResolution (@AppStorage)"] -->|"enginevision_start"| cmd["HALO_CMDLINE_EXTRA -vidmode"]
worker["engine_worker device defaults (setenv, no overwrite)"] --> env
Every field is an independent _Atomic 32-bit word (floats are bit-cast with memcpy) read and written with relaxed ordering (halo_settings.c:9-15). There is no lock on the render path; "a torn pair is harmless because every value is read through its own clamped accessor". Initialisation is published with an acquire/release flag: the first call to any accessor, get or set runs ensure(), which seeds every field from the environment once per process (halo_settings.c:43-83). The header's contract is that values are read once per frame at well-defined points, never mid-pass, "so a change can not tear a frame between the two eyes" (halo_settings.h:3-8).
halo_settings_set (halo_settings.c:100-125) validates each field and replaces an out-of-range or NaN value with a fallback; the accessors repeat the same range check on read (halo_settings.c:127-136). Note that several set/accessor fallbacks differ from the environment defaults (the table lists both).
Only the render resolution persists across launches (@AppStorage("HaloRenderResolution")). The live settings in HaloSettings are held in memory: EngineSettingsModel initialises from halo_settings_get and pushes every change with halo_settings_set, but nothing in EngineSettingsView.swift or halo_settings.c writes them to UserDefaults or a file. At the next launch they start again from the environment, which on the headset means the compiled defaults below.
Setting (HaloSettings field) |
Environment seed | Env default and valid range |
set/accessor range and fallback |
Settings panel control | Consumers |
|---|---|---|---|---|---|
stereo_separation: half the eye separation in Halo world units (one unit is ten feet; 0 = mono, which halves the world passes) |
HALO_STEREO_IPD (mm, 40–90, default 63) → mm / 1000 / 2 / 3.048; then HALO_STEREO_SEPARATION (world units, 0–1) overrides; HALO_STEREO starting with 0 forces 0 |
63 mm → about 0.01033 units | 0–1, else 0 | "Render both eyes" toggle and "Eye separation" 45–80 mm (step 1); off sends 0 |
panorama_hooks.inc stereo enable and eye offset (panorama_hooks.inc:33-34) |
panorama_vfov: vertical field of view of each panorama view, radians |
HALO_PANORAMA_VFOV (degrees, 60–175) |
105° | 1.0472–3.0543 rad (60–175°), else 2.6179939 rad (150°) | "Vertical coverage" 90–175° (step 5) |
panorama_view_vfov() (panorama_hooks.inc:50) |
haptics_strength: scale for synthesized controller haptics (0 disables) |
HALO_HAPTICS starting with 0 → 0 |
1 | 0–4, else 1 | "Strength" 0–2 (step 0.05) |
haptics.c (haptics.c:38, haptics.c:103) |
backdrop_brightness: surround that continues the picture past its edges |
HALO_BACKDROP (0–2) |
1 | 0–2, else 1 | Surround "Brightness" 0–2 (step 0.05) |
EngineImmersive.swift (EngineImmersive.swift:981) |
spatial_shell: render the front-end menu through the panorama path so its 3D backdrop surrounds the viewer |
HALO_SPATIAL_MENU starting with 0 → off |
on | boolean | "Spatial front-end menu" toggle |
host_panorama_dispatch: in the shell ([00718FC9] nonzero) with this off, the panorama path is skipped (panorama_hooks.inc:381-385) |
panorama_target_fps: frame rate the bearing budget keeps (draws more of the sphere while busy time stays under it) |
HALO_PANORAMA_TARGET_FPS (10–60) |
30 | 10–60, else 27 | "Keep at least" 20–30 fps (step 1) |
panorama_target_fps() (panorama_hooks.inc:39-40); see Panorama Budget and LOD
|
self_gain_db: dB added to sounds Halo places on the listener (own weapon, reload, melee), which Halo hands to DirectSound 20 dB down |
HALO_SELF_GAIN_DB (0–24) |
18 dB | 0–24, else 18; halo_settings_self_gain() returns 10^(dB/20)
|
"Your own weapon" 0–24 dB (step 1) |
directsound_mixer.c (directsound_mixer.c:118, directsound_mixer.c:275) |
gaze_pointer: menu cursor also follows the head between pinches |
HALO_GAZE_POINTER starting with 1 → on |
off | boolean | Menus "Head pointer" toggle |
EngineImmersive.swift hover preview (EngineImmersive.swift:816), host_pointer_enabled()
|
layer_align: rotate panorama layers drawn in earlier engine frames to the newest centre camera (presentation only) |
HALO_LAYER_ALIGN starting with 1 → on |
off | boolean | Joins "Line up older views" toggle |
EngineImmersive.swift (EngineImmersive.swift:710); see Layer Alignment
|
frame_pacing: optional publication pacing on a 90 Hz grid (45/30/22.5/18/15 fps rungs) |
HALO_FRAME_PACING equal to 1, even or on → on (anything else, including malformed input, off) |
off | on only when exactly 1 | Frame rate "Even frame cadence" toggle |
frame_pacing_hooks.inc (frame_pacing_hooks.inc:84); see Frame Pacing
|
Rationale recorded in the source:
- 105° vertical field (halo_settings.c:52-57): "Rectilinear projection spreads a wide vertical field thinly: at 150 degrees the centre of view gets under 3 pixels per degree, which reads as a soft, low-resolution image. 105 degrees nearly triples that for the same pixels, and the cap beyond it is faded rather than shown."
- Head pointer off: "the eyes choose (the system's gaze ray at the pinch, its hover glow between)".
- Layer alignment off (halo_settings.c:69-76): checked only on synthetic pictures; while the stick turns it bends the side bands at eye level and sharpens the trailing join's cross-fade, so it stays off "until a b30 stick-turn A/B on the headset shows the joins improve without those side effects".
-
Frame pacing off: "Aggregate headset traces cannot establish a clear per-frame win. Keep experimental pacing opt-in, including after malformed input." Timedemo mode (
[007196D8]nonzero) forces pacing off regardless (frame_pacing_hooks.inc:82-84). - Own-weapon gain: "in a headset firing that cannot be heard over the music is a fault" (halo_settings.h:32-36).
EngineSettingsModel converts between the panel's units and the stored ones: eye separation mm / 1000 / 2 / 3.048 ("Halo's world unit is ten feet, so millimetres convert through 3.048"), degrees to radians for the field of view. When stereo is off the model stores 0 and remembers 63 mm for the slider. Any @Published change calls push(), which sends the whole structure; halo_settings_set stores every field, so the panel is the single writer of these fields once it has been opened. The panel header says "Changes apply to the next rendered frame."
The panel's slider ranges are narrower than what halo_settings accepts (for example 20–30 fps against 10–60, 90–175° against 60–175°, 45–80 mm against the environment's 40–90 mm), so an environment value outside a slider's range is shown clamped by SwiftUI's Slider; the code does not establish whether merely opening the panel re-sends such a value.
| Setting | Storage | Default | Effect and where read |
|---|---|---|---|
| Radial fog |
HALO_RADIAL_FOG exactly 1, cached once per process with pthread_once
|
off | Experimental bearing-invariant fog. Kept out of the live structure so "simultaneous diagnostic and renderer reads cannot see different values during initialization" (halo_settings.c:17-31). Read by d3d9.c, d3d9_render.inc, metalrenderer.m and the diagnostics. See Radial Fog. |
| Render resolution |
UserDefaults key HaloRenderResolution via @AppStorage; picker offers 1280x960, 1600x1200, 1920x1440, 2048x1536
|
2048x1536 |
Read only in enginevision_start (visionOS) and passed as -vidmode W,H,60 through HALO_CMDLINE_EXTRA; accepted when 640 <= W <= 4096 and 480 <= H <= 3072. HALO_VIDMODE (a full W,H,R string) takes precedence; an existing HALO_CMDLINE_EXTRA is never overwritten. Takes effect at the next launch because "the engine fixes its back buffer when it creates the device" (EngineVisionRuntime.m:681-695). |
| Core telemetry | HALO_CORE_TELEMETRY |
on in the app (off unless set to 0); always off in the desktop host |
Sets host_core_telemetry once before the engine runs (EngineVisionRuntime.m:656-659). See Threading and Synchronization. |
| Command line | HALO_CMDLINE_EXTRA |
unset (desktop); -vidmode ... (visionOS) |
Appended to "C:\Halo\halo.exe" -window -novideo. |
halo_settings_set_head(yaw, pitch) and halo_settings_set_head_roll(roll) carry where the viewer is looking inside the panorama, published every frame by EngineImmersive.swift (EngineImmersive.swift:772, EngineImmersive.swift:778). "The pose is not a setting, so it is neither seeded from the environment nor carried in HaloSettings; it starts at zero and is replaced every frame" (halo_settings.c:138-154). Non-finite values become 0; roll is clamped to ±1.5 rad and a non-finite roll is ignored. Yaw is read by the mixer so a sound stays with its object when only the head turns (directsound_mixer.c:138); pitch and roll by the panorama hooks (roll places the two centre cameras along the tilted baseline, unless HALO_STEREO_ROLL=0).
On visionOS, engine_worker installs this list with setenv(name, value, 0) before calling host_run and logs each resolved value as [device-config] NAME=value (EngineVisionRuntime.m:612-645). Because the overwrite flag is 0, a value already present in the launch environment wins. The comment explains the purpose: "Match the verified campaign probe without relying on launch-time shell variables, which a normal headset launch does not supply."
| Variable | Value | Meaning (owning page) |
|---|---|---|
HALO_FF3D |
1 |
Fixed-function 3D draw path for shaderless declaration draws (Direct3D9 Bridge) |
HALO_PANORAMA |
1 |
Multi-view panorama (Panorama System) |
HALO_PANORAMA_DENSE |
1 |
Dense panorama layout (Panorama System) |
HALO_PANORAMA_WORLD_FP |
1 |
World-space first-person weapon (Panorama System) |
HALO_PV_SKIN_FAST |
1 |
Exact-token skinning fast path (Geometry Fast Paths) |
HALO_NATIVE_GATHER |
1 |
Native BSP light/shadow gathers (Engine Overrides and Hooks) |
HALO_DRAW_FASTPATH |
1 |
Static geometry reuse, capped at 128 MiB (Metal Renderer) |
HALO_PAD2KEY |
1 |
Controller to keyboard/mouse bridge (Input and Controllers) |
HALO_A10_AUTOPLAY |
0 |
Probe autoplay off (Input and Controllers) |
HALO_UNLOCK_CAMPAIGN |
1 |
In-memory campaign unlock (Engine Overrides and Hooks) |
HALO_AUDIO_VOICE_TRACE |
1 |
Bounded five-second DirectSound/decoder summaries (Audio System) |
HALO_CTLLOG |
1 |
Controller logging (Input and Controllers) |
HALO_HSC_TRACE |
120 |
HSC script trace interval (Engine Overrides and Hooks) |
HALO_HSC_TRACE_MAX |
128 |
HSC trace events per snapshot |
HALO_CORE_TELEMETRY |
1 |
Core residency/scheduling/frame-split telemetry (Diagnostics and Telemetry) |
After the list, HALO_DRAW_FASTPATH is applied to the renderer immediately (mr_set_fast_paths), and HALO_TEXTURE_PACK / HALO_SHADER_PACK are set (again without overwriting) to the bundled TextureMods.hvt and ShaderMods.hvs when present (EngineVisionRuntime.m:646-654). The live-setting variables (HALO_STEREO*, HALO_PANORAMA_VFOV, ...) are not in the list, so on the headset they start at the compiled defaults.
mods/runtime-settings.json is a release record of the defaults the app ships with. docs/FEATURES.md states: "The JSON records compiled defaults; it is not a separate settings loader." No code reads it at run time; changing it changes nothing. It is listed with its size and SHA-256 in SOURCE_MANIFEST.json and included in the complete release archive.
| JSON key | Value | Corresponds to |
|---|---|---|
formatVersion |
1 | record format |
version |
1.0.3 |
MARKETING_VERSION in project.yml
|
runtimeBaseline |
Build91 |
the runtime build these defaults were verified against |
appliesAutomatically |
true |
defaults need no user action |
renderResolution |
2048x1536 |
HaloRenderResolution default / -vidmode 2048,1536,60
|
targetEngineFPS |
30 |
panorama_target_fps (HALO_PANORAMA_TARGET_FPS) |
panoramaVerticalFOVDegrees |
105 |
panorama_vfov (HALO_PANORAMA_VFOV) |
stereoIPDMillimetres |
63 |
stereo_separation (HALO_STEREO_IPD) |
hapticsStrength |
1 | haptics_strength |
backdropBrightness |
1 |
backdrop_brightness (HALO_BACKDROP) |
selfGainDecibels |
18 |
self_gain_db (HALO_SELF_GAIN_DB) |
spatialMenu |
true |
spatial_shell (HALO_SPATIAL_MENU) |
gazePointer |
false |
gaze_pointer (HALO_GAZE_POINTER) |
experimentalLayerAlignment |
false |
layer_align (HALO_LAYER_ALIGN) |
experimentalFramePacing |
false |
frame_pacing (HALO_FRAME_PACING) |
radialFog |
false |
HALO_RADIAL_FOG |
audio.sampleRate |
48000 |
OUTPUT_RATE in directsound.c
|
audio.bufferFrames |
1536 |
output_frames_per_buffer default (HALO_AUDIO_FRAMES, 64–2048, overrides) in directsound.c
|
audio.queuedBufferCount |
3 |
OUTPUT_BUFFERS in directsound.c
|
environmentDefaults |
15 variables | exactly the engine_worker list above |
privacy |
text | states that no per-device tracking history, pose, identity, registration, save state or signing data is included, and that the sampled headset had no saved render-resolution override |
limits |
text | "Matching settings and assets do not establish equal performance or resolve known gameplay issues." |
When a default changes in code, the record should be updated to match; nothing enforces that automatically in the files examined for this page.
| Test | Asserts |
|---|---|
test_halo_settings_layer_align.c |
In fresh child processes (settings are seeded once per process): unset, empty, 0 and yes give 0, 1 gives 1, and halo_settings_get agrees with the accessor. Live: setting layer_align to 1 and back changes only that field (gaze_pointer and spatial_shell unchanged). Built by run_source_checks.py with halo_settings.c. |
test_radial_fog_settings.c, test_frame_pacing_host.c, test_haptics_*.c, test_mixer_quality.c and several panorama tests |
Link halo_settings.c and exercise the radial-fog switch, pacing toggle, haptics strength and self gain from their subsystems' side; see Radial Fog, Frame Pacing, Haptics, Audio System. |
- EngineHost Overview
- visionOS App, Immersive Presenter, Layer Alignment
- Panorama System, Panorama Budget and LOD, Frame Pacing
- Audio System, Haptics, Radial Fog
- Engine Overrides and Hooks (menu pointer, campaign unlock, HSC trace)
- Environment Variables
Documents master-chef at commit 9f915af (v1.0.3). Unofficial project, not affiliated with Microsoft, Bungie, Gearbox or Apple. Original code is MIT licensed; game content is not included.
Overview
- Architecture Overview
- Repository Layout
- Glossary
- Environment Variables
- Contributing Guide
- Open Questions
Translation
- Static Translation Pipeline
- XWA Decoder and Lifter
- Function Address Lists
- EngineReuse Runtime
- x87 Floating Point
Host runtime
- EngineHost Overview
- Win32 Compatibility Layer
- Threading and Synchronization
- Guest Memory and Heap
- Engine Overrides and Hooks
- Runtime Settings
Graphics
- Direct3D9 Bridge
- Metal Renderer
- Shader Translation
- Textures and Texture Packs
- Geometry Fast Paths
- Radial Fog
Panorama and presentation
- Panorama System
- Panorama Budget and LOD
- Frame Pacing
- visionOS App
- Immersive Presenter
- Layer Alignment
Audio and input
Tooling and process