Skip to content

Fix Evo's visor, camera shaking, overscan drawing, black TV screens, terminal trails, dome reflections; render to RDRAM only when needed - #7

Open
filipverlee wants to merge 7 commits into
Cellenseres:mainfrom
filipverlee:fix/rig-visor-and-overscan-scissor
Open

filipverlee wants to merge 7 commits into
Cellenseres:mainfrom
filipverlee:fix/rig-visor-and-overscan-scissor

Conversation

@filipverlee

@filipverlee filipverlee commented Oct 20, 2026 •

Copy link
Copy Markdown

Three fixes and one performance change, compared against the original game in Mupen64Plus-Next.

Evo's visor floating above his head (modelview_stack_patches.c)

In the new-game cockpit cutscene, Evo's red visor floats a head-height above him (and a small piece floats above Dan). render_rig_instance_xlu pushes joint 1 a second time before joint 3, so rig part 9 gets the joint 1 offset twice. For Evo in that scene, joint 1 is about (4, 0, 144) and joint 3 about (-4, 0, 80), which puts the visor around z=368 instead of z=224, next to the head (part 8, at joint 1 + joint 2).

The fix pushes joint 3 from the joint 1 level that the previous pop already restored, and drops one pop so the stack stays balanced. The matrix stack depth when part 9 is drawn goes from 5 to 4, and the caller's depth is unchanged. The visor now sits on Evo's face, as on the original.

3D geometry drawing into the 8px border in 4:3 (graphics_patches.c)

  • draw_rectangle left the scissor at full screen after drawing. The original restores 8, 8, w - 8, h - 8, so geometry drawn after any rectangle spilled into the overscan border.
  • draw_intro_logo_model always used the full frame, so the title-screen ship flew over the border. The original uses 8, 8, 312, 232.

Both now call set_default_frame_scissor, which gives the TV-safe rect in 4:3 and the full frame in widescreen. Widescreen keeps its previous behaviour.

Camera shaking in levels (lod_patches.c, sssv_patch_types.h)

