Author SHA1 Message Date
CydandClaude Opus 4.8 d0e29a4d97 radar: composite the OVERLAY plane -- SECTOR/SCALE were blank on every desktop path
Playtester DOS-binary comparison: our radar showed blank SECTOR:/SCALE: values.
Root cause [T1]: the secondary CRT's low byte is authored as TWO ports
(L4GAUGE.CFG:4395-4396) -- sec (0x3F, btspal) + overlay (0xC0, btopal,
TransparentZero) carrying the btsec1ov graticule, the SCALE numeral
(RadarRange) and the SECTOR numerals (sectorDisplay).  Both numeral writers
are reconstructed and RUN -- their pixels were in the shared buffer's 0xC0
bits all along -- but every desktop expand indexed the LUT with pixel & 0x3F,
stripping them.  The arcade DAC indexed the FULL low byte.

Key mechanism: clut0 ALREADY holds the DAC's combined 256-entry table --
BuildSecondaryPalette (AllChannels, sec) fills all 256 indices and the
overlay's TransparentZero pass overwrites groups 0x40/0x80/0xC0 -- so the
composite is exactly a mask change, no blending code:

  - BTSecCompositeMask(): sec->GetBitMask() | overlay's (NULL-guarded, same
    display), used by BTDrawGaugeSurfaces + BTDrawCockpitPanels (surround),
    SVGA16::Update case 0 (pod parity), and the glass BlitSurface palette
    expand.
  - GlassWindowToken: the palette-expanded radar window's dirty token now
    includes the overlay port -- the SECTOR numerals tick on that plane, and
    a pixel-only 0x3F token would never repaint them.

The overlay's ColorMapper re-stomp over combined indexes is authentic (the
shipped machine ran the identical palette code) and is left alone.

