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:
co-authored by
Claude Fable 5
parent
b31c6c4527
commit
412053d5af
@@ -170,6 +170,99 @@ void
|
||||
// localToWorld in its ctor -- no explicit AddDynamicRenderable here
|
||||
// (unlike the 1996 VideoComponent path).
|
||||
(void)mech_root;
|
||||
|
||||
//
|
||||
// SEARCHLIGHT pass (2026-08-05) -- the roster walk's `case 0xbd8`
|
||||
// (@004cef28, raw pseudocode part_014). The binary, per searchlight
|
||||
// subsystem: resolve the "LightOn" attribute (error print
|
||||
// BTL4VID.CPP:0x941 when absent); COCKPIT build (view 1) -> stash up
|
||||
// to TWO attrs for the fog-swap watcher (@00456778); EXTERNAL build
|
||||
// -> load "spot.bgf" (warn if missing), hang it as a child of the
|
||||
// mount joint and bind visibility to the attribute (@0045612c). Our
|
||||
// tree builder does not walk the roster, so the probe bridge
|
||||
// (searchlight.cpp) supplies {attr, mount segment} and the two
|
||||
// watcher Execute bodies live in TickSearchlight.
|
||||
//
|
||||
{
|
||||
extern int BTMechSearchlightProbe(void *, int **, int *);
|
||||
int *attrs[2];
|
||||
int segs[2];
|
||||
int count = BTMechSearchlightProbe((void *)entity, attrs, segs);
|
||||
std::map<Entity*, MechRenderTree>::iterator ti =
|
||||
mMechRenderTrees.find(entity);
|
||||
if (count > 0 && ti != mMechRenderTrees.end())
|
||||
{
|
||||
MechRenderTree &tree = ti->second;
|
||||
tree.searchLightCount = 0;
|
||||
tree.searchIsCockpit = (view_type == 1);
|
||||
for (int sl = 0; sl < count; ++sl)
|
||||
{
|
||||
MechRenderTree::SearchLight &light =
|
||||
tree.searchLight[tree.searchLightCount];
|
||||
light.cone = NULL;
|
||||
light.coneObj = NULL;
|
||||
light.lightOn = attrs[sl];
|
||||
light.shown = 0;
|
||||
light.mountSeg = segs[sl];
|
||||
// the binary's inverted seed (@00456778: [9]/[10] =
|
||||
// (*attr == 0)) -- guarantees the FIRST tick "changes"
|
||||
// and normalizes the fog to the real lamp state, which
|
||||
// is how a night mission starts on nosearchlightfog.
|
||||
tree.searchFogCache[tree.searchLightCount] =
|
||||
(*attrs[sl] == 0);
|
||||
if (!tree.searchIsCockpit)
|
||||
{
|
||||
// NB: the renderer-level LoadObject wrapper (@00498448) is
|
||||
// still a btstubs no-op; load through the d3d route the
|
||||
// tree builder itself uses (extension REQUIRED there).
|
||||
d3d_OBJECT *spot = d3d_OBJECT::LoadObject(
|
||||
GetDevice(), (char *)"spot.bgf"); // @0051d6c0
|
||||
if (spot == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "[spot] Couldn't locate spot.bgf "
|
||||
"for mech\n" << std::flush; // @0051d6c9
|
||||
}
|
||||
else
|
||||
{
|
||||
std::map<int, HierarchicalDrawComponent*>::iterator si =
|
||||
tree.segRenderable.find(segs[sl]);
|
||||
HierarchicalDrawComponent *parent =
|
||||
(si != tree.segRenderable.end()) ? si->second : NULL;
|
||||
if (parent == NULL)
|
||||
{
|
||||
// slot-space mismatch would land here -- keep the
|
||||
// receipt loud (see the bench).
|
||||
DEBUG_STREAM << "[spot] mount segment " << segs[sl]
|
||||
<< " has no joint renderable -- cone skipped; slots:";
|
||||
for (std::map<int, HierarchicalDrawComponent*>::iterator di =
|
||||
tree.segRenderable.begin();
|
||||
di != tree.segRenderable.end(); ++di)
|
||||
DEBUG_STREAM << " " << di->first;
|
||||
DEBUG_STREAM << "\n" << std::flush;
|
||||
}
|
||||
else
|
||||
{
|
||||
LinearMatrix identity(True);
|
||||
dpl_ISECT_MODE gSpotIsectMode; // stub type (binary: mode 1)
|
||||
light.cone = new DPLStaticChildRenderable(
|
||||
entity, false /* main zone */, spot,
|
||||
gSpotIsectMode, 0 /* mask 0: never pickable */,
|
||||
identity, parent);
|
||||
light.cone->SetDrawObj(NULL); // hidden until LightOn
|
||||
light.coneObj = spot;
|
||||
}
|
||||
}
|
||||
}
|
||||
++tree.searchLightCount;
|
||||
}
|
||||
DEBUG_STREAM << "[spot] searchlight pass: " << count
|
||||
<< " light(s), view=" << (int)view_type
|
||||
<< " mountSeg=" << segs[0]
|
||||
<< (tree.searchIsCockpit ? " (cockpit: fog watcher)"
|
||||
: " (external: spot cone)")
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -492,6 +585,22 @@ HierarchicalDrawComponent*
|
||||
mCamera = mEyeCockpit;
|
||||
dbg_eye = 1;
|
||||
}
|
||||
//
|
||||
// SEARCHLIGHT (2026-08-05): sites are ATTACHMENT joints, and the
|
||||
// 1995 render graph gave every one a DCS node -- that node is what
|
||||
// the subsystem-visual walk parents to (the spot cone hangs on the
|
||||
// searchlight site, @004cef28 case 0xbd8). Build the geometry-less
|
||||
// child so site slots are posed + parentable; nothing draws for the
|
||||
// site itself.
|
||||
//
|
||||
{
|
||||
dpl_ISECT_MODE site_isect;
|
||||
HierarchicalDrawComponent *site_child = new DPLStaticChildRenderable(
|
||||
entity, inDeathZone, NULL /* no geometry */,
|
||||
site_isect, 0, offset_matrix, parent_DCS);
|
||||
dcs_array[segment->GetIndex()] = site_child;
|
||||
render_tree.segRenderable[segment->GetIndex()] = site_child;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1545,6 +1654,63 @@ void
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// TickSearchlight -- the two 1995 searchlight watchers, transcribed
|
||||
//#############################################################################
|
||||
//
|
||||
// External cone (@0045612c, Execute @004561d8): poll the subsystem's LightOn
|
||||
// attribute; on change, set the spot instance visible iff the value matches
|
||||
// (match value 1) and flush. Our equivalent of SetInstanceOn/Flush is the
|
||||
// SetDrawObj object swap (the wreck-reveal mechanism).
|
||||
//
|
||||
// Cockpit fog (@00456778, Execute @00456814): poll BOTH stashed attrs; when
|
||||
// either changed, either-on -> SetFogStyle(searchLightOnFogStyle) else
|
||||
// SetFogStyle(searchLightOffFogStyle). The ctor's INVERTED caches make the
|
||||
// first poll always fire, normalizing a night start onto nosearchlightfog --
|
||||
// the authentic dark (fog= is the LIT set; see BTDPL.INI per-page authoring).
|
||||
//
|
||||
void
|
||||
BTL4VideoRenderer::TickSearchlight(Entity *mech)
|
||||
{
|
||||
std::map<Entity*, MechRenderTree>::iterator ti = mMechRenderTrees.find(mech);
|
||||
if (ti == mMechRenderTrees.end() || ti->second.searchLightCount <= 0)
|
||||
return;
|
||||
MechRenderTree &tree = ti->second;
|
||||
|
||||
int any_on = 0, changed = 0;
|
||||
for (int sl = 0; sl < tree.searchLightCount; ++sl)
|
||||
{
|
||||
MechRenderTree::SearchLight &light = tree.searchLight[sl];
|
||||
if (light.lightOn == NULL)
|
||||
continue;
|
||||
const int cur = (*light.lightOn != 0);
|
||||
|
||||
if (light.cone != NULL && cur != light.shown) // @004561d8
|
||||
{
|
||||
light.cone->SetDrawObj(cur ? light.coneObj : NULL);
|
||||
light.shown = cur;
|
||||
if (getenv("BT_FIRE_LOG") || getenv("BT_FOG_LOG"))
|
||||
DEBUG_STREAM << "[spot] cone " << (cur ? "SHOWN" : "HIDDEN")
|
||||
<< " (seg " << light.mountSeg << ")\n" << std::flush;
|
||||
}
|
||||
|
||||
if (cur != tree.searchFogCache[sl]) // @00456814
|
||||
changed = 1;
|
||||
tree.searchFogCache[sl] = cur;
|
||||
any_on |= cur;
|
||||
}
|
||||
|
||||
if (tree.searchIsCockpit && changed)
|
||||
{
|
||||
SetFogStyle(any_on ? searchLightOnFogStyle : searchLightOffFogStyle);
|
||||
if (getenv("BT_FIRE_LOG") || getenv("BT_FOG_LOG"))
|
||||
DEBUG_STREAM << "[spot] cockpit fog -> "
|
||||
<< (any_on ? "searchLightOn" : "searchLightOff") << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Sim-side bridge (per-mech, every frame, from Mech::PerformAndWatch).
|
||||
//
|
||||
@@ -1557,6 +1723,7 @@ void BTArmourDamageTick(Entity *mech)
|
||||
if (renderer == NULL)
|
||||
return;
|
||||
renderer->TickArmourDamage(mech);
|
||||
renderer->TickSearchlight(mech); // the searchlight watchers ride the same beat
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user