Searchlight reconstruction -- the pod's night kit, both halves

The subsystem (sim/toggle/attribute/replication) was already complete; this
lands the missing VISUALS, decoded from MakeMechRenderables @004cef28 case
0xbd8 (raw pseudocode part_014):

- COCKPIT: the 1995 searchlight is a FOG SWAP -- the @00456778/@00456814
  watcher switches DPLRenderer::SetFogStyle between the authored fog= (lit)
  and nosearchlightfog= (dark) sets per map/time page in BTDPL.INI.  The
  engine kept the whole system under its real names; completed the stubbed
  plane application (currentFogNear/Far) and transcribed the watcher (with
  its inverted-cache seed) into TickSearchlight.  CONSEQUENCE: night now
  STARTS on the authentic dark set (near-plane 5u on arena pages) -- our
  builds had rendered the searchlight-ON fog permanently.
- EXTERNAL: spot.bgf beam cone hung on the searchlight SITE joint, shown/
  hidden from the replicated LightOn attribute (@0045612c watcher).  Site
  segments now build geometry-less DCS children (posed + parentable, as the
  1995 graph did) -- previously they were skipped entirely.
- searchlight.hpp: commandedOn @0x1DC identified as mountSegment (resource
  segmentIndex; the cone's mount joint).

Benches: searchfog.sh (solo cockpit: first-tick dark sync, F5/0x14 press ->
SetFogStyle(2), red-fog probe end-to-end), spotcone.sh (2-node: B's button ->
lightState replication -> A logs "[spot] cone SHOWN (seg 20)").  Cone look
(size/aim on the mount) pending an eyeball pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-05 19:44:23 -05:00
co-authored by Claude Fable 5
parent b31c6c4527
commit 412053d5af
7 changed files with 329 additions and 11 deletions
+42 -6
View File
@@ -82,7 +82,7 @@ Searchlight::SharedData
//
// Chains to the PowerWatcher ctor (FUN_004b18a4) with &DAT_00511198, installs
// the Searchlight vtable (PTR_FUN_005114d4), builds the 2-level state
// AlarmIndicator (@0x1E4) and seeds commandedOn from the segment field
// AlarmIndicator (@0x1E4) and seeds mountSegment from the segment field
// (+0x28). When this is a live (non-copy) segment with the has-performance
// flag set it registers SearchlightSimulation (this[7..9] <- PTR_FUN_00511200);
// otherwise it marks the instance "no per-frame performance" (flag bit 2).
@@ -103,7 +103,7 @@ Searchlight::Searchlight(
lightState = 0; // @0x1D8
requestedOn = 0; // @0x1E0
stateAlarm.Initialize(2); // FUN_0041b9ec(this+0x1E4, 2)
commandedOn = subsystem_resource->segmentIndex; // @0x1DC <- inherited resource +0x28
mountSegment = subsystem_resource->segmentIndex; // @0x1DC <- inherited resource +0x28 (the spot-cone mount joint)
// GATE FIX (the functional bug the de-shim unblocks): the binary reads OWNER
// simulationFlags (param_2+0x28, raw part_013.c:6012), NOT the old shadow
@@ -311,8 +311,8 @@ void
if (!announced)
{
announced = 1;
DEBUG_STREAM << "[light] SearchlightSimulation RUNNING (commandedOn "
<< commandedOn << ", requestedOn " << requestedOn
DEBUG_STREAM << "[light] SearchlightSimulation RUNNING (mountSegment "
<< mountSegment << ", requestedOn " << requestedOn
<< ", lightState " << lightState << ")" << std::endl;
}
}
@@ -323,7 +323,7 @@ void
if (getenv("BT_FIRE_LOG") || getenv("BT_HEAT_LOG"))
DEBUG_STREAM << "[light] reported lightState " << previous << " -> " << lightState
<< " (commandedOn " << commandedOn << ", requestedOn " << requestedOn << ")"
<< " (mountSegment " << mountSegment << ", requestedOn " << requestedOn << ")"
<< std::endl;
}
@@ -405,7 +405,7 @@ int
struct SearchlightLayoutCheck
{
static_assert(offsetof(Searchlight, lightState) == 0x1D8, "Searchlight lightState @0x1D8 (PowerWatcher ends 0x1D8)");
static_assert(offsetof(Searchlight, commandedOn) == 0x1DC, "Searchlight commandedOn @0x1DC (res+0x28)");
static_assert(offsetof(Searchlight, mountSegment) == 0x1DC, "Searchlight mountSegment @0x1DC (res+0x28 segmentIndex)");
static_assert(offsetof(Searchlight, requestedOn) == 0x1E0, "Searchlight requestedOn @0x1E0");
static_assert(offsetof(Searchlight, stateAlarm) == 0x1E4, "Searchlight stateAlarm @0x1E4 (proves base ends 0x1D8)");
static_assert(sizeof(Searchlight) <= 0x238, "sizeof(Searchlight) must fit the factory Memory::Allocate(0x238)");
@@ -423,3 +423,39 @@ Subsystem *CreateSearchlightSubsystem(Mech *owner, int id, void *seg)
return (Subsystem *) new (Memory::Allocate(0x238))
Searchlight(owner, id, (Searchlight::SubsystemResource *)seg, Searchlight::DefaultData);
}
//===========================================================================//
// Video-layer probe (2026-08-05, the spot-cone/fog reconstruction). The 1995
// MakeMechRenderables walk (@004cef28) hits `case 0xbd8` per roster subsystem
// and pulls the "LightOn" attribute + the mount joint; our port's tree builder
// does not walk the roster, so btl4vid calls this bridge instead (complete-
// Searchlight TU, per the databinding rule). Returns the number of
// searchlights found (0..2 reported -- the binary's cockpit branch keeps TWO
// attr slots, @004cef28 local_118/local_11c; more than 2 exist on no chassis).
//===========================================================================//
int BTMechSearchlightProbe(
void *mech_v,
int **light_on_out, // [2] "LightOn" attribute pointers (int*)
int *mount_seg_out) // [2] mount segment slots (resource segmentIndex)
{
Mech *mech = (Mech *)mech_v;
if (mech == 0)
{
return 0;
}
int found = 0;
for (int i = 0; i < mech->GetSubsystemCount() && found < 2; ++i)
{
Subsystem *sub = mech->GetSubsystem(i);
if (sub == 0
|| (int)sub->GetClassID() != (int)RegisteredClass::SearchlightClassID) // 0xBD8
{
continue;
}
Searchlight *light = (Searchlight *)sub;
light_on_out[found] = light->LightOnPointer(); // == GetAttributePointer("LightOn")
mount_seg_out[found] = light->MountSegmentIndex(); // @0x1DC (res+0x28 segmentIndex)
++found;
}
return found;
}