fix(render): draw look-in dynamics only at retail walk turns

This commit is contained in:
Erik 2026-08-31 03:22:01 +02:00
parent 572de1ec30
commit 966836895f
5 changed files with 84 additions and 37 deletions

View file

@ -104,13 +104,11 @@ internal interface IWalkFrameLeafRenderer
void DrawTerrainSlice(int sliceIndex);
/// <summary>One committed cell's EnvCell shell —
/// <c>PView::DrawCells</c>'s <c>DrawEnvCell</c> @0x005a4abe, which
/// precedes <c>DrawObjCellForDummies</c> @0x005a4b0d (the cell's static
/// contents, appended to the stream instead — see
/// <see cref="WalkFrameDriver"/>'s type doc comment) for every cell of
/// EVERY flood this stage drives (the ordinary interior root's own
/// <c>DrawCells</c> AND a building's look-in <c>DrawCells</c> both walk
/// this same shell-then-contents order).</summary>
/// <c>PView::DrawCells</c>'s <c>DrawEnvCell</c> @0x005a4abe. Retail first
/// draws ALL shells in reverse <c>cell_draw_list</c> order, then starts a
/// second reverse loop for <c>DrawObjCellForDummies</c> @0x005a4b0d.
/// The ordinary interior-root flood and every building look-in flood use
/// this same two-pass discipline.</summary>
void DrawCellShell(uint cellId);
/// <summary>One landscape cell's or building shell's static-owner
@ -421,16 +419,17 @@ internal readonly struct WalkFrameEvent
/// <see cref="IWalkEventSink.OnBuildingShellTurn"/> fires, so it only gets a
/// mark ahead of whatever non-stream event comes next (the next building's
/// alpha barrier, or the final mark at <see cref="Replay"/>'s prepare step).
/// This single rule, combined with "shell before contents" per cell,
/// This single rule, combined with retail's two reverse flood passes (ALL
/// shells, then ALL contents),
/// retail's own building order (alpha barrier → portal pass → shell — see
/// <see cref="RetailFrameWalk.DrawBuilding"/>'s doc comment), and retail's
/// own interior-root DRAW order (landscape → clear → seals → the flood's own
/// cells — see <see cref="IWalkEventSink.OnInteriorFloodDrawTurn"/>'s doc
/// comment; this is NOT the order the walk's EVENTS fire in, which is
/// breakpoint-entry order matching the FW0 oracle traces), is what produces
/// every ordering constraint the plan's frame script names: [cell1 shell]
/// [cell1 contents mark] [cell2 shell] …, [alpha barrier] [punch fan(s) +
/// look-in flood(s), each following the SAME shell-then-contents per-cell
/// every ordering constraint the plan's frame script names: [far shell] …
/// [near shell] [far contents] … [near contents], [alpha barrier] [punch
/// fan(s) + look-in flood(s), each following the SAME reverse two-pass
/// discipline] [building shell content mark], [landscape (if exit views
/// survived)] [interior depth clear] [exit-portal seals] [the interior
/// root's own flood cells], and a final mark at Replay's prepare step. No
@ -440,8 +439,9 @@ internal readonly struct WalkFrameEvent
/// root <see cref="RetailFrameWalk.WalkFrame"/> already ports),
/// <c>RenderDeviceD3D::DrawSortCell</c> @0x0059f140 (building-before-
/// DrawObjCell per landscape cell), <c>PView::DrawCells</c> @0x005a4840
/// (<c>DrawEnvCell</c> @0x005a4abe before <c>DrawObjCellForDummies</c>
/// @0x005a4b0d per flooded cell; <c>LScape::draw</c> pc:432719, the depth
/// (the complete reverse <c>DrawEnvCell</c> loop @0x005a4a000x005a4ade
/// before the complete reverse <c>DrawObjCellForDummies</c> loop
/// @0x005a4ade0x005a4b2d; <c>LScape::draw</c> pc:432719, the depth
/// clear pc:432731-432732, and the exit-portal seals pc:432785-432786 —
/// ALL strictly before the flood's own cells, though the event marking
/// <c>DrawCells</c> entry fires before all three), <c>RenderDeviceD3D::DrawBuilding</c>
@ -878,8 +878,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
for (int i = 0; i < cells.Count; i++)
InteriorFloodCells.Add(cells[i]);
for (int i = 0; i < cells.Count; i++)
EmitCellTurn(WalkDrawStage.CellStatic, cells[i]);
EmitFloodTurns(WalkDrawStage.CellStatic, cells);
}
// ------------------------------------------------------------------
@ -949,19 +948,36 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
// Any other stage (LookInStatic) is a building's look-in flood:
// retail calls DrawCells re-entrantly there with no landscape/clear/
// seal step, so its DC event already fires at the real draw point —
// record immediately, unchanged from before this correction.
for (int i = 0; i < cells.Count; i++)
EmitCellTurn(stage, cells[i]);
// record immediately. PView::DrawCells uses two complete reverse
// loops: every EnvCell shell first, then every cell object list.
EmitFloodTurns(stage, cells);
}
private void EmitCellTurn(WalkDrawStage stage, uint cellId)
private void EmitFloodTurns(WalkDrawStage stage, IReadOnlyList<uint> cells)
{
// PView::DrawCells @0x005A4840, loop 2 (005A4A00005A4ADE):
// cell_draw_list[count - 1] down to zero, DrawEnvCell only.
for (int i = cells.Count - 1; i >= 0; i--)
{
MarkIfGrown();
_events.Add(WalkFrameEvent.CellShell(cells[i]));
}
// Loop 3 (005A4ADE005A4B2D): restart at count - 1 and draw each
// complete object cell. Keep a stream mark per cell so the packed
// static records and its dynamic/particle tail stay at that exact
// retail turn.
for (int i = cells.Count - 1; i >= 0; i--)
EmitCellContentsTurn(stage, cells[i]);
}
private void EmitCellContentsTurn(WalkDrawStage stage, uint cellId)
{
MarkIfGrown();
_events.Add(WalkFrameEvent.CellShell(cellId));
WalkFrameStaticRecords records = _worldData.GetCellStatics(cellId);
_populator.PopulateCell(
_stream, stage, cellId, records.Records, records.TupleLandblockId,
_cameraWorldPosition, _viewProjection);
MarkIfGrown();
if (stage == WalkDrawStage.LookInStatic)
{
// Retail draws a look-in cell's complete object list at this
@ -970,7 +986,6 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
// shells, which let the cathedral's 0x112 remote player overpaint
// opaque walls. Keep animation/fade in the packed route, but replay
// it here between this cell's content and the building shell.
MarkIfGrown();
int routeIndex = _lookInRouteIndex++;
LookInCellTurns.Add(cellId);
LookInCells.Add(cellId);