Resistance: Retribution: Add smoke trail nodes at most every 1/30s

The debris smoke trails add a node on every update, so at 60fps the smoke was twice as dense
as at 30fps and spawned twice as many trails. Keep the nodes 1/30 of the effect's clock apart
instead. In the angel boss explosion the display list now peaks at 0x483ec bytes without the
overflow guard's help, against 0x637f4 before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-15 11:42:18 +02:00
1 parent 6d9c85f366
commit 3258bc2c8e
2 files changed
+34

No files matched your search

+33
View File
@@ -1488,6 +1488,7 @@ static int Hook_resistance_draw_trail() {
// time, so the higher the frame rate, the more of them are alive. After the angel boss in Bonn the
// trails behind the debris make the display list overflow its buffer at 60fps, into the music
// stream and beyond (#19755). Locking the CPU to 222MHz, which slows the frame rate, avoids it.
// resistance_update_trail now limits the node rate, so this is a backstop.
// The game keeps its two list buffers and their limits next to the write pointer, but doesn't
// check them here. Hooked where the node count is about to become the loop count: when the
// current buffer is nearly full, skip the trail by making the count zero.
@@ -1511,6 +1512,37 @@ static int Hook_resistance_draw_trail() {
return 0;
}
static int Hook_resistance_update_trail() {
// The trail adds a node on every update that its emitter's rate allows (one per update at most,
// and the explosion debris always allow one), and the effect's clock advances 1/60 per update
// at 60fps. Keep the nodes at least 1/30 of that clock apart, as if the game ran at 30fps, so
// running faster doesn't pack the smoke denser and multiply the trails (#19755). Hooked where the
// accumulator is about to be tested: holding it under the threshold postpones the node.
const u32 trail = currentMIPS->r[MIPS_REG_S0];
if (!Memory::IsValid4AlignedRange(trail + 0x140, 0x3C)) {
return 0;
}
const u32 emitter = Memory::ReadUnchecked_U32(trail + 0x140);
const u32 nodes = Memory::ReadUnchecked_U32(trail + 0x144);
const s32 count = (s32)Memory::ReadUnchecked_U32(trail + 0x178);
if (count <= 0 || !Memory::IsValid4AlignedAddress(emitter + 8) || !Memory::IsValid4AlignedAddress(nodes + (count - 1) * 4)) {
return 0;
}
const u32 lastNode = Memory::ReadUnchecked_U32(nodes + (count - 1) * 4);
if (!Memory::IsValid4AlignedAddress(lastNode + 0x50)) {
return 0;
}
const float now = currentMIPS->f[20];
const float lastNodeTime = Memory::ReadUnchecked_Float(lastNode + 0x50);
// Updates come 1/60 apart, so this lets every other one through.
if (now - lastNodeTime < 1.0f / 30.0f - 0.001f) {
const float rate = Memory::ReadUnchecked_Float(emitter + 8);
const float acc = Memory::ReadUnchecked_Float(trail + 0x170);
Memory::WriteUnchecked_Float(std::min(acc, 1.0f - rate - 0.001f), trail + 0x170);
}
return 0;
}
static int Hook_katamari_render_check() {
if (!Memory::IsValidRange(currentMIPS->r[MIPS_REG_A0] + 0x3C, 8)) {
return 0;
@@ -1851,6 +1883,7 @@ static const ReplacementTableEntry entries[] = {
{ "everybodysgolf_spinner_background", &Hook_everybodysgolf_spinner_background, 0, REPFLAG_HOOKENTER, 0x138 },
{ "codedarms_stencil_check", &Hook_codedarms_stencil_check, 0, REPFLAG_HOOKENTER, 0x8C },
{ "resistance_draw_trail", &Hook_resistance_draw_trail, 0, REPFLAG_HOOKENTER, 0x198 },
{ "resistance_update_trail", &Hook_resistance_update_trail, 0, REPFLAG_HOOKENTER, 0x438 },
{}
};
+1
View File
@@ -377,6 +377,7 @@ static const HardHashTableEntry hardcodedHashes[] = {
{ 0xa85fe8abb88b1c6f, 52, "vector_sub_t", },
{ 0xa9194e55cc586557, 268, "memcpy", },
{ 0xa91b3d60bd75105b, 28, "vadd_t", },
{ 0xaaf1cc9399336a21, 1872, "resistance_update_trail", },
{ 0xab97ec58c58a7c75, 52, "sceVfpuVector3Div", },
{ 0xac84fa7571895c9a, 68, "memcpy", }, // Marvel Ultimate Alliance 2
{ 0xacc2c11c3ea28320, 268, "ceilf", },