acdream/tests/AcDream.App.Tests/Rendering/Wb/WbDrawDispatcherTorchGateTests.cs
Erik b7d655bce7 fix(lighting): A7 Fix D round 2 — outdoor objects get NO torches (retail useSunlight gate) (#140)
The Holtburg meeting-hall facade washed out warm/bright vs retail. The round-1
checkpoint blamed torch REACH (acdream Falloff 6×1.3=7.8m vs a supposed retail
Falloff 4). That theory is WRONG, and this commit fixes the real cause.

Empirical (HoltburgTorchFalloffProbeTests, headless dat dump via the production
LightInfoLoader): the orange entrance torch (setup 0x020005D8) is raw dat
Falloff 6 and acdream reads it FAITHFULLY — there is no Falloff-4 torch anywhere
in Holtburg. Both clients read the same dat float, so reach was never inflated.

Decomp (read verbatim + corroborated by an independent adversarial workflow):
retail's per-object torch binder minimize_object_lighting (0x0054d480) is gated
in RenderDeviceD3D::DrawMeshInternal (0x0059f398) by `if (Render::useSunlight == 0)`.
The outdoor landscape stage runs useSunlightSet(1) (PView::DrawCells 0x005a485a,
before LScape::draw), so the building EXTERIOR shell — drawn via
DrawBlock→DrawSortCell→DrawBuilding→CPhysicsPart::Draw→DrawMeshInternal — is lit
by SUN + ambient ONLY; torches are SKIPPED. The static bake
(SetStaticLightingVertexColors 0x0059cfe0) is EnvCell-only. So retail NEVER
torch-lights outdoor objects. This exactly explains the isolation test (object
point lights OFF → building matches retail).

Fix: WbDrawDispatcher.ComputeEntityLightSet gates per-object torch selection on
the object being INDOOR (ParentCellId is an EnvCell, (id&0xFFFF)>=0x0100) via the
pure predicate IndoorObjectReceivesTorches. Outdoor objects (building shells with
null ParentCellId, outdoor scenery, outdoor creatures) keep the all-(-1) light
set ⇒ sun + ambient only = retail. The indoor "no sun" half is already handled by
the global sun-kill when the player is inside a cell (UpdateSunFromSky). No
dungeon regression: EnvCell statics get ParentCellId set (keep torches).

Divergence register: AP-37 (residual: acdream keys sun/torch on the object's own
cell + a per-frame player-inside sun-kill, vs retail's per-draw-stage useSunlight;
only matters for through-doorway look-ins). The round-1 CHECKPOINT got a RESOLVED
banner correcting the reach theory.

Tests: WbDrawDispatcherTorchGateTests (7), HoltburgTorchFalloffProbeTests (dat
dump). App 280/1skip, Core 1486/2skip green. Held at the visual gate — not merged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 23:56:49 +02:00

42 lines
1.7 KiB
C#

using AcDream.App.Rendering.Wb;
using Xunit;
namespace AcDream.App.Tests.Rendering.Wb;
/// <summary>
/// A7 Fix D round 2 — pins retail's <c>useSunlight</c> gate for per-object torch
/// lighting (<c>WbDrawDispatcher.IndoorObjectReceivesTorches</c>). Retail enables
/// the static wall-torches on an object ONLY in the indoor stage
/// (<c>DrawMeshInternal</c> 0x0059f398: <c>if (useSunlight == 0) minimize_object_lighting()</c>),
/// so OUTDOOR objects — building exterior shells (null ParentCellId) and outdoor
/// scenery (land sub-cell 0x0001..0x00FF) — get the sun, never torches. Only
/// EnvCell-parented (indoor, low word &gt;= 0x0100) objects receive torches.
/// </summary>
public sealed class WbDrawDispatcherTorchGateTests
{
[Fact]
public void BuildingShell_NullParent_IsOutdoor_NoTorches()
{
// Building exterior shells are top-level landblock stabs with no
// ParentCellId (LandblockLoader sets BuildingShellAnchorCellId, not Parent).
Assert.False(WbDrawDispatcher.IndoorObjectReceivesTorches(null));
}
[Theory]
[InlineData(0xA9B4_0001u)] // outdoor land sub-cell
[InlineData(0xA9B4_0020u)] // outdoor land sub-cell
[InlineData(0xA9B4_0040u)] // last outdoor land sub-cell (0x40)
public void OutdoorLandCell_NoTorches(uint parentCellId)
{
Assert.False(WbDrawDispatcher.IndoorObjectReceivesTorches(parentCellId));
}
[Theory]
[InlineData(0xA9B4_0100u)] // first EnvCell
[InlineData(0xA9B4_0164u)] // interior EnvCell
[InlineData(0x0007_0143u)] // dungeon EnvCell
public void IndoorEnvCell_GetsTorches(uint parentCellId)
{
Assert.True(WbDrawDispatcher.IndoorObjectReceivesTorches(parentCellId));
}
}