Skip to content

Releases

visr-v0.6.12

visr-v0.6.12 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 06 Nov 12:46

visr-v0.6.12 (2026-11-06)

Summary

  • Downloads: VISR now gets device profiles, shader lists, benchmark recordings and update notices from feeds.visr.gg. Check that the game starts normally and that Benchmark lists its recordings.
  • Memory: each change of render size (dynamic resolution, an external display, a rotated or resized window) used to leave the previous screen buffers in memory for the rest of the session; VISR now frees them, so memory no longer grows over a long session.

iCloud Saves starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

feat

  • feeds: builds and releases read their feeds from feeds.visr.gg (#112)
  • feeds: tools/feeds_store.py records the feeds in steverice/visr-feeds and copies them to the feeds.visr.gg bucket (#112)

fix

  • render: free the screen targets a resize leaves behind — render_target_get_layer made a new entry whenever the screen's size or scale changed and kept the old entry and its texture for the rest of the run

test

  • feeds: a build fetches and verifies every feed from a stand-in feeds.visr.gg (#112)

visr-v0.6.11

visr-v0.6.11 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 05 Nov 13:28

visr-v0.6.11 (2026-11-05)

Summary

  • Flags: above 30 fps, a flag's cloth moves at its normal pace and stays smooth on the pole and when carried, and a falling flag's or oddball's waypoint follows it smoothly.
  • Effects: decals left by particle effects no longer depend on the frame rate.
  • Menus: a mouse or trackpad passing over a carousel picture selects that list without stepping it, and a click right after Save and Quit no longer lands on the menu that just closed.
  • External displays: a TV now gets its preferred mode first, without a refused switch to 4K, and dynamic resolution has room to lower the render size on the TV, so fewer frames arrive late.
  • Benchmarks: a run made while the screen is mirrored to a TV or over AirPlay is marked as mirrored and is left out of device profile tuning.
  • Logs: VISR keeps the previous session's log beside the current one, caps the log at 8 MB, and records frame pacing, external display modes and dynamic resolution in it.
  • Mac: new Game and View menus with Command-key shortcuts: Release Mouse (Control-Command-M), Back (Command-[) and Enter Full Screen (Control-Command-F). These replace F12, F1 and F11, which the Mac app no longer reads, since a Mac keyboard's top row controls brightness and volume.
  • Shadows: every device uses 128 shadows, including devices without a device profile and devices that can't reach the profile server. The visual difference at higher sizes is small, and the cost in big games is large.
  • Metal: when the game draws shadows and small effects in the middle of a scene, the scene is no longer stored and reloaded, which cuts memory traffic in busy scenes.
  • Multiplayer: a join refused because too many players come from one address now says so.

iCloud Saves starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

feat

  • log: keep the last session's log and the renderer's lines in ios-runtime.log — ios-runtime.log was opened with w at every launch, so a relaunch erased the session a tester needed; host_log_file_open now renames the last log to ios-runtime.previous.log, host_log_file_line keeps the log to 8 MB by moving a full one to ios-runtime.rotated.log and continuing over the same descriptor that host_crash.c writes to, and platform_log (the renderer's frame pacing, external display rate and resolution planner lines, which iOS does not keep on stderr) now also lands in the log; log_file_probe.m covers both
  • mac: Command-key shortcuts and Game and View menus in the native Mac app — host_macos_menu.m adds Release Mouse (Control-Command-M), Back (Command-[) and Enter Full Screen (Control-Command-F), host_macos_shortcuts.h keeps those keystrokes and the removed F1, F11 and F12 from the game, and F8 and F9 now exist only in development builds

fix

  • logging: put the output change, the pacing rate and a DRS summary in the runtime log — the renderer wrote them through platform_log to stderr, which iOS does not keep, so a player's ios-runtime.log showed neither the frame rate the display link really ran at nor what dynamic resolution did; Metal: output changed and Metal: frame pacing follows ... measured now go through host_logf, and dynamic_minute adds one line a minute while dynamic resolution acts: frames, late frames, frames below the ceiling, the lines' range, ceiling, floor and thermal state
  • external: find a display's preferred mode by size — begin_modes matched preferredMode against availableModes by object, UIKit handed back a different object, and with no preferred index host_external_mode_choose asked a 1080p TV for its 3840x2160 mode first (a refused switch of about a second) and labeled the mode it kept [capped]; host_external_mode_preferred matches size and pixel aspect, with probe cases
  • drs: give dynamic resolution room on an external display — a device profile's floor is chosen for the device's own screen, and on a 1080-line TV the iPhone 15 Pro class's 1080-line predictable floor equaled the ceiling, so every frame rendered 1080 lines and a session presented a third of its frames a refresh late with no level to drop to; dynamic_resolution_output_floor holds the floor at most at half the ceiling while host_external_active(), host_perf_hooks_dynamic_config applies it and logs the floor it uses once per ceiling, the device's own screen keeps the profile's floor, and the planner probe and a new external actuation case cover it
  • external: log the refresh rate of the mode a display connects in and of a kept sub-60 Hz mode — UIScreenMode carries no rate, so the connect line now gives the current mode's maximumFramesPerSecond and says whether it is the preferred mode, and keeping a mode below 60 Hz names the mode, its rate and whether it is the display's preferred one; with the existing physical ... at N Hz, drawable ... line, a log shows the preferred and chosen modes with their rates
  • benchmark: count AirPlay mirroring and never report a mirror for an iPad app on a Mac — the scene accessory offered for 3 s (HOST_EXTERNAL_OFFER_SETTLE) with no scene of VISR's connected and the game not on the display now counts as mirrored, which catches AirPlay mirroring to a TV without counting AirPlay speakers; host_external_poll keeps the offer clock (host_external_offer_track), so VISR's own scene connecting or choosing its mode is never flagged; host_external_display_use takes a host_external_display_signals struct, and NSProcessInfo.iOSAppOnMac ("Designed for iPad" on a Mac) reports no external display at all; probe cases for the settle time, the offer clock and the Mac case
  • profiles: leave runs mirrored to another display out of profile proposals — profile_derive.usable_runs skips runs whose output.display_use is mirrored, derive says how many it left out, and run_table marks them, since such a run measured neither the built-in screen nor the external display
  • benchmark: record when the system mirrors the device to a display the game is not on — a TV attached without an external scene leaves the game drawing the device's own size at the device's rate while the system scales each frame to the TV, and the report said only builtin, so such runs were judged against the device's targets; host_external_displays now reads the other screens, how many mirror the device, the HDMI or AirPlay output in the audio route and the scene accessory's availability, host_external_display_use (in host_external_session.h, with probe cases) calls a run in_use, mirrored, other or none (an HDMI route with no screen listed counts as mirrored; AirPlay alone does not, since speakers route the same way), output_now adds display_use, other_screens, mirroring_screens, video_route and accessory_available to the report's output, and a suite or run mirrored at its start or end gets the external_display_mirrored flag
  • metal: close the remaining ordering gaps in scene pass parking
  • metal: treat a rectangle view as its parent when deciding whether a pass may run ahead of the scene
  • flags: draw the flag cloth between the last two ticks' shapes — behavior described in OpenH1 PR #540
  • network: a specific message for join refusal code 13 — code 13 is OpenCE's address limit, as described in Chupathingy's public release notes
  • menus: a pointer passing over a carousel slot focuses its list and does not step it — behavior described in OpenH1 PR #531
  • menus: drop the pointer's targets and pending input when a widget is deleted — behavior described in OpenH1 PR #524
  • effects: draw a frame's local random numbers from the render seed — behavior described in OpenH1 PR #547
  • hud: draw goal waypoints and their distance from drawn state — behavior described in OpenH1 PR #540

perf

  • display: default display.shadow_map_size to 128 — devices without a profile get the same shadow size; regenerate the schemas and settings tables and update the README text
  • profiles: set display.shadow_map_size to 128 in every bundled profile — the visual gain above 128 is small and the cost is large in big games, and the bundled profiles now match the served ones; the evidence notes say why and the profile set revision is bumped to 17
  • metal: run passes that interrupt the scene ahead of it instead of storing and reloading the scene
  • flags: step flag cloth once per game tick — behavior described in OpenH1 PR #540

test

  • profiles: expect the 128 default in config_origins_probe and profile_probe — and keep the workload-context check meaningful with a 256 variable override

ci

  • release: accept the Xcode 27.2 release (27B52) — the xcode-27 runner image now carries only 27B52, so tools/xcode_select.py stopped the release probe there

chore

  • mac: log the key a menu bar item presses for the game

visr-v0.6.10

visr-v0.6.10 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 04 Nov 14:23

visr-v0.6.10 (2026-11-04)

Summary

  • Mac: the mouse stays captured while you play, so turning no longer stops at the screen edge, with no F12 needed. Menus release it, and F12 still toggles it.
  • Multiplayer: these rules match the Xbox game again: a second weapon is picked up with the action button, not by walking over it; infinite grenades are off with five or more players; a team kill's respawn penalty goes to the betrayer, and the victim respawns on the normal timer; a dead player stays on the death camera; a weapon swap during a switch drops the weapon in hand (a remote player on a host still drops the selected weapon, which covers network timing).
  • Unarmed players: a player with no weapon can't melee or throw grenades, in campaign and in a gametype with no starting weapons, as on the Xbox.
  • Grenade throws: in the air, the body no longer turns with the aim.
  • Shader warm-up: each pixel shader now has one key, so a map's shader list warms far more of what it draws. Look for fewer first-time hitches on maps you haven't played before.
  • Upscaled textures: Start playing becomes available sooner, since it no longer waits for menu art that nothing draws. Menu titles drawn as high-resolution text are no longer upscaled.
  • Metal: moving geometry makes fewer GPU buffer uploads, and the game spends less time tracking its memory writes.
  • Campaign: AI chatter and new particles no longer leave leftover data in the game state, so recorded replays stay in step with the game.

iCloud Saves starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

feat

  • upscale: classify replaced bitmaps as original and report which bitmaps no root reaches

fix

  • shaders: restore alpha_test_samples in pixel_key_finish — moving the key build out of prepare_draw dropped the block that sets it for multisampled, alpha-tested, non-blended draws, which turned off MSAA alpha-test edge coverage and the shader_list_pixel_made MSAA skip. tools/test_pixel_key_samples.py builds keys with the real pixel_key_finish and canonicalizer and checks the field is set, kept, and absent for single-sample, blended and sample-mask-less draws; it is in ios_test.py
  • units: drop the exception for a loadout of none from the melee and grenade prevention — a multiplayer game that spawns players with no weapon now prevents both too, as on the Xbox in every gametype (weapon_prevents_melee_attack(NONE) and weapon_prevents_grenade_throwing(NONE) are true); behavior described in Chupathingy's release notes and OpenH1 PR #551
  • units: prevent a grenade throw for a unit holding no weapon — a10's opening let the unarmed player throw; as on the Xbox, where weapon_prevents_grenade_throwing(NONE) is true, only a multiplayer game whose custom loadout gives no weapon keeps it; behavior described in Chupathingy's release notes
  • bipeds: prevent melee for a player holding no weapon — the unarmed melee added in 23b542601 started a swing at nothing in a10's opening; as on the Xbox, where weapon_prevents_melee_attack(NONE) is true, only a multiplayer game whose custom loadout gives no weapon (game_engine_unarmed_loadout, which the Xbox game has not) keeps it; behavior described in Chupathingy's release notes and OpenH1 PR #551
  • camera: keep a dead multiplayer player on the death camera — dead_camera_new no longer starts the 15 second switch to following a living player, as on the Xbox; a host option to allow the follow camera comes later; behavior described in Chupathingy's release notes
  • players: drop the weapon in hand when a swap comes during a weapon switch — player_handle_weapon_swap dropped the weapon the player had selected; a local player now drops the one in hand, as on the Xbox, while a remote player on the host keeps the selected-weapon rule because its choice reaches the host before the host's copy has finished switching; behavior described in Chupathingy's release notes
  • bipeds: keep the body from turning with the aim in the air while throwing a grenade — the throwing_turn enhancement now applies on the ground only, so the campaign's AI, which reacts to the body, matches the original; behavior described in OpenH1 PR #565
  • game-engine: give the team killer the respawn penalty, not the player killed — game_engine_player_killed charged the victim of a team kill the gametype's suicide penalty; the betrayer now takes it on the next respawn along with any friendly fire penalty, and the victim respawns on the normal timer, as on the Xbox; behavior described in Chupathingy's release notes (PR #677)
  • game-engine: turn infinite grenades off at five or more players — game_engine_infinite_grenades_internal and game_engine_remap_equipment check _game_engine_5_or_more_players_bit again, so grenades spawn on the map and a throw uses one up, as on the Xbox; behavior described in Chupathingy's release notes
  • players: pick up a second weapon in multiplayer with the action button, not by walking over it — unit_should_autopick_weapon again limits the walk-over into the empty slot to campaign, as on the Xbox; behavior described in Chupathingy's release notes and OpenH1 PR #552
  • mac: keep F12 working with the menu-driven mouse hold — platform_mouse_menus_changed closes a menu with a recapture only when F12 has not released the mouse, so leaving the pause menu no longer overrides the player's release
  • mac: hold the mouse in the iOS app run on a Mac as well — host_sdl_set_relative_mouse now also passes the request to SDL when host_ios_app_on_mac (NSProcessInfo.isiOSAppOnMac) is true, since a "Designed for iPad" run is the iOS binary and matches neither HALO_NATIVE_MAC nor TARGET_OS_MACCATALYST
  • mac: hold the mouse while the game is played — the iOS guest (HALO_ILP32) compiles out every platform_mouse_capture caller, so SDL_SetWindowRelativeMouseMode was never called and the cursor roamed free until it hit the screen edge and the view stopped turning; the guest's halo_ui_pointer_update now asks the host to hold the mouse when no menu is up and release it when one is, and host_sdl_set_relative_mouse passes the request to SDL only on the Mac (AppKit and Catalyst), where touch screens have no cursor to hold
  • ai: zero the AI packets, speech items and new particle data built on the stack — the reply speech item, the communication packet and the second speech item in ai_communication.c, and the new_particle_data in effects.c and breakable_surfaces.c were filled field by field and copied whole into hashed state, leaving padding and unused members (such as an unattached particle's node_index) as stack garbage; tools/test_zero_init_structs.py fails when such a local is used before being cleared (behavior described in OpenH1 PR #467 and its sibling zeroing commits)

perf

  • metal: track the vertex mirror in the write tracker's pages and back off the volatile re-watch — MIRROR_PAGE_SIZE was 4 KB while host_memory.c keeps one generation per 16 KB, so one write to a dynamic vertex page made its three neighbors look rewritten, uploaded them again and turned them volatile. memory_watch_page.h now gives every consumer the tracker's page (MEMORY_WATCH_PAGE_SIZE, memory_watch_page_size()), used by the mirror, gpu_capture.c and gpu_validate.c. A page that turns volatile again right after its period ran out now waits twice as long each time, up to 32 times MIRROR_VOLATILE_FRAMES.
  • shaders: regenerate port/shader-lists/*.txt with canonical pixel shader keys — 14,254 ps lines (5,927 distinct keys) become 1,620 (228), 17,357 pipelines 2,451, 9.0 MB 1.1 MB, with python3 tools/shader_lists.py canonicalize
  • shaders: zero the combiner registers past the combiner count in pixel shader keys, so one shader is one key — the translation runs only the stages below D3DRS_PSCOMBINERCOUNT, but combiner_state copied the leftovers of earlier shaders, which made 5,031 distinct keys of 218 shaders and unseen maps miss 32% of theirs; the warm path canonicalizes list keys and maps old pixel hashes to them so older lists and feeds still warm their pipelines
  • upscale: leave title art out of the upscale set and out of the Start playing gate when nothing draws it

refactor

  • shaders: tools/shader_lists.py canonicalizes pixel shader keys when it merges, rewriting each pipeline line to the canonical ps hash, and gains a canonicalize command

test

  • memory: report the in-window remap race beside a watched block — memory_probe.c loops host_guest_mmap beside a block the renderer protects in the same 16 KB page and counts writes that did not advance the generation (diagnostic; HALO_PROBE_D_STRICT makes it fail)
  • input: add debug.test_input unarmed:<start> — it stands still and presses melee and the grenade in turn, to check a10's opening before the first weapon
  • shaders: add alias_count to the shader_list stub in test_network_shader_warmup.py — halo_shader_list_warm now resets it, so the probe no longer compiled and ios_test failed

chore

  • units: log a...
Read more

visr-v0.6.9

visr-v0.6.9 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Nov 06:45

visr-v0.6.9 (2026-11-03)

Summary

  • Shadows: dead bodies and weapons in scenes with many shadows no longer show hard black silhouettes of other units, and their shadows no longer jump around as the order changes.
  • Shadow map size: 512 is the largest size. Higher values from settings, profiles or overrides use 512.
  • iPhone, iPad and Apple Vision Pro: a disc image imported from outside VISR's own folder stays where it is. An image inside the app's Documents is removed only after the installed maps verify.

iCloud Saves starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

fix

  • ios: remove imported images from private Documents — verify installed maps before reclaiming the disc copy
  • render: draw overflow shadows in the game's own targets — render_object_shadows applied a shadow that found no deferred slot through the last applied deferred shadow's atlas tile and second target, with deferred_shadow_applying still set, so its blur was skipped and it was applied from that other object's blurred shadow: dead bodies and weapons showed hard black silhouettes of other units, which jumped as the order changed. It now calls rasterizer_environment_shadows_apply_end first. Overflow happens whenever a frame has more shadows than the atlas holds (3 at display.shadow_map_size 2048, 12 at 1024, about 90 at 512)

refactor

  • render: make 512 the largest display.shadow_map_size — values above 512 (settings, profiles including [variable.display], HALO_SHADOW_MAP_SIZE, benchmark and knob overrides) now clamp to 512 in offscreen_target_scale, with a log line for the setting, instead of being used up to 2048, and debug.knob_cycle no longer offers 1024. The 1024 and 2048 sizes cost a lot, overflow the atlas after 12 and 3 shadows, and do not look noticeably better. Docs and generated schemas now say 128 to 512

test

  • knob: knob_cycle_probe expects the shadow map knob to stop at 512 — debug.knob_cycle no longer offers 1024

visr-v0.6.8

visr-v0.6.8 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 31 Oct 12:31

visr-v0.6.8 (2026-10-31)

Summary

  • Apple Vision Pro: SMAA anti-aliasing is now the default, over each eye and over the zoomed scope picture.
  • Sniper rifle: the scope's reticle is redrawn at high resolution.
  • Multiplayer: picking a browsed game larger than this device holds now asks before joining. When a game cannot be reached because of this network's NAT, the browser says "Strict NAT, turn on UPnP".
  • Multiplayer: a client no longer starts a game before the map it is downloading is there.
  • Co-op: after a revert to a checkpoint, clients keep their living players and get the checkpoint's units back.
  • Gameplay fixes: items resting on surfaces numbered above 32767, decals and unit lighting no longer depend on the texture cache, and wind now steps once a tick.

iCloud Saves starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

feat

  • stereo: FXAA and SMAA over each eye in head-tracked stereo — display.anti_aliasing filtered nothing in a stereo frame, because halo_render_scene_end looked up the mono back buffer (HALO_STEREO_LAYER_MONO, never drawn there) and anti_aliasing_pass returned early in any stereo frame
  • profiles: hold a big multiplayer game to 30 frames a second — network.full_rate_players is the most players a device holds at its variable frame rate; above it a multiplayer map runs at 30 for the whole map, nested under network.player_capacity, with no player setting
  • menus: say "Strict NAT, turn on UPnP" when the server browser cannot reach a game and this network's NAT is the cause — a failed join showed only "Could not reach " even though the p2p log already knew the NAT gave each destination its own port and no router forwarded the tunnel's
  • visionos: add live anti-aliasing comparisons — debug.knob_cycle exposes AA and runtime quality while stereo logs each comparison
  • stereo: multisampling on the eyes' foveated targets, with color samples kept only in tile memory — display.anti_aliasing "msaa2x"/"msaa4x" now reaches a stereo frame's eyes and debug.rate_map_test's targets, and a multisampled color target's samples are memoryless, drawn back from a copy of the resolved picture when a later pass loads them
  • metal: multisampling for display.anti_aliasing "msaa2x", "msaa4x" and "msaa8x" on the Metal renderer — the measurement spike's multisampling (acb5726fc) brought onto current main, as the base for Vision Pro stereo anti-aliasing
  • network: list a fake marked game in development builds — HALO_BROWSER_FAKE_BIG adds one BIG listing to the server browser through lobby_browser_games_read, compiled only under HALO_DEVELOPMENT_HOOKS, so the join warning can be screenshot without a real host
  • network: ask before joining a browsed game larger than this device holds — picking a game marked BIG in the server browser or the LAN list opens a Halo-style warning (error/confirm_join_big_modal_fullscreen, the PC menus' confirm-dialog look) before its password screen, preview or join; OK replays the pick against the game found again by its identity, CANCEL and B return to the browser with its selection kept, and Direct Link, codes and invites do not ask; tools/test_join_warning.py covers both browsers, cancel, capacity 0, password order and a vanished or reordered game

fix

  • hud: round the sniper reticle center — hud_reticles_scope now uses a tiny solid circle with the original dot’s visual weight
  • hud: add the sniper scope redraw — hud_reticles_scope was missing from the 8x set
  • stereo: the zoomed scope picture honors display.anti_aliasing — render_target_get_layer makes the zoom target a multisampling screen buffer and anti_aliasing_layer returns the zoom layer during the zoomed pass, so FXAA, SMAA and MSAA run on it as on the eyes while scoped
  • coop: preserve living replicas on revert — capture full unit IDs before game_state_revert replaces the world
  • coop: recreate checkpoint units — unchanged datum IDs let distributed_client_create reuse corpses
  • net: do not start a client before the map it is downloading is there — a Custom Edition client still fetching its map by map sharing asked for its player again after the host started, the host took the request for a late joiner's and started it at once, and the client loaded a map it did not have (the damaged disc error)
  • metal: multisample at a count the GPU takes — the visionOS simulator refuses 2 samples a pixel, and allocating a 2x target aborted on Metal's descriptor validation; gpu_metal_texture_multisample now takes the next count supportsTextureSampleCount accepts (4 there) and logs the substitution once
  • scenario: step the wind once a tick, at the end of game_tick — scenario_frame_update stepped it from the frames' elapsed time, so the wind and the antennas it moves differed with the frame rate and between runs
  • effects: make a decal whether or not the texture cache has its bitmap yet — decal_new_from_collision returned when the bitmap was not cached, which a thread fills by the clock and the cache's room, so two runs of one map made different numbers of decals and every later local-seed pick parted
  • objects: light a unit from its lightmap whether or not the texture cache has it yet — lights_illumination_at_point sampled the structure's lightmap only if the cache already held its bitmap, which the loading thread fills by the clock, so a unit's illumination (the AI's sight) differed between runs in the first ticks of a map
  • units: do not keep the sound manager's index of a unit's speech — unit.speech.impulse_sound_index took the mixer's index, which depends on whether the sound can be heard and has a channel, and nothing reads it, so two runs of one game differed there
  • scenario: set the bonus field in scenario_location_from_point — it set only the leaf and cluster, so the stack's bytes went into every object, particle and effect that keeps the location and two runs of one game hashed differently
  • ios: send on a connected datagram socket when the game still gives a destination — Darwin refuses sendto with EISCONN on a connected UDP socket, which Linux and Winsock accept, so ios_sendto failed every gameplay datagram the game addressed after connecting
  • interface: start each local player's first-person weapon_index at NONE on a new map — first_person_weapons_initialize_for_new_map zeroed the structure, so slot zero was taken for a real weapon until the player held one
  • items: keep an item's resting surface past 32767 — the surface index lives in a signed 16-bit field, so a collision surface above 32767 read back negative and item_accelerate and item_update indexed the collision surfaces with it
  • render: let a frame draw 32768 structure triangles and give the dynamic triangle buffer room for them — retail stops the list at 16384 and RASTERIZER_MAXIMUM_DYNAMIC_TRIANGLES held 32768, so a large map or a high resolution frame lost the rest of its structure
  • network: give the fake marked game a Slayer engine — lobby_browser_games_read left engine_type at 0, so the Game column read CO-OP while the description said Slayer
  • network: list the fake marked game first — lobby_browser_games_read put it after the sorted listings, on the last page of a long list

perf

  • metal: keep the anti-aliasing targets of two sizes — a stereo frame's eyes and the zoomed picture differ in size, so with SMAA each scope toggle remade three private textures and compiled three pipelines on the render thread; gpu_anti_alias now swaps between the current and previous sizes
  • input: read the clock only when a button is held or collision times are shown — update_hold_ticks read it for every key each frame and collision_log_start_time and collision_log_end_time read the performance counter twice for every collision test, whether or not collision_log_time showed the result
  • menus: do not look up the last-used multiplayer map on every lobby redraw — map_display_name and lobby_map_show asked ui_widget_port_multiplayer_maps for a last_used they never read, which opened the saved game file each time

build

  • profiles: sign generations 3 and 4 from one source — tools/profile_pr.py signs every published generation in one run, device-profiles-3 without network.full_rate_players, whose released validators refuse it, so released apps keep getting profile updates once a profile sets the key

test

  • coop: guard the revert capture's dead filter and reset — two more negative controls for network_objects_coop_reverting, and NETCODE.md now says units alive throughout rely on the ordinary inventory checksums
  • net: p2p_lobby_check expects 40 and 41 joinable and refuses 42 and 43 — it used 40 and 41 as its unjoinable versions, so main's Linux release compile failed after halo_network_version_joinable took 40 and 41

chore

  • profiles: SMAA is the Vision Pro default — display.anti_aliasing = "smaa" in vision-pro.toml and vision-pro-m2.toml; GPU median at quality 0....
Read more

visr-v0.6.7

visr-v0.6.7 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 30 Oct 11:03

visr-v0.6.7 (2026-10-30)

Summary

  • Try joining multiplayer games hosted on network versions 40 and 41, in addition to earlier supported hosts.
  • Join a version 40 or 41 host and confirm you reach the lobby without a generic join failure.
  • If a host refuses your join request, check that you now see a specific message explaining why instead of a generic failure.
  • Try joining a host on version 42 and confirm it is still refused.
  • Join a host on an earlier version, below 40, and confirm joining still works as before.

iCloud Saves is new and starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

feat

  • net: join hosts of network versions 40 and 41 — hosts from 40 take a 350-byte message_client_join_game_request (234 zero bytes before the type trailer), built by create_network_game_message_padded; refusal codes 8 to 10 now show a message through platform_show_message instead of a generic failure. Behavior described from black-box observation of OpenCE release builds.

fix

  • net: log the join request's wire size — network_game_client_idle_joining read GET_MESSAGE_SIZE after network_game_client_write had byte-swapped the header in place, so it logged 1216 for a 116-byte request; halo_network_join_request_wire_size pins 116 below version 40 and 350 from 40

test

  • net: delta_probe joins 40 and 41 and refuses 42 — 40 was its example of an unjoinable version
  • net: the join request's padding follows network_game_host_version — review found no test tied network_game_client_idle_joining to the host version

visr-v0.6.6

visr-v0.6.6 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 30 Oct 07:20

visr-v0.6.6 (2026-10-30)

Summary

  • Finish a benchmark run, then try the completion screen and benchmark menu. Dragging on the completion screen should no longer crash the app, which was seen on an iPad Pro M4.
  • Open the benchmark menu more than once between frames, for example by choosing "Not now" or reopening it after an offscreen benchmark. The app should stay stable.
  • Play split screen with 3 or 4 players and check the fog effects in windows 2 through 4. They should no longer cause memory corruption or crashes.
  • Walk through portals and doorways, and watch for areas that briefly fail to appear for a single frame. Areas behind portals should now draw correctly.
  • Load modded or merged maps where a player's HUD reference is empty. The game should no longer crash on those seats.

iCloud Saves is new and starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

fix

  • benchmark: leave the frame workgroup only once between frames — gpu_metal_frame_deadline_leave compared the state after frame_deadline_state_after_leave, which returns FRAME_DEADLINE_LEFT for a thread that has already left, so a second ask() with no shown frame between (Not now, or the menu again after an offscreen benchmark) called os_workgroup_leave for a thread that is no longer a member; frame_deadline_leave_wanted lets only the joined thread leave, and frame_deadline_probe checks the double leave
  • benchmark: leave the frame workgroup before the benchmark menu runs UIKit's loop — the drawing thread is the main thread and stays in the VISR frame workgroup after its first Present, and UIKit raises from _beginInterval on such a thread (an iPad Pro M4 crashed dragging the completion screen); ask() calls gpu_metal_frame_deadline_leave, the next shown frame joins again, and a @try/@catch keeps a UIKit exception from ending the app
  • rasterizer: index the fog screen's previous camera matrix by window — a local previous_camera_matrix pointer shadowed the static array, so rasterizer_environment_fog_screen_draw wrote past previous_camera_matrix for windows 2 to 4 of split screen. Same bug class as described in OpenCE PR #519. previous_camera_matrix_for_window now checks its index.
  • math: zero a NaN crossing parameter in convex_polygon2d_clip_to_plane — a portal corner exactly on the clip line with its neighbor a rounding error behind gave 0/0, which PIN passes through, so the clusters behind the portal were skipped for a frame. Same bug class as described in OpenCE PR #518. Adds tools/test_portal_clip_nan.py, which clips outlines that produce the NaN.
  • cache: return NONE from tag_loaded for a NULL name — verify_tag_reference passes an empty reference's name straight in and _stricmp dereferenced it, crashing on seats whose HUD reference is empty (modded or merged maps). Behavior as in halo64 commit d3245ada48 (CC0-compatible). Adds tools/test_tag_loaded_null.py.

visr-v0.6.5

visr-v0.6.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Oct 23:15

visr-v0.6.5 (2026-10-29)

Summary

  • If the texture import refuses, tap Try Again. Start playing should stay available afterward, and the import screen should keep the retry note and buttons centered.
  • On Mac, check that the import screen shows storage estimates and keeps them visible.
  • On Mac, check that Start playing stays blue with readable black text, even when the window isn't active. Also check that the first click on the import window works and that VoiceOver names the button.
  • In VR, zoom in on foot and turn your head. The reticle and scope ticks should stay centered instead of swinging off and snapping back.
  • In VR, start a game from the main menu, especially multiplayer. It should go straight to the full view without briefly showing the flat HUD first.
  • With the Metal renderer, look at distant decals while moving the camera slightly, such as the base emblems on Blood Gulch through a scope. They should stay intact instead of losing triangles.

iCloud Saves is new and starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

feat

  • upscale: retry refused imports — keep Start playing available and show Mac storage estimates

fix

  • stereo: keep the zoomed reticle centered on foot in VR — it projected the unit's aim, which follows the head only at each game tick, through the zoom camera, which follows the head every frame, so between ticks the reticle and its scope ticks swung off center by the head's turn times the magnification and snapped back 30 times a second
  • stereo: go from the menu straight to the full view when a game starts in VR — the main menu's scripted camera is the film, and its end path (the hold, the wait for the camera to reach the eyes, the window onto the world, the expansion) carried into the game the menu started, so a multiplayer game's first second or two showed on the screen with the flat HUD, its field of view changed there, and only then did it expand around the player
  • macos: keep Start playing blue when the window is not key — a rounded bezel's color turns gray in an inactive window, which left its black title on dark gray; the button now draws the bar's blue with its own layer
  • macos: draw Start playing's title in black on the bar's blue, and test that Start playing returns after a passing Try Again — the AppKit title was white on the light blue fill (about 1.7:1 contrast); it is black as on iPhone and iPad (about 12:1); the driver probe now checks that, after a refusal and a passing retry, the import offers Start playing again once the main menu's textures are made
  • import: name Start playing for assistive tools, take the first click on the Mac, and quiet a false stall line — on Catalyst the filled button's configuration title was not its accessibility label, so VoiceOver found an unnamed button; the AppKit phase window comes forward and key before its modal session, and its buttons accept the click that brings VISR forward instead of spending it on activation; the import watchdog no longer logs "nothing finished" when a catalog rebuilt on the app's return hides the main menu's count after Start playing was already offered
  • upscale: center refusal actions and retain Mac storage counts — keep retry notes and button positions stable in the import UIs
  • upscale: update the import screen asynchronously again, and add a scripted Try Again for tests — the retry's iOS callback waited on the main queue for each update, so a main thread that was held (the app in the background) would hold the import with it; Try Again is now read from the controller's atomic flag on the import worker; HALO_TEXTURE_UPSCALE_TEST_RETRY (development builds only) presses Try Again at the given seconds after the refusal shows, for tests where no mouse can reach the window
  • ai: store only the meaningful part of an actor's secondary look direction — actor_look_secondary copied the caller's whole direction_specification, whose union bytes and padding each caller leaves as its stack had them, so actor datums differed between runs; it now zeroes the stored direction and copies the type and the one member that type uses (behavior described in OpenH1 PR #39)
  • collision: set the locations' bonus in collision_test_vector and collision_test_pill_new — both set only the leaf and cluster, so object_translate copied stack bytes into a projectile's datum location; nothing reads the field, but whole-datum hashes differed between runs (behavior described in OpenH1 PR #46)
  • metal: keep reversed depth's precision at a distance — the MSL vertex epilogue wrote w - z of the finished position, which at a distance keeps only the rounding of the two dot products that made z and w, different at every vertex; decals a millimeter or two off their surface lost whole triangles from one small camera move to the next (Blood Gulch's base emblems at a scope's zoom, about 25 units away). The translator now keeps the operands of the dot products that write the clip position's z and w and, when they share the position, takes the difference of the projection's rows first, which is exact, so clip.w - clip.z has the rounding of one small dot product; programs that write either component any other way keep w - z. The stored depth, its compares, clears, biases and read-backs are unchanged. Behavior described in OpenH1 PR #211.

docs

  • site: keep the 0.6.4 release tree's docs/site on the 0.6.5 branch — the visr-v0.6.4 tag carries main's docs/site from the hold commit, which this tree's site and Pages tooling tests do not match; Pages deploys only from main, so nothing served changes

visr-v0.6.4

visr-v0.6.4 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Oct 16:50

visr-v0.6.4 (2026-10-29)

Summary

  • Device profiles download again. 0.6.3 could quarantine every new profile update and then report "Can't reach the profile server. Benchmarking disabled." 0.6.4 reads profiles from a fresh feed, so it picks up the current set, including the new iPad Pro (M4) profile, and the benchmark recordings stop downloading over and over.
  • Texture upscaling no longer sits at 0% on the import screen. The bar now moves from the start (model load and setup count toward it), marks where Start playing appears, and offers Start playing if the work stops moving for a minute. When there is not enough free storage, or the device is too warm, the screen says so instead of waiting, and VISR tries again later.
  • Mac: the Mac app now upscales textures after import, as iPhone and iPad do, and quits when asked to from outside the app. Please try clicking Start playing on the upscaling screen.

iCloud Saves starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

feat

  • upscale: count the setup on the import bar and mark where Start playing appears — the model's load (3%) and the catalog by maps classified (7%) fill the start of the bar, so it moves before the first texture is made; the textures fill the rest by work units as each one is published, the bar never goes back when the app returns and the catalog is built again, and the label rounds down so 100% means every texture; a "ready to start" line in the bar's blue (HaloUpscaleTick) marks where the main menu's textures are all made, on iPhone, iPad, Vision Pro, the Mac (Catalyst) and Apple TV; iOS's continued processing reports the same fraction (texture_upscale_import_bar.h, tested by texture_upscale_import_bar_probe)
  • macos: upscale textures in the AppKit Mac app as on iPhone and iPad — the app now bundles the compiled S4g model, and after an import (or once for a game imported before) it shows the upscaling phase: the bar over the setup and the whole set, gray until the main menu's textures are made, the "ready to start" mark and Start playing, with the work kept from App Nap while it runs; the pre-level wait, the stop for gameplay and the cache under the data folder already run on the Mac

fix

  • data-packs: write the launch trial flag as a boolean — selectMenuFolder: stored @(!…), an integer NSNumber, so choosing the already-healthy snapshot at the launch menu left a state.json that loadState rejects; the next start then threw the store away, downloaded the snapshot again and lost its crash rollback
  • data-packs: keep one store per cache root — the upscaling check before the launch menu read the device profile through host_profile_update_folder, which ran initializeLegacy() and opened a second HostDataPackStore on every root the menu then opened again; the two each downloaded the same profile shard, recording catalog and shader lists, pruned each other's stage folders mid-validation, marked the sequence seen with nothing installed, and on the next launch the menu's store quarantined the trial the legacy store had just begun, so testers stayed on bundled profiles ("Can't reach the profile server") or looped on the catalog ("Benchmark recordings are unavailable")
  • profiles: move the profile feed to generation 3 — 0.6.4 reads device-profiles-3, so it starts on a clean cache root, away from the shard sequences 0.6.3's duplicate stores quarantined, and new shards land where 0.6.3 never looks; device-profiles-2 is retired, and 0.6.0 to 0.6.3 keep their bundled profiles when it has nothing new
  • textures: let a downloaded profile shard decide the upscaling gate again — since the look before the launch menu stopped opening a store, host_device_profile_peek read only the bundled profiles, so a shard's display.upscaled_textures (and the Settings pane's "Not used on this device") never applied; +[HostDataPackStore peekFolderAtRoot:…] now reads the shard a launch starting now would use straight from disk, writing nothing and opening no store, with a trial an earlier process left counted as rolled back
  • upscale: offer Start playing when the import phase stops moving, and end a level's wait that never gets its list — an iPhone 15 Pro Max on 0.6.3 sat at 0% with nothing counted, and the phase held Start playing until the main menu's textures were made, so a step that never finishes (the model's load, the loading picture, the catalog) held the player on the import screen; now 60 s without a finished step (TEXTURE_UPSCALE_IMPORT_STALL_SECONDS) offers it, logs the stuck step, and the work goes on behind the menus, and a level whose textures are still not listed after 30 s (TEXTURE_UPSCALE_LEVEL_CHECK_SECONDS) loads instead of holding the loading screen with nothing to skip
  • upscale: say which part is missing when the upscaler is off — the gate logged "Neural Engine unavailable" whenever texture_upscale_model_available failed, which on the AppKit Mac app (built without the compiled model) blamed an M2 Max's Neural Engine; texture_upscale_model_unavailable_reason now names a missing model, a missing Neural Engine or an OS before Core ML's device list
  • upscale: end the import phase when storage refuses the work, and count the important-usage capacity — 0.6.3 gated on the opportunistic capacity, which an iPhone 15 Pro Max on iOS 27.2 read as 0 MB with 5 GB and again with 10 GB free, and the pump, refused before it took its pending scan, ran again at once: 30,000 to 36,000 refusals per log, the phase never ended, the bar sat at 0 and Start playing never appeared; storage is now the important-usage capacity, or the volume's free blocks, less 2 GB so the work leaves that much free (texture_cache_storage_usable), with the opportunistic, important and volume values logged once per launch, and the loading picture shares the gate; a pump a gate refuses (storage, the player's switch, critical thermal state, gameplay) now ends (texture_upscale_pump_runs_again) and is tried again on the timer, the app's return or the next launch, the refusal is logged once per launch, and the import screen says "Not enough free storage to upscale textures" (or that it continues once the device cools) with Start playing; HALO_TEXTURE_UPSCALE_CAPACITY (development builds only) stands in for the reads
  • import: keep Start playing's place on the upscaling screen — the button was hidden until the main menu's textures were made, so the stack view closed its gap and VISR, the label and the bar jumped about 48 points when it appeared; it now stays laid out, unseen and disabled, until it may be used, on iPhone, iPad, Vision Pro, the Mac (Catalyst) and Apple TV
  • upscale: type the pump's ends and tighten the refusals after review — a pump now ends with an enum texture_upscale_end, and texture_upscale_end_refused decides which refusals end it, so a new refusal cannot fall back into 0.6.3's loop (texture_upscale_schedule_probe runs every refusal through a model of the pump and fails if any repeats); storage counts the smaller of the important-usage capacity and the volume's free blocks, less 2 GB, since this cache in Library/Caches is among the first the system purges; one retry timer at a time, and a pump refused at its gates no longer rewrites the generator log; the device turning critically warm mid-run ends as a thermal refusal, and the import screen says "Device is too warm to upscale textures"; a refusal shows its line in place of the bar, which keeps its place, with "VISR will try again when space is available." (or when it cools down) under it, on iPhone, iPad, the Mac (Catalyst and AppKit) and Apple TV; the import watchdog starts over when the app returns to the foreground; the loading picture's storage refusal is logged once per launch
  • macos: quit the Mac apps on SIGTERM, SIGINT and SIGHUP — SDL turns SIGINT and SIGTERM into a quit event only when its events are polled, which the import screens and the AppKit app's modal windows do not do, so kill left the app running there; dispatch sources on the main queue (host_quit_signals_install) now take all three, post the game's quit event as Command-Q does, and call host_exit two seconds later if nothing took it, on the AppKit app and Catalyst (quit_signals_probe)
  • macos: set the storage refusal's second line right under the message, in the secondary label color — it sat halfway to the button in a tertiary gray that read as disabled
  • import: make Start playing a full-size button and press it with Return — on the Mac (Catalyst) it was a small text-only button that a click did not reach, and no key pressed it; it is now a filled button in the bar's blue the size of Choose XISO, and Return or Enter presses it once it is offered (keyCommands on the import screen, as AppKit's button already had)

chore

  • upscale: log the import phase's stage and progress every 5 s — the model's load time, the catalog, and the main menu's and the whole set's textures and units, so a phase that sits at 0% shows where it waits; HALO_TEXTURE_UPSCALE_RECORDS=astc (development builds only) makes ASTC re...
Read more

visr-v0.6.3

visr-v0.6.3 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Oct 16:21

visr-v0.6.3 (2026-10-28)

Summary

  • Loading screen: with upscaled textures turned on, the loading picture should now look sharper instead of stretched. If upscaling is off, you get the original picture.
  • Audio during calls: game sound should stay quiet while a phone call, Siri, or an alarm is active. It should only come back once that interruption ends.
  • Headphones: if your headphones or AirPods disconnect, sound should reopen on the current output without waiting for a call to end.
  • Audio routing: try connecting an HDMI display, AirPlay, USB audio, or CarPlay mid-game. Sound should return on the new output instead of staying silent.
  • External display: on iOS and tvOS 27, gameplay on an external 60 Hz monitor should feel steadier, with less drift between smooth and choppy.
  • Texture cache: after you clear the upscaled texture cache, the "Upscaling textures" import screen should show again.
  • Loading chant: the fade at the end of the loading chant is restored.

iCloud Saves is new and starts off. To try it, sign in to iCloud on each device, then turn on iCloud Saves under Network Setup in the game's profile settings (on iPhone, iPad and Apple Vision Pro also on VISR's page in the Settings app). It takes effect at the next launch.

VISR automatically sends missing shader descriptions and device model information to improve loading. Reports exclude gameplay and personal file names. Apple's upload service attaches an account-specific identifier. Shader diagnostics are required in builds with shader uploads enabled.

VISR automatically sends game performance data to improve the game. Reports exclude names, chat, and personal file names. Apple's upload service attaches an account-specific identifier. Performance diagnostics are required in builds with performance reports enabled. Sharing this data is part of testing; leaving the beta stops it.

Full changelog

feat

  • upscale: draw the loading screen's picture 4x larger when the on-device upscaler made it — the 320 by 240 gray picture from maps/loading.tga was never upscaled and was stretched about 4.5x on an iPhone 17 Pro; the upscaler pump now makes it first (before the catalog is built, so before the import phase's menu textures and the first loading screen) with the same S4g model and the graphics path (pinned, no back-projection), and keeps it as loading-picture.bin in the texture cache's folder, headed by hashes of its source picture and recipe (loading_picture_upscaled.h), so the cache's size, the Settings delete and HALO_TEXTURE_UPSCALE_CLEAN cover it. progress_bar_load_loading_texture draws it, in a 1280 by 960 texture with texture coordinates scaled to cover the same area, only when the profile and the player's switch have upscaled textures on and the file is intact and made from the picture being drawn; otherwise the original, as before. Tint, layout, fade and pacing are unchanged.

fix

  • audio: end the interruption hold only on its end, and reopen after a route disconnect — becoming active during a call (Control Center, the app switcher, back from the Phone app) cleared audio_interrupted and let the stall check bring game sound back over the call, so only AVAudioSessionInterruptionTypeEnded clears it now (a flag left set only turns the stall check off); a Began with AVAudioSessionInterruptionReasonRouteDisconnected (iOS 17, headphones or AirPods dropping) no longer holds anything and instead asks for a reopen on the current route, unless a call is already holding; the decision is host_audio_interruption in host_audio_stall.h, with probe cases
  • audio: leave system interruptions and plain re-routes to iOS — the stall check from 87d791cea reopened the sound device 1.5 s into a phone call, Siri or an alarm, bringing game sound back over it or retrying every 30 s, so audio_interrupted (set on AVAudioSessionInterruptionTypeBegan, cleared on its end or UIApplicationDidBecomeActiveNotification) now holds off the stall check and debug.audio_reopen_test, and host_sdl_audio_touch restarts the 1.5 s once it clears; a route change reopens only when it involves an HDMI, AirPlay, USB audio, DisplayPort or CarPlay output, changes the rate or channel count, or follows a recovery, so headphones and Bluetooth keep the running queue iOS moves itself; the stall schedule (host_audio_stall.h) waits 0, 3, 6, 12, then 30 s as documented, with probes for all three
  • audio: bring sound back after a route change at any time — host_sdl_audio_route_changed opened each new stream before closing the old one, so SDL kept the same physical device and AudioQueue and a queue the system had stopped (an HDMI display connecting mid-game, an interruption) stayed silent through every later reopen and output pick; every stream is now closed before any reopens, so the device is recreated on the system's current route, the audio session is made active again first, a playing stream whose device has made no callback for 1.5 s gets the same reopen (backing off from 3 s to every 30 s while it stays stopped), and debug.audio_reopen_test runs it on a timer for devices that cannot be replugged
  • external: pace frames on the external display's own refreshes — pacing_start made the pacing CADisplayLink with -[UIScreen displayLinkWithTarget:selector:], which iOS 27 deprecates in favor of the window scene's link and does not document as following a scene accessory's display, so frames presented to a 60 Hz monitor could be held against another clock and drift between smooth and choppy; on iOS and tvOS 27 the link now comes from -[UIWindowScene displayLinkWithTarget:selector:] on the scene the renderer presents in (host_external_scene), with the screen's link kept for earlier systems, and the log reports the rate the link actually runs at after each output change
  • benchmarks: word the web build's missing console plainly and render the page — the committed web rows carried a reason with no plain sentence, which failed the Tooling test, and the page had not been re-rendered after the rebase
  • upscale: have the development clean hook clear the import screen's shown flag — HALO_TEXTURE_UPSCALE_CLEAN emptied the cache but left VISRUpscaleImportShown, so a cleaned device never showed the "Upscaling textures" import screen again and background upscaling refilled the cache unseen at the menu

build

  • benchmark: bind the 20-case tester catalog to the 0.6.3 guest at its own feed — docs/site/packs/tester/recordings-c2ce7f3b23742865e51ad2716fd5dd15ecfe14c68c205cea5ba5be33e3d00b89.json is the 0.6.2 catalog with its trimmed recordings, signed as sequence 1793178642 with compatibility benchmark-catalog-1:c2ce7f3b23742865e51ad2716fd5dd15ecfe14c68c205cea5ba5be33e3d00b89, which 0.6.3 builds read; the older feeds stay byte-identical, and docs/site/packs/tester/publication.json and tools/test_pages_main_assets.py name the new envelope's SHA-256
  • release: publish the signed latest-release record for 0.6.2 — docs/site/packs/tester/latest.json is sequence 1793176604 under latest-release-1 and names 0.6.2 for iOS, tvOS, visionOS and macOS; docs/site/packs/tester/publication.json names its SHA-256

revert

  • restore the loading-chant fade after the 0.6.2 cut — reverts bd6b95dd9, so main's tree is f56c5695d's again plus the visr-v0.6.2 release metadata (CHANGELOG.md, .release/current.json)

docs

  • changelog: list only the tagged work under 0.6.2 and the loading-chant fade as unreleased — the generated visr-v0.6.2 section named the fade (f56c5695d) and its hold revert, which the tagged tree does not contain; the fade now sits under Unreleased for 0.6.3, and the 0.6.2 summary is the What to Test text the builds carry
  • benchmarks: add VISR 0.6.1 Apple TV HD and Vision Pro rows for the seven cross-port windows and master-chef 1.0.3 c10 on the Vision Pro — snapshot refreshed from the public sheet, charts re-rendered
  • benchmarks: add OpenCE web build-314 on the M6 Mac mini — Campaign c10 and b30 at 2560×1920, capped by the browser's display sync
  • benchmarks: add OpenCE web build-314 on the M4 Mac mini — Campaign c10 and b30 at 2560×1920, capped by the browser's display sync, with Combat and Cutscenes marked not measured because the web build has no console