

Render Texture Main RawImage and letterboxes top and bottom. The underlay path was already there for the wider case but gated only on aspect > 1.85; added a symmetric aspect < 1.768 lower threshold (16:9 = 1.7777..., so 1.768 catches anything definitively narrower while leaving exact 16:9 untouched) so the underlay engages for narrower-than-16:9 panels too. Same path solves both directions: bind world camera RT to a full-screen RawImage on the sortOrder=0 underlay canvas, hide the game's mainImage + Background, mirror the post-FX RT onto a sibling at full-screen size. FOV bump, menu-camera narrowing, and main-menu fog tightening stay 16:9+ only — those compensate for the world edge that wider FOV reveals on the truck scene, which a 16:10 panel doesn't exposeREPOSlider's < > buttons in the graphics popup. MenuLib's OpenMenuPage writes addedPageOnTop = false unconditionally, and the game's MenuManager filter then skips RegisterHover on stock MenuButtons under that flag — slider arrows registered no hover state and clicks did nothing. Re-stamping addedPageOnTop = true after OpenPage restores the standard hover + click path. Drag still worked through the slider's own input handler, so this only affected users clicking the arrows instead of draggingShadowCastingMode.On over them, leaving the body and flashlight visible in first-person. Death cam also flashed a white frame because the mod's fog postfix was stamping fogEnd + 10 over the death camera's 70m near plane and inverting the frustum. Both fixed plus two related cleanups — full per-fix detail in the 1.5.3 entries belowSpectateCamera.Update to LateUpdate (game moved its state-machine tick), fog multiplier moved to a FogLogic postfix so room-to-room RoomFog transitions stay scaled, and RoomVolumeCheck.CheckSet replacement mirrors v0.4's new scouting-point credit + tutorial extraction reminder. Existing shadow cap, fog multiplier, and optimized room-volume check behave the same on v0.4 as they did on v0.3Render Texture Main RawImage, so the world image is squashed horizontally and the screen sides are filled with a full-screen black Background image. The mod adds a separate sortOrder=0 Canvas with a full-screen RawImage bound to the same world RT, hides the game's Background and Render Texture Main so they don't overdraw, and mirrors the post-processing pass (Render Texture Overlay) onto a sibling RawImage at full-screen size so vignette / bloom / screen flashes cover the full aspect. Game's UI canvas (HUD, buttons, menus) is left untouched at sortOrder=1 and renders on top — mods that hook those by GameObject path or component continue to find their targets unchangedCameraNoPlayerTarget.AwakeMenuLevel() + UltrawideUiFix + aspect > 16:9, with a 30-frame stabilization delay so we don't sample fog while the scene's lighting is still mid-load (would write fog × 0.60 = 0 = fog-colour screen flood). Restored when transitioning out of the menu — but never written into gameplay's RenderSettings.fog, since the main menu shares its Unity scene with gameplay and a vanilla restore would bleed menu values onto the levelUltrawideUiFix off (so the game's Background re-letterboxes the sides) and forces every camera's aspect to 16:9 (so world content renders un-squashed into the 16:9 mainImage box). Restored on next F10. Per-frame enforcement of camera aspect because game scripts can write it from Screen.aspectDisplay.main.systemWidth/Height. Resolution dropdown also reads native from Display.main instead of Screen.currentResolution so the dropdown can list the actual native even when the game is currently running below itAutoTuneNeedsInitialBenchmark checks revision + GPU but not screen size, so swapping resolutions doesn't trigger a 90-second benchmark every time. Resolution-driven staleness is silent until the user manually re-runs from the menu. Auto-benchmark is also gated on ModEnabled so an F10-disable + resolution swap can't re-poke the upscaler while it's torn down (the old "black screen after F10" failure mode)OnSettingsChanged handler wraps each refresh call in try/catch. A single bad frame in the canvas pipeline used to silently kill the FOV slider when the canvas walker hit a destroyed Graphic — now each refresh is independentbounds.size.magnitude < 3m, or 5m on Potato) was ResolvedShadowDistance × 0.7f — but Settings.ApplyFogClamps already pulls ResolvedShadowDistance down to fogEnd × 1.1, so the cull point landed at ~0.77 × fogEnd. On levels with a typical fog config (start ~50% of end), that's dead in the middle of the visible fog fade-in band — small props lost their shadows where you could still see them. Threshold now uses fogEnd × 1.1f to match the player-avatar / point-light / flashlight-budget paths in the same file (Potato pulls at fogEnd × 1.0f for the extra savings)RoomVolumeCheck.CheckSet replacement: overlap + swept-path + sticky + rest-skip. Vanilla samples Physics.OverlapBox at a single point at 10Hz, so fast movement (tumble wings, fly) can skip a whole room between ticks. The miss reads as a false "not in any room" state: breaks per-room ambience / reverb, flickers truck-safety and enemy-AI room awareness, and costs the player the scouting-points credit when the game eventually adds that. Replaced with a four-tier check — rest-skip when the player is stationary, OverlapBoxNonAlloc for the common case, BoxCastNonAlloc sweep from last position to catch seam crossings, and sticky carry-over for brief coverage gaps where the player flies above the room's collider ceiling (the sweep can't catch those because Unity's cast APIs skip the starting collider). Ungated — rest-skip makes the common case strictly cheaper than vanilla, so the patch now runs regardless of frame time. Over a 26-minute play session the old NonAlloc-only patch would have had 4022 false-empty ticks; the new patch had zero. Per-tick CPU average on a 5090 dropped from 3.26 µs (vanilla allocating path) to 0.57 µs (new authoritative path), and the Collider[] allocation on every call is goneShadowCastingMode.On instead of saving each renderer's original mode. ShadowsOnly shadow-proxy meshes got converted to visible On renderers on F10-off / auto-tune sweeps — turned latent on shipped REPO (no ShadowsOnly small MRs to hit) but manifested as a floating "second flashlight" on v0.4.0 once that build added shadow-proxy renderers on the avatar. Now each watchlisted renderer's original mode is captured into a dict at build-time and restored verbatimPlayerAvatarVisuals.ApplyLocalVisibilityBody owns shadowCastingMode on the local avatar's renderer set; its ApplyLocalVisibility early-exit gate (if (localVisibility == newVisibility) return;) means once localVisibility settles at ShadowsOnly, any outside write to those renderers persists — the game never re-cascades. Our CaptureDistanceCullWatchlist and ApplyTinyRendererCull were scanning scene-wide MeshRenderers, catching the avatar's children in their prefab-default On state before the game's first Update tick cascaded ShadowsOnly, and later stamping that On back on F10-off / preset change / auto-tune. GetComponentInParent<PlayerAvatar>() couldn't filter them out either — the renderers live under inspector-linked playerAvatarVisuals / flashlightController / playerCosmetics roots that sit OUTSIDE PlayerAvatar.transform. Now we pre-build a HashSet<Renderer> from those linked fields before each scene scan and skip anything in it; the game keeps full ownership_vanillaFarClip was a single static captured once per EnvironmentDirector.Setup, so the in-game vanilla far-clip value (~15m) was stamped onto the menu camera (vanilla far ~230m) on restore, clipping the truck out of view. Tracked per-camera via a Dictionary<Camera, float> now — each camera records its own pre-mod farClipPlane on the first write and restores from that. Cameras the mod never touched are left alone!expressionAvatar && nonExpressionCount > 1 bail in PlayerAvatarMenuAAPatch.ApplyToMenu was tripping on transient overlap: when the menu page changed, the outgoing PAM stays alive in the scene for one extra Update tick before its parentPage-null self-destruct fires (PlayerAvatarMenu.Update line 120-124 in t20). The new PAM's Start postfix ran while the dying PAM was still findable, count = 2, bail tripped, preview stayed at vanilla resolution until F10-cycle forced ReapplyAll. Filter now counts only PAMs whose parentPage is alive AND activeInHierarchy — dying PAMs (parentPage destroyed → == null) and worldAvatar / iconMakerAvatar variants (no parentPage assignment in Awake) drop out for free, so truck-lobby protection still works. Cap raised to 2 to allow a real pause+customize overlayEnvironmentDirector.FogLogic in t20 explicitly skips its own MainCamera.farClipPlane write when SpectateCamera.State.Death is active (line 248-251) — StateDeath sets nearClipPlane = 70f, farClipPlane = 90f so the camera renders the body's 70-90m orbit slice, and any further far-plane write inverts the frustum. Our PostfixFogLogic (which runs after vanilla in the same Update tick) was stamping fogEnd + 10f (e.g. 54m on a 44m-fog level) over the 70m near plane every frame — frustum became 70 → 54, no geometry rendered, screen cleared to fog color. Centralized the death-state check at UpscalerManager.SetModFarClip, the chokepoint every QualityPatch / UpscalerManager farClip write routes through. Resumes normal writes once the player respawns. Worth backporting to public — same inversion can happen on any level whose Level.FogEndDistance + 10f is less than 70mObject.FindObjectsOfType<Light>() every 100ms — same pattern as the 1.5.1 flashlight-controller scan. Cached the item-glow list on scene load alongside the other watchlists; per-tick scan gone(0,0,-2000) — far enough from Camera.main that the gate flagged them past-fog and skipped their Updates, freezing the preview's expressions / eyelids / bone poses. Surfaced as a regression at low framerate (where the cpuPatches auto-gate flips on most often). Throttle now early-bails for any transform without a PlayerAvatar in its parent chainplayerGrabbers.ToList() every frame even when nothing was actively grabbing the object — that's an empty List per instance per frame, all gen0 garbage. Added a fast-path that skips the entire Update when both playerGrabbing and listOfAllGrabbers are empty. Per-call cost dropped from ~0.6 µs to ~0.2 µsLayerMask.GetMask(new string[] { "LowPassTrigger" }) and allocating a Collider[] from OverlapSphere on every 15Hz tick. Layer mask cached statically, OverlapSphere swapped to NonAllocObject.FindObjectsOfType<FlashlightController>() every 100ms — on a 7000+ object scene that's ~9ms per tick and the source of a lot of 0.1% low spikes. Cached the controller list on scene load / player spawn; per-tick cost dropped from 0.93ms/frame amortized to 0.001ms/frame on a large map. Worst-frame times dropped ~15ms in testingperfLevel=0 for CPU-bound users where "0" meant "Ultra visual tier" in the autotune code but "don't cut anything" in the perf-opt gating — the two fields had opposite semantics. Now forces perfLevel=3 when cpuBound regardless of shadow tier, unlocking Explosion / Item / Animated / Particle / TinyRenderer shadow culling on Auto for weak-CPU usersOnSettingTweaked's "tweak → Custom" fallback. Probe now brackets sweep + restore in a preset-revert suppression counterAuto as the first sweep cell so users see autotune's exact resolved config in the current scene instead of interpolating between the discrete Potato / Low / Med / High / Ultra cellsMod-internal cost section with Stopwatch-measured spans around the mod's hot paths (per-tick shadow passes, camera hooks, LateUpdate, ApplyCAS, SceneOptimizer.Apply). Surfaces where the mod's per-frame cost actually lives — caught the flashlight-budget 9ms tick aboveautotune.json and settings.json at the tail so one clipboard paste gives full diagnostic context instead of three separate file requestswl-copy / xclip / xsel on native Linux when Unity's systemCopyBuffer silently no-ops. Log line and Done status reflect actual outcomeRenderSettings.fogStartDistance / fogEndDistance via RestoreVanillaQuality. Previously inherited the 0.3× fog from the preceding fog-matrix cell, so the vanilla sample ran at sub-vanilla fog and looked wrong on-screenspotlight.shadows — the FlashlightController.Start Harmony patch, the flashlight foreach in SetItemLightShadows, and ApplyZeroIntensityShadows catching the flashlight during the pause-menu Hidden state. Plus a duplicate unsaved zap in QualityPatch.ApplyRangeTieredLightShadows that never restored. Consolidated ownership to UpdateFlashlightShadowBudget. Potato drops the flashlight shadow entirely (it's the "cut everything" preset); every other preset keeps the 4 closestshadowDistance=150m / lightDistance=75m clamping to ~5m after a Custom-preset fog-slider session. ApplyFogClamps was reading a stale ResolvedEffectiveFogEnd left behind by the previous preset's multiplier — now recomputes from the captured vanilla baseline on every callPerfXxx setters saved to disk but never called NotifyChanged(), so PerfSettingsWatcher never saw the flipdiagnosticsEnabled in settings.json — fine for me, not fine for testersModEnabled state is restored on exit — previously hardcoded to true, which silently switched the mod back on for users who wanted it offmesh.triangles.LongLength with mesh.GetIndexCount for the scene triangle count. Game assets ship with isReadable=false so the array path threw hundreds of "Not allowed to access triangles/indices" Unity errors per probe run. GPU metadata path gives the same number without the readability requirementMod ENABLED/DISABLED, preset resolve summary, Upscaler active, restore-state diagnostics, benchmark results, probe outputdiagnosticsEnabled to true via the new menu toggle (or in settings.json), load a save, press F9 and the full probe runs (~90s), copying a report to the clipboard when it finishes. Built for sending me "here's what's going on with my machine" data when someone needs supportmodEnabled, optEnabled, cpuPatches) — should be enough for one-shot diagnosis without needing follow-up questions> 1f gate that silently ignored anything under vanilla. Presets and auto-tune stay above 0.5× ("playable floor") so dragging fog into your face stays a deliberate choicerestore-state log line prints OK or LEAK on every F10 so any regression is obviousPhysGrabObject.Update skips entirely when the Rigidbody's sleeping and the object isn't being grabbed — 40+ idle objects in a typical scene used to pay for a full Update tick each frame for nothing. Grab-list bookkeeping still runs when grabbedupdateWhenOffscreen = false on every player avatar's SkinnedMeshRenderer. Unity only skips bone matrix updates when this is false — vanilla left it true on some avatars, paying for off-screen player animations every framePlayerAvatarEyelids.Update, PlayerExpression.Update, and PlayerAvatarOverchargeVisuals.Update skip entirely when the player is past fog end. The blendshape spring math is the most expensive of the three — three players past fog used to pay for it three times a frame for visuals nobody could seerefresh × 1.05 padding was fighting the 1%-low safety the benchmark was already applying, which is how a 5090 on 240Hz ended up with LOD 2 and 8x AFAUTO-TUNE BENCHMARK (15s) in-game, AUTO-TUNE — WILL QUEUE (START A GAME) in the menu, AUTO-TUNE QUEUED (WILL RUN ON NEXT LEVEL) after queuing). Tap while queued to canceloptimizer_benchmark.txtautotune.json, separate from your settings. Other presets are never touched. Re-benchmarks on mod updates or hardware changes.