With SSSV_PATCH_DISABLE_LOD / SSSV_PATCH_DISABLE_FAR_AWAY_CULL on, both visibility classifiers return VISIBILITY_VISIBLE for everything in a loaded cell. The game acts on those results beyond drawing, and the camera never settles. I logged the camera eye (gCameraEyeWorldX/Z) once per frame at the level 1 start. At rest it cycles +1.5, +1.6, +1.8, −3.9 every 6–7 frames, which gave 139 direction reversals in 900 frames. While moving, this shows up as large camera jumps. In Mupen64Plus (camera RAM read through RetroArch's READ_CORE_RAM), the camera is completely still at the same spot.

The fix splits the two overrides:

  • DISABLE_LOD now only forces gLodDetailState = 0 for objects the game itself classified as visible, so the extra detail stays.
  • DISABLE_FAR_AWAY_CULL keeps its old behaviour, but is now off by default.

At the same spot afterwards: 0 reversals, and the camera stays at rest, also after walking and turning.

The same override also caused the black levels from #3 (only bubbles, stars and the HUD visible). It showed up with aspect ratio Expand. Sting in the Tail is black from the start with the v0.2.0 lod_patches.c and renders normally with this change. Something Fishy stays visible while swimming to the monitor and over the wall. Details: #3 (comment)

Fixes #3

Performance: only render to RDRAM when the game reads the frame back (graphics_patches.c)

RT64 writes every frame back to RDRAM by default, and that readback scales with the output resolution. At 2x on an Intel Iris Plus 640, the gameplay attract demo ran at 24.9 fps, with 357 of 870 frames slower than 40 ms. That shows up as uneven frame pacing and a juddering camera.

The game reads rendered frames back in only two places:

  • the title/attract screen grab for the transition into the game (func_80298F1C);
  • the previous frame copied into the pause-menu thumbnail (func_803925D0 while gInitialisationState counts 1–3).

The patch emits gEXSetRenderToRAM(0) for gameplay frames, except while the pause menu is being entered, and gEXSetRenderToRAM(1) for intro/title frames. On the same demo afterwards: 29.8 fps, with 87 frames over 40 ms. That's the same as switching render-to-RAM off globally. I checked the title transition (tile break-up) and the pause thumbnail on screen.

Black TV screens in Something Fishy (graphics_patches.c)

On the original, the wall monitors in Something Fishy show TV static. In the recomp they're black. The TV screen model (D_01017AA0) draws a black quad and then a white quad with prim alpha 0x80 and G_AC_DITHER (from the low bits of its SetOtherMode_L render-mode word), in 2-cycle mode with G_CC_PASS2, G_CC_PRIMITIVE. The alpha compare uses the first cycle's alpha, which is COMBINED there. On hardware that still holds the previous pixel's output (about 0x80), so about half the pixels pass the dither compare at random. RT64 starts COMBINED at 0, so every pixel fails and only the black quad is left.

load_segments now rewrites that one combine command in the loaded model to G_CC_PRIMITIVE, G_CC_PRIMITIVE. It only does this when segment 1 holds the TV model: the command must match the original and must follow the dithered render mode. Afterwards the monitors show animated static (about half the pixels white, most of them changing every frame), like the original in Mupen64Plus-Next.

Missing trails on the species terminal screen (overlay_context_patches.c)

On the species/stats terminal (touching a TV screen, or the new-animal cutscene), the original leaves fading trails behind the scrolling glyph text, the DNA helix and the animal. That's the "noise" from #4. In phase 1, render_terminal_background_frame cleared the frame to opaque black every frame. The original only draws its translucent black rectangle (alpha 100) over the previous frame there. The clear now only happens in widescreen. Without it, a native-width copy of the stat text stays visible next to the widened layout. In 4:3 (Original and 2x) the screen now looks like the original in Mupen64Plus-Next. Widescreen is unchanged.

Reflections in the dome glass (ship_window_patches.c, lod_patches.c, graphics_patches.c)

In Snow Joke the glass walls at the edge of the dome reflect the player (#4). Despite its name, render_ship_window_projection_replay is how the game draws this. Near a reflecting wall, the camera code sets gCameraVisibilityMask[6] (bit 0: z mirror, bit 1: x mirror, plus the plane offset). The game then replays the frame's opaque and translucent object lists under a mirror matrix multiplied into the projection, before the world geometry is drawn, so the mirrored objects only show through the translucent glass. The classifiers also count an object as visible when its mirror image is. Commit 108d986 had replaced the replay with an empty function and removed those visibility checks.

Both are restored as in the decomp. The mirror matrices come from the existing mtx_patches.c builders, which give the same values as the original. One adjustment: the translucent object list now starts with a projection reload (SSSV_PATCH_SHIP_WINDOW_XLU_STATE_BARRIER), which would drop the mirror matrix. So setup_world_perspective_6AB090 records where that state ends, and the replay runs the list from there and loads the world modelview itself.

Checked at the reporter's spot in Snow Joke (start, go right to the glass), in 4:3 and Expand, against Mupen64Plus-Next with ParaLLEl-RDP and with GlideN64 (both show the reflection). Level 1 and Something Fishy look unchanged. Not checked: the x-mirror case (I don't know a wall that uses it) and frame interpolation above 30 fps with the replayed objects.

Testing

  • Linux (Ubuntu, Mesa Intel Vulkan), built following validate.yml (patches built with libdragon's mips64-elf-gcc), US 1.0 ROM.
  • Intro, title screen, attract demos and the new-game cockpit cutscene in 4:3, plus the intro and level 1 in Expand. Each was compared against Mupen64Plus-Next.
  • Camera: per-frame camera logs at the level 1 start, standing still and walking/turning, before and after the change, against Mupen64Plus RAM reads. The far-away cull override is only switched off, not removed, so distant objects are culled as on the original again.
  • Something Fishy and Sting in the Tail, loaded through a temporary test patch (not included) that swaps the level loaded for Smashing Start, in 4:3 and Expand. The species terminal was opened through a temporary test patch that sets the new-animal countdown (D_803F2AA3), in 4:3 at Original/2x and in Expand, and compared against Mupen64Plus-Next with the same RAM write.
  • Not covered: the pause menu and zone-select Dan TV paths that also use render_rig_instance_xlu (pose banks 0 and 2) were not checked on screen.

🤖 Generated with Claude Code

https://claude.ai/code/session_01421MLZmp5pdkDs5MnD9xmK

filipverlee and others added 2 commits October 20, 2026 18:52
render_rig_instance_xlu pushed joint 1 a second time before joint 3, so
rig part 9 (Evo's red visor, and Dan's part 9) got the joint 1 offset
twice and floated a head-height above the model in the intro cockpit.
Push joint 3 from the joint 1 level restored by the preceding pop and
drop the now-unneeded pop, keeping the stack balanced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01421MLZmp5pdkDs5MnD9xmK
draw_rectangle left the scissor at full screen and draw_intro_logo_model
always used the full frame, so in 4:3 the 3D geometry drawn afterwards
(and the title-screen ship) spilled into the 8px overscan border. The
original restores 8, 8, w - 8, h - 8; use set_default_frame_scissor,
which keeps the full frame in widescreen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01421MLZmp5pdkDs5MnD9xmK
With SSSV_PATCH_DISABLE_LOD/DISABLE_FAR_AWAY_CULL the visibility
classifiers returned VISIBILITY_VISIBLE for everything in a loaded cell.
The game acts on those results beyond rendering, and the camera never
settles: at rest it cycles +1.5, +1.6, +1.8, -3.9 every 6-7 frames
(139 direction reversals in 900 frames at the level 1 start), which
shows as big camera jumps while moving. On the original (Mupen64Plus)
the camera is still at rest.

Split the overrides: DISABLE_LOD now only forces gLodDetailState = 0 for
objects the game itself classified as visible, and the far-away cull
override (unchanged behaviour) is off by default. Same spot after the
change: 0 reversals, camera at rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01421MLZmp5pdkDs5MnD9xmK
@filipverlee filipverlee changed the title Fix Evo's floating visor and 3D drawing into the overscan border Fix Evo's floating visor, camera shaking, and 3D drawing into the overscan border Oct 20, 2026
RT64 writes every frame back to RDRAM by default. That readback scales
with the output resolution: at 2x on an Intel Iris Plus 640 the
gameplay attract demo ran at 24.9 fps with 357 of 870 frames over 40 ms.
The game only reads rendered frames in two places: the title/attract
screen grab for the transition into the game, and the previous frame
copied into the pause-menu thumbnail (gInitialisationState 1-3).

Emit gEXSetRenderToRAM(0) for gameplay frames, except while the pause
menu is being entered, and gEXSetRenderToRAM(1) for intro/title frames.
Same demo afterwards: 29.8 fps, 87 frames over 40 ms (same as turning
render-to-RAM off globally). Title transition and pause thumbnail
checked on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01421MLZmp5pdkDs5MnD9xmK
@filipverlee filipverlee changed the title Fix Evo's floating visor, camera shaking, and 3D drawing into the overscan border Fix Evo's floating visor, camera shaking, overscan drawing; render to RDRAM only when needed Oct 20, 2026
The TV screen model draws its static as a white quad with prim alpha 0x80
and G_AC_DITHER, in 2-cycle mode with G_CC_PASS2 / G_CC_PRIMITIVE. The
alpha compare uses the first cycle's alpha, which is COMBINED there. On
hardware that still holds the previous pixel's output, so about half the
pixels pass. RT64 starts COMBINED at 0, every pixel fails and the
monitors in Something Fishy stay black.

Rewrite that combine command in the loaded model to use PRIMITIVE in
both cycles, only when segment 1 holds the TV model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@filipverlee filipverlee changed the title Fix Evo's floating visor, camera shaking, overscan drawing; render to RDRAM only when needed Fix Evo's floating visor, camera shaking, overscan drawing, black TV screens; render to RDRAM only when needed Oct 21, 2026
@Cellenseres

Copy link
Copy Markdown
Owner

First of all, thank you so much for working on this!
I barely had time to continue working on SSSV in the last months but I'll definitely try out your fixes!
If everything works, I'll approve your PR!

@filipverlee

Copy link
Copy Markdown
Author

First of all, thank you so much for working on this! I barely had time to continue working on SSSV in the last months but I'll definitely try out your fixes! If everything works, I'll approve your PR!

I try to get it running on a Linux box for myself and the kiddos ;) and it would be stupid to not share my results. So hopefully everyone can enjoy the results.

render_terminal_background_frame cleared the frame to opaque black every
frame in phase 1. The original only draws a translucent black rectangle
over the previous frame there, so the glyph text, DNA and animal leave
fading trails behind them, which is the noise effect on the stats screen.

Only clear in widescreen, where the uncleared frame also keeps a
native-width copy of the stat text visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@filipverlee filipverlee changed the title Fix Evo's floating visor, camera shaking, overscan drawing, black TV screens; render to RDRAM only when needed Fix Evo's floating visor, camera shaking, overscan drawing, black TV screens, terminal trails; render to RDRAM only when needed Oct 21, 2026
render_ship_window_projection_replay is how the game draws reflections in
the dome glass (e.g. the glass walls in Snow Joke): near a reflecting wall
it sets gCameraVisibilityMask[6] and replays the frame's object lists under
a mirror matrix multiplied into the projection, before the world geometry,
so the mirrored objects show through the translucent glass. The recomp had
replaced it with an empty function and removed the matching mirrored
visibility checks.

Restore both. The translucent object list now starts with a projection
reload (the XLU state barrier), which would drop the mirror matrix, so the
replay runs that list from the end of the barrier and loads the world
modelview itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@filipverlee filipverlee changed the title Fix Evo's floating visor, camera shaking, overscan drawing, black TV screens, terminal trails; render to RDRAM only when needed Fix Evo's visor, camera shaking, overscan drawing, black TV screens, terminal trails, dome reflections; render to RDRAM only when needed Oct 21, 2026

This branch was successfully deployed

1 active deployment
external — 059e5a2e Deployed Oct 21, 2026 by filipverlee via authorize #6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Something fishy level depth/frame/fog buffer issue?

2 participants