Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion soh/src/code/z_actor.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,19 @@ void ActorShadow_Draw(Actor* actor, Lights* lights, PlayState* play, Gfx* dlist,
f32 temp2;
MtxF sp60;

if (actor->floorPoly != NULL) {
// https://github.com/HarbourMasters/Shipwright/issues/1953
//
// Actors that cache floorPoly pointers into the DynaPoly polyList buffer can end up holding stale references when
// DynaPoly_ExpandSRT rebuilds the buffers. The pointer address stays valid but the data at that
// address shifts to a different poly -- often a wall poly with a horizontal normal.
//
// When this happens, func_80038A28 builds the shadow matrix from the wall normal, rotating the shadow 90-degrees
// onto a vertical plane. Combined with G_CULL_BACK in sSetupDL[0x2C], the shadow flickers in and out depending on
// camera angle.
//
// Add a normal.y > 0 guard so polys that no longer face upward are rejected. This prevents rendering from stale
// wall or ceiling polys.
if (actor->floorPoly != NULL && actor->floorPoly->normal.y > 0) {
temp1 = actor->world.pos.y - actor->floorHeight;

if (temp1 >= -50.0f && temp1 < 500.0f) {
Expand Down
35 changes: 35 additions & 0 deletions soh/src/code/z_bgcheck.c
Original file line number Diff line number Diff line change
Expand Up @@ -2960,6 +2960,34 @@ void func_8003F8EC(PlayState* play, DynaCollisionContext* dyna, Actor* actor) {
/**
* DynaPolyInfo_setup
*/

// https://github.com/HarbourMasters/Shipwright/issues/1953
//
// When DYNAPOLY_INVALIDATE_LOOKUP triggers a full rebuild in DynaPoly_ExpandSRT, the polyList buffer is rewritten and
// poly indices can shift (i.e., when a BgActor is added or removed). Actors that cached a floorPoly pointer into the
// old buffer now hold stale references.
//
// Rederive floorPoly for every actor on a DynaPoly surface so their shadows render from correct data.
static void DynaPoly_RefreshActorFloorPolys(PlayState* play) {
Actor* actor = NULL;
s32 floorBgId = 0;

for (s32 category = 0; category < ACTORCAT_MAX; ++category) {
for (actor = play->actorCtx.actorLists[category].head; actor != NULL; actor = actor->next) {
if (actor->floorBgId != BGCHECK_SCENE) {
Vec3f checkPos;
checkPos.x = actor->world.pos.x;
checkPos.y = actor->world.pos.y + 50;
checkPos.z = actor->world.pos.z;
actor->floorHeight = BgCheck_EntityRaycastFloor5(play, &play->colCtx,
&actor->floorPoly, &floorBgId,
actor, &checkPos);
actor->floorBgId = floorBgId;
}
}
}
}

void DynaPoly_Setup(PlayState* play, DynaCollisionContext* dyna) {
DynaPolyActor* actor;
s32 vtxStartIndex;
Expand Down Expand Up @@ -2999,6 +3027,9 @@ void DynaPoly_Setup(PlayState* play, DynaCollisionContext* dyna) {
dyna->bitFlag |= DYNAPOLY_INVALIDATE_LOOKUP;
}
}

bool isPolyListInvalid = (dyna->bitFlag & DYNAPOLY_INVALIDATE_LOOKUP) != 0;

vtxStartIndex = 0;
polyStartIndex = 0;
for (i = 0; i < BG_ACTOR_MAX; i++) {
Expand All @@ -3007,6 +3038,10 @@ void DynaPoly_Setup(PlayState* play, DynaCollisionContext* dyna) {
}
}
dyna->bitFlag &= ~DYNAPOLY_INVALIDATE_LOOKUP;

if (isPolyListInvalid) {
DynaPoly_RefreshActorFloorPolys(play);
}
}

/**
Expand Down