Repository navigation
Releases
visr-v0.6.12
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.pyrecords 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_layermade 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 (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.logwas opened withwat every launch, so a relaunch erased the session a tester needed;host_log_file_opennow renames the last log toios-runtime.previous.log,host_log_file_linekeeps the log to 8 MB by moving a full one toios-runtime.rotated.logand continuing over the same descriptor thathost_crash.cwrites to, andplatform_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.mcovers both - mac: Command-key shortcuts and Game and View menus in the native Mac app —
host_macos_menu.madds Release Mouse (Control-Command-M), Back (Command-[) and Enter Full Screen (Control-Command-F),host_macos_shortcuts.hkeeps 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_logto stderr, which iOS does not keep, so a player'sios-runtime.logshowed neither the frame rate the display link really ran at nor what dynamic resolution did;Metal: output changedandMetal: frame pacing follows ... measurednow go throughhost_logf, anddynamic_minuteadds 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_modesmatchedpreferredModeagainstavailableModesby object, UIKit handed back a different object, and with no preferred indexhost_external_mode_chooseasked 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_preferredmatches 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_floorholds the floor at most at half the ceiling whilehost_external_active(),host_perf_hooks_dynamic_configapplies 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 newexternalactuation case cover it - external: log the refresh rate of the mode a display connects in and of a kept sub-60 Hz mode —
UIScreenModecarries no rate, so the connect line now gives the current mode'smaximumFramesPerSecondand 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 existingphysical ... 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_pollkeeps the offer clock (host_external_offer_track), so VISR's own scene connecting or choosing its mode is never flagged;host_external_display_usetakes ahost_external_display_signalsstruct, andNSProcessInfo.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_runsskips runs whoseoutput.display_useismirrored,derivesays how many it left out, andrun_tablemarks 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_displaysnow 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(inhost_external_session.h, with probe cases) calls a runin_use,mirrored,otherornone(an HDMI route with no screen listed counts as mirrored; AirPlay alone does not, since speakers route the same way),output_nowaddsdisplay_use,other_screens,mirroring_screens,video_routeandaccessory_availableto the report'soutput, and a suite or run mirrored at its start or end gets theexternal_display_mirroredflag - 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_sizeto 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_sizeto 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_probeandprofile_probe— and keep the workload-context check meaningful with a 256 variable override
ci
- release: accept the Xcode 27.2 release (
27B52) — thexcode-27runner image now carries only 27B52, sotools/xcode_select.pystopped 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 (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_samplesinpixel_key_finish— moving the key build out ofprepare_drawdropped the block that sets it for multisampled, alpha-tested, non-blended draws, which turned off MSAA alpha-test edge coverage and theshader_list_pixel_madeMSAA skip.tools/test_pixel_key_samples.pybuilds keys with the realpixel_key_finishand canonicalizer and checks the field is set, kept, and absent for single-sample, blended and sample-mask-less draws; it is inios_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)andweapon_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
23b542601started a swing at nothing in a10's opening; as on the Xbox, whereweapon_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_newno 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_swapdropped 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_turnenhancement 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_killedcharged 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_internalandgame_engine_remap_equipmentcheck_game_engine_5_or_more_players_bitagain, 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_weaponagain 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_changedcloses 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_mousenow also passes the request to SDL whenhost_ios_app_on_mac(NSProcessInfo.isiOSAppOnMac) is true, since a "Designed for iPad" run is the iOS binary and matches neitherHALO_NATIVE_MACnorTARGET_OS_MACCATALYST - mac: hold the mouse while the game is played — the iOS guest (
HALO_ILP32) compiles out everyplatform_mouse_capturecaller, soSDL_SetWindowRelativeMouseModewas never called and the cursor roamed free until it hit the screen edge and the view stopped turning; the guest'shalo_ui_pointer_updatenow asks the host to hold the mouse when no menu is up and release it when one is, andhost_sdl_set_relative_mousepasses 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 thenew_particle_dataineffects.candbreakable_surfaces.cwere filled field by field and copied whole into hashed state, leaving padding and unused members (such as an unattached particle'snode_index) as stack garbage;tools/test_zero_init_structs.pyfails 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_SIZEwas 4 KB whilehost_memory.ckeeps 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.hnow gives every consumer the tracker's page (MEMORY_WATCH_PAGE_SIZE,memory_watch_page_size()), used by the mirror,gpu_capture.candgpu_validate.c. A page that turns volatile again right after its period ran out now waits twice as long each time, up to 32 timesMIRROR_VOLATILE_FRAMES. - shaders: regenerate
port/shader-lists/*.txtwith canonical pixel shader keys — 14,254pslines (5,927 distinct keys) become 1,620 (228), 17,357 pipelines 2,451, 9.0 MB 1.1 MB, withpython3 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, butcombiner_statecopied 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.pycanonicalizes pixel shader keys when it merges, rewriting each pipeline line to the canonicalpshash, and gains acanonicalizecommand
test
- memory: report the in-window remap race beside a watched block —
memory_probe.cloopshost_guest_mmapbeside a block the renderer protects in the same 16 KB page and counts writes that did not advance the generation (diagnostic;HALO_PROBE_D_STRICTmakes it fail) - input: add
debug.test_inputunarmed:<start>— it stands still and presses melee and the grenade in turn, to check a10's opening before the first weapon - shaders: add
alias_countto theshader_liststub intest_network_shader_warmup.py—halo_shader_list_warmnow resets it, so the probe no longer compiled andios_testfailed
chore
- units: log a...
visr-v0.6.9
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_shadowsapplied a shadow that found no deferred slot through the last applied deferred shadow's atlas tile and second target, withdeferred_shadow_applyingstill 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 callsrasterizer_environment_shadows_apply_endfirst. Overflow happens whenever a frame has more shadows than the atlas holds (3 atdisplay.shadow_map_size2048, 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 inoffscreen_target_scale, with a log line for the setting, instead of being used up to 2048, anddebug.knob_cycleno 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_probeexpects the shadow map knob to stop at 512 —debug.knob_cycleno longer offers 1024
visr-v0.6.8
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_aliasingfiltered nothing in a stereo frame, becausehalo_render_scene_endlooked up the mono back buffer (HALO_STEREO_LAYER_MONO, never drawn there) andanti_aliasing_passreturned early in any stereo frame - profiles: hold a big multiplayer game to 30 frames a second —
network.full_rate_playersis the most players a device holds at its variable frame rate; above it a multiplayer map runs at 30 for the whole map, nested undernetwork.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_cycleexposes 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 anddebug.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 currentmain, as the base for Vision Pro stereo anti-aliasing - network: list a fake marked game in development builds —
HALO_BROWSER_FAKE_BIGadds oneBIGlisting to the server browser throughlobby_browser_games_read, compiled only underHALO_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
BIGin 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.pycovers both browsers, cancel, capacity 0, password order and a vanished or reordered game
fix
- hud: round the sniper reticle center —
hud_reticles_scopenow uses a tiny solid circle with the original dot’s visual weight - hud: add the sniper scope redraw —
hud_reticles_scopewas missing from the 8x set - stereo: the zoomed scope picture honors
display.anti_aliasing—render_target_get_layermakes the zoom target a multisampling screen buffer andanti_aliasing_layerreturns 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_revertreplaces the world - coop: recreate checkpoint units — unchanged datum IDs let
distributed_client_createreuse 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_multisamplenow takes the next countsupportsTextureSampleCountaccepts (4 there) and logs the substitution once - scenario: step the wind once a tick, at the end of
game_tick—scenario_frame_updatestepped 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_collisionreturned 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_pointsampled 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_indextook 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
bonusfield inscenario_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
sendtowithEISCONNon a connected UDP socket, which Linux and Winsock accept, soios_sendtofailed every gameplay datagram the game addressed after connecting - interface: start each local player's first-person
weapon_indexatNONEon a new map —first_person_weapons_initialize_for_new_mapzeroed 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_accelerateanditem_updateindexed 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_TRIANGLESheld 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_readleftengine_typeat 0, so the Game column read CO-OP while the description said Slayer - network: list the fake marked game first —
lobby_browser_games_readput 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_aliasnow swaps between the current and previous sizes - input: read the clock only when a button is held or collision times are shown —
update_hold_ticksread it for every key each frame andcollision_log_start_timeandcollision_log_end_timeread the performance counter twice for every collision test, whether or notcollision_log_timeshowed the result - menus: do not look up the last-used multiplayer map on every lobby redraw —
map_display_nameandlobby_map_showaskedui_widget_port_multiplayer_mapsfor alast_usedthey never read, which opened the saved game file each time
build
- profiles: sign generations 3 and 4 from one source —
tools/profile_pr.pysigns every published generation in one run,device-profiles-3withoutnetwork.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, andNETCODE.mdnow says units alive throughout rely on the ordinary inventory checksums - net:
p2p_lobby_checkexpects 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 afterhalo_network_version_joinabletook 40 and 41
chore
- profiles: SMAA is the Vision Pro default —
display.anti_aliasing = "smaa"invision-pro.tomlandvision-pro-m2.toml; GPU median at quality 0....
visr-v0.6.7
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 bycreate_network_game_message_padded; refusal codes 8 to 10 now show a message throughplatform_show_messageinstead 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_joiningreadGET_MESSAGE_SIZEafternetwork_game_client_writehad byte-swapped the header in place, so it logged 1216 for a 116-byte request;halo_network_join_request_wire_sizepins 116 below version 40 and 350 from 40
test
- net:
delta_probejoins 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 tiednetwork_game_client_idle_joiningto the host version
visr-v0.6.6
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_leavecompared the state afterframe_deadline_state_after_leave, which returnsFRAME_DEADLINE_LEFTfor a thread that has already left, so a secondask()with no shown frame between (Not now, or the menu again after an offscreen benchmark) calledos_workgroup_leavefor a thread that is no longer a member;frame_deadline_leave_wantedlets only the joined thread leave, andframe_deadline_probechecks 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 frameworkgroup after its first Present, and UIKit raises from_beginIntervalon such a thread (an iPad Pro M4 crashed dragging the completion screen);ask()callsgpu_metal_frame_deadline_leave, the next shown frame joins again, and a@try/@catchkeeps a UIKit exception from ending the app - rasterizer: index the fog screen's previous camera matrix by window — a local
previous_camera_matrixpointer shadowed the static array, sorasterizer_environment_fog_screen_drawwrote pastprevious_camera_matrixfor windows 2 to 4 of split screen. Same bug class as described in OpenCE PR #519.previous_camera_matrix_for_windownow 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, whichPINpasses through, so the clusters behind the portal were skipped for a frame. Same bug class as described in OpenCE PR #518. Addstools/test_portal_clip_nan.py, which clips outlines that produce the NaN. - cache: return
NONEfromtag_loadedfor a NULL name —verify_tag_referencepasses an empty reference's name straight in and_stricmpdereferenced it, crashing on seats whose HUD reference is empty (modded or merged maps). Behavior as in halo64 commit d3245ada48 (CC0-compatible). Addstools/test_tag_loaded_null.py.
visr-v0.6.5
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 playingavailable 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_secondarycopied the caller's wholedirection_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'
bonusincollision_test_vectorandcollision_test_pill_new— both set only the leaf and cluster, soobject_translatecopied 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 - zof the finished position, which at a distance keeps only the rounding of the two dot products that madezandw, 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'szandwand, when they share the position, takes the difference of the projection's rows first, which is exact, soclip.w - clip.zhas the rounding of one small dot product; programs that write either component any other way keepw - 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/siteon the 0.6.5 branch — thevisr-v0.6.4tag carries main'sdocs/sitefrom the hold commit, which this tree's site and Pages tooling tests do not match; Pages deploys only frommain, so nothing served changes
visr-v0.6.4
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 bytexture_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 integerNSNumber, so choosing the already-healthy snapshot at the launch menu left astate.jsonthatloadStaterejects; 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 raninitializeLegacy()and opened a secondHostDataPackStoreon 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-2is 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_peekread only the bundled profiles, so a shard'sdisplay.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_availablefailed, which on the AppKit Mac app (built without the compiled model) blamed an M2 Max's Neural Engine;texture_upscale_model_unavailable_reasonnow 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, andtexture_upscale_end_refuseddecides which refusals end it, so a new refusal cannot fall back into 0.6.3's loop (texture_upscale_schedule_proberuns 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
killleft 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 callhost_exittwo 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 (
keyCommandson 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...
visr-v0.6.3
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.tgawas 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 asloading-picture.binin 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 andHALO_TEXTURE_UPSCALE_CLEANcover it.progress_bar_load_loading_texturedraws 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_interruptedand let the stall check bring game sound back over the call, so onlyAVAudioSessionInterruptionTypeEndedclears it now (a flag left set only turns the stall check off); aBeganwithAVAudioSessionInterruptionReasonRouteDisconnected(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 ishost_audio_interruptioninhost_audio_stall.h, with probe cases - audio: leave system interruptions and plain re-routes to iOS — the stall check from
87d791ceareopened 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, soaudio_interrupted(set onAVAudioSessionInterruptionTypeBegan, cleared on its end orUIApplicationDidBecomeActiveNotification) now holds off the stall check anddebug.audio_reopen_test, andhost_sdl_audio_touchrestarts 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_changedopened 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), anddebug.audio_reopen_testruns it on a timer for devices that cannot be replugged - external: pace frames on the external display's own refreshes —
pacing_startmade the pacingCADisplayLinkwith-[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_CLEANemptied the cache but leftVISRUpscaleImportShown, 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.jsonis the 0.6.2 catalog with its trimmed recordings, signed as sequence 1793178642 with compatibilitybenchmark-catalog-1:c2ce7f3b23742865e51ad2716fd5dd15ecfe14c68c205cea5ba5be33e3d00b89, which 0.6.3 builds read; the older feeds stay byte-identical, anddocs/site/packs/tester/publication.jsonandtools/test_pages_main_assets.pyname the new envelope's SHA-256 - release: publish the signed latest-release record for 0.6.2 —
docs/site/packs/tester/latest.jsonis sequence 1793176604 underlatest-release-1and names 0.6.2 for iOS, tvOS, visionOS and macOS;docs/site/packs/tester/publication.jsonnames its SHA-256
revert
- restore the loading-chant fade after the 0.6.2 cut — reverts
bd6b95dd9, so main's tree isf56c5695d's again plus thevisr-v0.6.2release 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.2section 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
c10on 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