Verified live (glass panels): SECTOR: 497.503 ticking with movement,
SCALE: 1000 at spawn (radarRange init 250*2^2 -- the DOS reference's 4000 is
max zoom, not a delta), and the btsec1ov graticule rules now draw.  KB:
GAUGE_COMPOSITE row 28 + polish list updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-13 07:18:43 -05:00
6 changed files with 69 additions and 87 deletions
+4 -5
View File
@@ -217,7 +217,7 @@ yet reconstructed (parse-skipped). The separate window is now the **live viewer*
- **Pod MFD port-name reconcile (pod-only):** a ~5-line positional dual-name fallback in `SVGA16::Update` - **Pod MFD port-name reconcile (pod-only):** a ~5-line positional dual-name fallback in `SVGA16::Update`
(`UL = GetGraphicsPort("auxUL2"); if(!UL) UL = GetGraphicsPort("Heat"); …`) so BT MFDs reach the pod's real (`UL = GetGraphicsPort("auxUL2"); if(!UL) UL = GetGraphicsPort("Heat"); …`) so BT MFDs reach the pod's real
monitors. NOT a CFG rename (the CFG uses BT names pervasively + `MUNGA_L4` is shared with RP's aux names). monitors. NOT a CFG rename (the CFG uses BT names pervasively + `MUNGA_L4` is shared with RP's aux names).
- **Polish:** overlay-plane compositing over `sec`; a pod-accurate RGB-packing toggle; texture atlas - **Polish:** ~~overlay-plane compositing over `sec`~~ (DONE 2026-08-13, row 28); a pod-accurate RGB-packing toggle; texture atlas
(one lock/upload); device-reset recreate hardening for the additional swap chain. (one lock/upload); device-reset recreate hardening for the additional swap chain.
## Widget reconstruction — the real fix (in progress; mapped by `bt-gauge-widget-recon-map`) ## Widget reconstruction — the real fix (in progress; mapped by `bt-gauge-widget-recon-map`)
@@ -629,8 +629,7 @@ attributes published on Emitter+Myomers — see the AUDIT finding A addendum bel
Byte-exact re-base of the PoweredSubsystem:HeatSink LEAF (Sensor/Emitter/Myomers) → radarPercent heat penalty Byte-exact re-base of the PoweredSubsystem:HeatSink LEAF (Sensor/Emitter/Myomers) → radarPercent heat penalty
(delete the sensor.cpp:287-288 guard — ✅ LANDED: the guard is deleted and the leaf is byte-exact, (delete the sensor.cpp:287-288 guard — ✅ LANDED: the guard is deleted and the leaf is byte-exact,
sensor.cpp:407-413) + the raw-offset cluster reads (partially locked since — MechWeapon byte-exact); sensor.cpp:407-413) + the raw-offset cluster reads (partially locked since — MechWeapon byte-exact);
radar pip/name symbology + staticEntities (task #17: pips landed the real L4GaugeImage path; radar pip/name symbology + staticEntities (task #17, under reconstruction 2026-07-12);
✅ staticEntities RESOLVED 2026-08-13, branch radar-static-geometry — see row 24);
CycleDisplayMode→ModeManager mask (✅ RESOLVED 2026-07-19, Gitea #6 [T2]: @4d1ae4 relabeled the CycleDisplayMode→ModeManager mask (✅ RESOLVED 2026-07-19, Gitea #6 [T2]: @4d1ae4 relabeled the
NotifyOfDisplayModeChange override (vtbl+0x4C) and made virtual; desktop 'N'/pad RightThumb → NotifyOfDisplayModeChange override (vtbl+0x4C) and made virtual; desktop 'N'/pad RightThumb →
CycleDisplayModeNow; secondary schematic swap dama→crit→heat pixel-verified via CycleDisplayModeNow; secondary schematic swap dama→crit→heat pixel-verified via
@@ -688,11 +687,11 @@ reconfigure/externalConfigure); parse-skip list EMPTY ([gskip]=0), all 50 attr b
| 21 | Eng myomer page: seek-step lamp | Myomers@0x320 INFERRED (ctor @004c8df4 not in the decomp export) | marked in code | T3 | CORRECT (best-effort; flagged) | | 21 | Eng myomer page: seek-step lamp | Myomers@0x320 INFERRED (ctor @004c8df4 not in the decomp export) | marked in code | T3 | CORRECT (best-effort; flagged) |
| 22 | Eng page: linked heat-sink number (hs+0x1d4 guarded raw) | PrepEngr numeric #2 | marked BEST-EFFORT (guarded) | T3 | CORRECT (guarded; flagged) | | 22 | Eng page: linked heat-sink number (hs+0x1d4 guarded raw) | PrepEngr numeric #2 | marked BEST-EFFORT (guarded) | T3 | CORRECT (guarded; flagged) |
| 23 | sec: radar map (wedge/blips/scale) | Mech Radar* attrs; wedge ← Torso::currentTwist@0x1D8 (Gitea #1) | #1 [T2]; LIVE: blips + wedge render, zoom via SetTargetRange | T2 | CORRECT | | 23 | sec: radar map (wedge/blips/scale) | Mech Radar* attrs; wedge ← Torso::currentTwist@0x1D8 (Gitea #1) | #1 [T2]; LIVE: blips + wedge render, zoom via SetTargetRange | T2 | CORRECT |
| 24 | sec: radar pip/name symbology + MAP GEOMETRY | GetVideoObject/LookUpPip/GetNameID; MapDisplay::DrawStatic ← staticEntities | pips: the real L4GaugeImage path landed (task #17). **Map geometry RESOLVED 2026-08-13** (branch radar-static-geometry, playtester DOS comparison): DrawStatic was always faithful but staticEntities was EMPTY — (1) the per-frame RebuildEntityGrid Clear()+dynamic-only refill structurally excluded the 528 static-flagged UnscalableTerrain placements (arenall: 59 static vs 3 dynamic), and (2) the notify gate was transcribed as Terrain, which matches ZERO shipped records (1008 UnscalableTerrain + 789 CulturalIcon derive from Entity — @0x4e6ef8 was almost certainly UnscalableTerrain's derivation). Fix: staticEntities is now persistent, filled once per mission (AllEntityIterator, UnscalableTerrain-derived + rtype-18 pip; `[radar] static grid filled: N` under BT_RADAR_LOG), reset at LinkToEntity, pruned by the gate-corrected remove notify. Verified live: 326 entities filled, arena outlines draw | T2 | CORRECT | | 24 | sec: radar pip/name symbology | GetVideoObject/LookUpPip/GetNameID | task #17 under reconstruction — cross-blip stand-in | T3 | DEFERRED-FEED |
| 25 | sec: headingPointer needle + numeric | YawPitchRoll decomposition (engine-convention port of euler[0]) | LIVE: 089→120 while turning under BT_GOTO | T2 | CORRECT | | 25 | sec: headingPointer needle + numeric | YawPitchRoll decomposition (engine-convention port of euler[0]) | LIVE: 089→120 while turning under BT_GOTO | T2 | CORRECT |
| 26 | sec: numericSpeed | LinearSpeed (abs(adv)/dt) | LIVE: 0 parked → 176-182 walking | T2 | CORRECT (units question stays open) | | 26 | sec: numericSpeed | LinearSpeed (abs(adv)/dt) | LIVE: 0 parked → 176-182 walking | T2 | CORRECT (units question stays open) |
| 27 | sec: digitalClock MISSION TIME | engine mission clock (format enum arg) | LIVE: 09:19→06:04 counting | T0/T2 | CORRECT | | 27 | sec: digitalClock MISSION TIME | engine mission clock (format enum arg) | LIVE: 09:19→06:04 counting | T0/T2 | CORRECT |
| 28 | sec: sectorDisplay (overlay) | localOrigin → Round(Z·0.01)+500 / Round(X·0.01)+500 | commit 4a4ec68 [BT_SECTOR_LOG live]; NOTE: overlay plane not composited into the dev sec cell (polish item) — data verified by log | T2 | CORRECT | | 28 | sec: sectorDisplay (overlay) | localOrigin → Round(Z·0.01)+500 / Round(X·0.01)+500 | commit 4a4ec68 [BT_SECTOR_LOG live]; **overlay plane COMPOSITED 2026-08-13** (branch sec-overlay-composite): every sec expand (BTDrawGaugeSurfaces, BTDrawCockpitPanels, SVGA16::Update case 0, glass BlitSurface) now uses sec\|overlay = 0xFF — clut0 already held the arcade DAC's combined table (BuildSecondaryPalette spreads each port over the other's bit combos), so the fix is mask-only; the glass dirty token gained the overlay port so the numerals repaint. Verified live: SECTOR ticks + SCALE:1000 at spawn (1000 = radarRange init; the DOS shot's 4000 is max zoom) + the btsec1ov graticule now draws | T2 | CORRECT |
| 29 | sec: schematic ARMOR view (cmArmor/multiArmor, dama.pcc) | zone damageLevel ×100 → adpal ramp | incr.3/4; all-green pristine LIVE | T2 | CORRECT (AUTH-STATIC all-green solo) | | 29 | sec: schematic ARMOR view (cmArmor/multiArmor, dama.pcc) | zone damageLevel ×100 → adpal ramp | incr.3/4; all-green pristine LIVE | T2 | CORRECT (AUTH-STATIC all-green solo) |
| 30 | sec: schematic CRITICAL view (cmCrit) | subsystem simulationState/damage | LIVE this audit: N-cycle shows the full subsystem list (GEN A-D, LOOP 1-6, HUD, SENSORS, GYRO, TORSO, weapons) | T2 | CORRECT | | 30 | sec: schematic CRITICAL view (cmCrit) | subsystem simulationState/damage | LIVE this audit: N-cycle shows the full subsystem list (GEN A-D, LOOP 1-6, HUD, SENSORS, GYRO, TORSO, weapons) | T2 | CORRECT |
| 31 | sec: schematic HEAT view (cmHeat) | subsystem currentTemperature tint | #6 pixel-verified; re-cycled this audit (mask 0x450421→0x490421→0x510421) | T2 | CORRECT | | 31 | sec: schematic HEAT view (cmHeat) | subsystem currentTemperature tint | #6 pixel-verified; re-cycled this audit (mask 0x450421→0x490421→0x510421) | T2 | CORRECT |
+11 -54
View File
@@ -2798,7 +2798,6 @@ GaugeRenderer::GaugeRenderer():
{ {
Test_Tell("GaugeRenderer::GaugeRenderer()\n"); Test_Tell("GaugeRenderer::GaugeRenderer()\n");
Check_Pointer(this); Check_Pointer(this);
staticEntitiesFilled = False; // radar map geometry: filled per mission
int int
i; i;
@@ -3016,13 +3015,6 @@ void
Test_Tell("GaugeRenderer::LinkToEntity(" << entity << ")\n"); Test_Tell("GaugeRenderer::LinkToEntity(" << entity << ")\n");
// RADAR MAP GEOMETRY (2026-08-13): a new mission link invalidates the
// persistent static grid -- clear it and let the next RebuildEntityGrid
// refill from the new mission's entities (the pod process hosts many
// missions back to back; stale pointers must not cross rounds).
staticEntities.Clear();
staticEntitiesFilled = False;
// //
//-------------------------------------------------------------------- //--------------------------------------------------------------------
// Inform all gauges // Inform all gauges
@@ -3621,6 +3613,7 @@ void
Check(this); Check(this);
movingEntities.Clear(); movingEntities.Clear();
staticEntities.Clear();
if (application == NULL) if (application == NULL)
{ {
@@ -3636,18 +3629,13 @@ void
// projectiles that AllEntityIterator would flood the grid with -- iterate the // projectiles that AllEntityIterator would flood the grid with -- iterate the
// dynamic masters (the local + AI/dummy vehicles) plus the dynamic replicants // dynamic masters (the local + AI/dummy vehicles) plus the dynamic replicants
// (peer vehicles in multiplayer). // (peer vehicles in multiplayer).
// // CLASSIFICATION (the phantom-red-pip fix, 2026-07-12): the dynamic
// STATIC GRID SPLIT (2026-08-13, the missing-radar-map-geometry fix): the // iterators ALSO surface non-Mover world entities (the map's CulturalIcon
// per-frame rebuild used to Clear() + refill staticEntities from the SAME // props, class 0x5E, register dynamic on this port) -- unsorted, they drew
// dynamic iterators -- which structurally excluded the map's STATIC-flagged // through the radar's MOVING loop as RED contacts ("multiple pips with one
// UnscalableTerrain placements (528 of the 1071 outlined map records; on the // player"). Sort by Mover derivation: vehicles into the moving grid (red
// arena maps essentially every wall/building), so MapDisplay::DrawStatic had // pips), everything else into the static grid (the dim silhouette layer
// nothing to draw and the radar showed no map. staticEntities is now // DrawStatic draws) -- the follow-up the old note deferred.
// PERSISTENT: filled ONCE per mission below (every UnscalableTerrain-derived
// entity carrying a rtype-18 GaugeImage -- static AND dynamic, so the
// CulturalIcon props the 2026-07-12 phantom-red-pip fix routed here still
// arrive), pruned by NotifyOfBecomingUninterestingEntity, reset at
// LinkToEntity. The per-frame loops below rebuild only the MOVING grid.
{ {
HostManager::DynamicMasterEntityIterator master_iterator(host_manager); HostManager::DynamicMasterEntityIterator master_iterator(host_manager);
master_iterator.First(); master_iterator.First();
@@ -3667,6 +3655,8 @@ void
if (st != 2 && st != 9) if (st != 2 && st != 9)
movingEntities.Add(entity); movingEntities.Add(entity);
} }
else
staticEntities.Add(entity);
} }
} }
{ {
@@ -3681,42 +3671,9 @@ void
if (st != 2 && st != 9) if (st != 2 && st != 9)
movingEntities.Add(entity); movingEntities.Add(entity);
} }
} else
}
//
// THE ONE-TIME STATIC FILL. Authoritative regardless of the interest
// feed's health: walk EVERY entity once per mission and keep the
// UnscalableTerrain-derived ones that carry a gauge representation (the
// rtype-18 GaugeImage in their model list -- the same records
// docs/MAPS.html renders). The class gate is UnscalableTerrain, NOT
// Terrain: the shipped content has ZERO Terrain records -- its map
// geometry is 1008x UnscalableTerrain + 789x CulturalIcon (a subclass),
// and our headers derive UnscalableTerrain from Entity, so the old
// Terrain gate matched nothing (see the btl4grnd.cpp note on @0x4e6ef8).
// Clear() first so any pre-fill interest-feed adds can't duplicate.
//
if (!staticEntitiesFilled)
{
staticEntities.Clear();
HostManager::AllEntityIterator all_iterator(host_manager);
Entity *entity;
int kept = 0;
while ((entity = all_iterator.ReadAndNext()) != NULL)
{
if (!entity->IsDerivedFrom(*UnscalableTerrain::GetClassDerivations()))
continue;
if (application->GetResourceFile()->SearchList(
entity->GetResourceID(),
ResourceDescription::GaugeImageStreamResourceType) == NULL)
continue;
staticEntities.Add(entity); staticEntities.Add(entity);
++kept;
} }
staticEntitiesFilled = True;
if (getenv("BT_RADAR_LOG"))
DEBUG_STREAM << "[radar] static grid filled: " << kept
<< " map entities with gauge outlines\n" << std::flush;
} }
Check_Fpu(); Check_Fpu();
} }
-5
View File
@@ -673,11 +673,6 @@ protected:
GraphicsPort *graphicsPort[maximumGraphicsPorts]; GraphicsPort *graphicsPort[maximumGraphicsPorts];
GaugeEntityList movingEntities; GaugeEntityList movingEntities;
GaugeEntityArray staticEntities; GaugeEntityArray staticEntities;
// RADAR MAP GEOMETRY (2026-08-13): staticEntities is PERSISTENT, filled
// ONCE per mission by RebuildEntityGrid's authoritative walk (and pruned by
// NotifyOfBecomingUninterestingEntity). Reset at LinkToEntity so a pod
// process that hosts many missions refills per round.
Logical staticEntitiesFilled;
LampManager *lampManager; LampManager *lampManager;
GaugeAlarmManager *gaugeAlarmManager; GaugeAlarmManager *gaugeAlarmManager;
+19
View File
@@ -1313,6 +1313,20 @@ static void
int mask = port->GetBitMask(); int mask = port->GetBitMask();
int palId = port->paletteID; int palId = port->paletteID;
int tint = w->monoTint; int tint = w->monoTint;
// OVERLAY COMPOSITE (2026-08-13): the radar's low byte is TWO ports -- sec
// (0x3F) + overlay (0xC0: the graticule art, the SCALE numeral and the
// SECTOR numerals). The arcade DAC indexed the full low byte and clut0
// already holds the combined 256-entry table (BuildSecondaryPalette spreads
// each port over the other's bit combinations), so the palette-expanded
// window composites both planes with just the OR'd mask -- previously the
// 0x3F mask left SECTOR:/SCALE: blank while their writers were live.
if (tint < 0)
{
L4GraphicsPort *ov =
static_cast<L4GraphicsPort*>(gr->GetGraphicsPort("overlay"));
if (ov != NULL && ov->graphicsDisplay == port->graphicsDisplay)
mask |= ov->GetBitMask();
}
static int sMfdPal = -1; static int sMfdPal = -1;
if (sMfdPal < 0) sMfdPal = getenv("BT_GLASS_MFD_PAL") ? 1 : 0; if (sMfdPal < 0) sMfdPal = getenv("BT_GLASS_MFD_PAL") ? 1 : 0;
if (sMfdPal && tint >= 0) if (sMfdPal && tint >= 0)
@@ -2110,6 +2124,11 @@ static unsigned long
const char *ports[8]; const char *ports[8];
int np = 0; int np = 0;
ports[np++] = w->portPrimary; ports[np++] = w->portPrimary;
if (w->monoTint < 0)
ports[np++] = "overlay"; // composited into the palette-expanded
// radar (2026-08-13) -- the SECTOR/SCALE
// numerals tick on THIS plane, so their
// churn must mark the window dirty
if (w->portAlt != NULL) ports[np++] = w->portAlt; if (w->portAlt != NULL) ports[np++] = w->portAlt;
for (int gi = 0; gi < w->groupCount && np < 7; ++gi) for (int gi = 0; gi < w->groupCount && np < 7; ++gi)
{ {
+32 -3
View File
@@ -860,6 +860,31 @@ static const float kBTPanelH = 480.0f;
// channel-enabled and which is BlankColor; the draw loop below skips the // channel-enabled and which is BlankColor; the draw loop below skips the
// blanked plane, so the dev cell shows exactly what the pod monitor shows. // blanked plane, so the dev cell shows exactly what the pod monitor shows.
// The Eng entries share their sibling's cell rect and FOLLOW it in the list. // The Eng entries share their sibling's cell rect and FOLLOW it in the list.
//
// OVERLAY COMPOSITE (2026-08-13). The secondary CRT's low byte is authored as
// TWO ports -- sec (bits 0x3F, btspal) and overlay (bits 0xC0, btopal,
// TransparentZero) carrying the graticule art, the SCALE numeral (RadarRange)
// and the SECTOR numerals (sectorDisplay). The arcade DAC indexed the FULL low
// byte, blending both planes; every port expand used to mask to sec's 0x3F,
// which is why SECTOR:/SCALE: read blank on desktop while their reconstructed
// writers were live in the buffer the whole time. clut0 already holds the
// DAC's combined 256-entry table (BuildSecondaryPalette spreads each port's
// colors across the other's bit combinations), so the composite is exactly
// this mask -- no blending code.
//
static int BTSecCompositeMask(GaugeRenderer *gr, L4GraphicsPort *sec_port)
{
int mask = sec_port->GetBitMask();
if (gr != NULL)
{
L4GraphicsPort *ov =
static_cast<L4GraphicsPort*>(gr->GetGraphicsPort("overlay"));
if (ov != NULL && ov->graphicsDisplay == sec_port->graphicsDisplay)
mask |= ov->GetBitMask();
}
return mask;
}
static const BTGaugeSurfaceDesc kBTGaugeSurfaces[9] = static const BTGaugeSurfaceDesc kBTGaugeSurfaces[9] =
{ {
{ "Heat", 0xFFFF, 0.0f / kBTPanelW, 0.0f, 320.0f / kBTPanelW, 0.5f, 0 }, { "Heat", 0xFFFF, 0.0f / kBTPanelW, 0.0f, 320.0f / kBTPanelW, 0.5f, 0 },
@@ -946,7 +971,9 @@ void BTDrawGaugeSurfaces(LPDIRECT3DDEVICE9 device, float px, float py, float pw,
rot = s_secRot; rot = s_secRot;
} }
svga->DrawDevSurface( svga->DrawDevSurface(
device, i, port->GetBitMask(), port->paletteID, d.monoTint, device, i,
d.monoTint < 0 ? BTSecCompositeMask(gr, port) : port->GetBitMask(),
port->paletteID, d.monoTint,
px + d.cellX * pw, py + d.cellY * ph, d.cellW * pw, d.cellH * ph, rot); px + d.cellX * pw, py + d.cellY * ph, d.cellW * pw, d.cellH * ph, rot);
} }
@@ -1322,7 +1349,9 @@ void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device)
} }
int surf = ckSlotOf[i]; int surf = ckSlotOf[i];
int useTint = (d.monoTint < 0) ? -1 : tint; int useTint = (d.monoTint < 0) ? -1 : tint;
svga->DrawDevSurface(device, i, port->GetBitMask(), port->paletteID, useTint, int useMask = (d.monoTint < 0) ? BTSecCompositeMask(gr, port)
: port->GetBitMask();
svga->DrawDevSurface(device, i, useMask, port->paletteID, useTint,
(float)L.surfX[surf], (float)L.surfY[surf], (float)L.surfW[surf], (float)L.surfH[surf], rot); (float)L.surfX[surf], (float)L.surfY[surf], (float)L.surfW[surf], (float)L.surfH[surf], rot);
} }
} }
@@ -5833,7 +5862,7 @@ Logical SVGA16::Update(Logical forceAll)
//Drawing secondary, make sure we got secondary //Drawing secondary, make sure we got secondary
if (secPort != NULL) if (secPort != NULL)
{ {
secMask = secPort->GetBitMask(); secMask = BTSecCompositeMask(renderer, secPort); // sec | overlay (DAC parity)
secPalette = &((SVGA16 *) secPort->graphicsDisplay)->palette[secPort->paletteID]; secPalette = &((SVGA16 *) secPort->graphicsDisplay)->palette[secPort->paletteID];
} else } else
{ {
+2 -19
View File
@@ -362,21 +362,7 @@ void
{ {
movingEntities.Add(entity); // FUN_004434f4, this+0x94 movingEntities.Add(entity); // FUN_004434f4, this+0x94
} }
// else if (entity->IsDerivedFrom(*Terrain::GetClassDerivations())) // 0x4e6ef8
// GATE CORRECTED 2026-08-13 (the missing-radar-map-geometry fix): this
// was transcribed as Terrain::GetClassDerivations() -- but the shipped
// content has ZERO Terrain records (census: 1008x UnscalableTerrain +
// 789x CulturalIcon, both deriving from Entity in our headers), so the
// Terrain predicate was dead code and rejected 100% of map geometry.
// The binary's @0x4e6ef8 must have been UnscalableTerrain's derivation
// (or the shipped-machine MUNGA derived UnscalableTerrain from
// Terrain). [T3 on which; the corrected gate matches the content.]
// The add is guarded on the one-time fill (GAUGREND RebuildEntityGrid)
// not having run -- post-fill notifications would double-add, and the
// fill's Clear()+walk supersedes any pre-fill adds.
//
else if (!staticEntitiesFilled
&& entity->IsDerivedFrom(*UnscalableTerrain::GetClassDerivations()))
{ {
staticEntities.Add(entity); // FUN_00443864, this+0xb0 staticEntities.Add(entity); // FUN_00443864, this+0xb0
} }
@@ -423,10 +409,7 @@ void
{ {
movingEntities.Remove(entity); // FUN_00443510, this+0x94 movingEntities.Remove(entity); // FUN_00443510, this+0x94
} }
// Gate corrected 2026-08-13 to match the ADD side (see the note there): else if (entity->IsDerivedFrom(*Terrain::GetClassDerivations())) // 0x4e6ef8
// UnscalableTerrain, not Terrain. Post-fill this is the prune path for
// a destroyed/expiring static -- it leaves the persistent radar grid.
else if (entity->IsDerivedFrom(*UnscalableTerrain::GetClassDerivations()))
{ {
staticEntities.Remove(entity); // FUN_00443930, this+0xb0 staticEntities.Remove(entity); // FUN_00443930, this+0xb0
} }