Destructible props LIVE: revive StateInstanceSwitchRenderable (Gitea issue #3)

Playtest report: trucks play a collision sound on impact but never change
state -- no explosion swap, no destroyed model, collision volume persists.

Investigation (BT_CULT_LOG census + BT_GOTO ram harness): the SIM was never
broken.  ARENA1 map-streams 124 CulturalIcon entities, each with a damage
zone, explosion resource and removeOnDeath; the mech crunch dispatch
(ProcessCollision -> BTDispatchCollisionDamage) reaches them; ~19 walking
bumps burn the zone -> CulturalIcon::TakeDamageMessageHandler spawns the
Explosion (psfx effect 1008 observed), posts the delayed BurningState and
deletes the collision boxes.  All of that ran correctly and invisibly.

The ACTUAL bug: the 2007 WinTesla port fully stubbed
StateInstanceSwitchRenderable ('STUBBED: DPL RB 1/14/07') -- ctor never
registered on the state dial, Execute never toggled anything -- so the
intact->rubble visual swap on BurningState NEVER fired, for every cultural
icon in the game.  The destroyed truck kept its intact model: exactly the
reported 'no state change'.

Revival (D3D9-native, 1995 semantics verbatim):
- StateInstanceSwitchRenderable now controls the draw COMPONENT via the
  SetDrawObj in-place drawable swap (the mech RemakeEntity mechanism):
  visible = captured d3d_OBJECT, hidden = NULL.  Initial state in the ctor,
  AddVideoWatcher on the SimulationState dial (SetState fires video
  watchers), toggle only on a real change.
- L4VIDEO cultural case passes the draw component + object (the dead
  dpl_INSTANCE param is gone); [video] Object hides at BurningState,
  Rubble shows.
- REQUIRED: cultural objects stay OUT of static-mesh consolidation
  (RecurseStaticObject, same exclusion class as banded LODs/shadows) --
  a merged static draws forever regardless of DrawObj.
- Permanent env diag BT_CULT_LOG: creation census (zones/explosion/flags/
  pos), TakeDamage trace, death transition, [cultvis] switch actions.

Verified live (BT_GOTO=-620,-328 ram, ARENA1): 19 crunches -> DYING ->
explosion 1008 at the icon -> [cultvis] HIDE x2 (intact) + SHOW (rubble)
-> contacts cease, mech walks into the former footprint.  Boot + render
un-regressed (statics consolidate as before minus the 124 icons).

KB: context/rendering.md new section (the full chain + the consolidation
gotcha).  MP replicant path shares the same SetState watcher (cross-pod
verification pending -- noted in the issue).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-19 09:23:02 -05:00
co-authored by Claude Fable 5
parent 0bcba26213
commit 53ac927d4b
5 changed files with 177 additions and 89 deletions
+23 -9
View File
@@ -3813,6 +3813,20 @@ void DPLRenderer::RecurseStaticObject(HierarchicalDrawComponent *obj)
// (translucent + depth bias); merging them would draw them opaque.
if (dobj->GetIsShadow())
banded = true;
// Issue #3 (cultural destruction): CulturalIcon/Landmark objects are
// DESTRUCTIBLE -- their intact model must vanish (and rubble appear) at
// BurningState via the revived StateInstanceSwitchRenderable's
// SetDrawObj swap. A model merged into the consolidated static mesh
// draws forever regardless of DrawObj, so destructibles stay OUT of
// consolidation and keep drawing individually (the same mechanism as
// the banded-LOD exclusion above).
{
Entity *owning = obj->GetOwningEntity();
if (owning != NULL
&& (owning->GetClassID() == CulturalIconClassID
|| owning->GetClassID() == LandmarkClassID))
banded = true;
}
if (!banded)
{
//Only load it for a static object if it has a valid mesh-
@@ -7086,12 +7100,6 @@ void
case CulturalIconClassID:
case LandmarkClassID:
{
dpl_INSTANCE *this_instance = NULL; // BT bring-up: the per-object instance
// assignments below are DPL-stubbed
// (commented out) -> initialise so the
// /RTC1 uninitialised-variable check does
// not fault when this_instance is passed
// to StateInstanceSwitchRenderable.
d3d_OBJECT *this_object;
dpl_ISECT_MODE intersect_mode;
HierarchicalDrawComponent *component = NULL;
@@ -7204,7 +7212,10 @@ void
// this_instance = another_instance->GetInstance();
}
}
// add the new object to our renderables list
// add the new object to our renderables list. Keep the draw
// component in hand -- the revived state switch (issue #3)
// controls IT (SetDrawObj show/hide), not a dead DPL instance.
HierarchicalDrawComponent *draw_component = component;
if (component)
mRenderables.Add(component);
component = NULL;
@@ -7212,6 +7223,7 @@ void
// Hook up the object to an instance switch renderable responsive to state
//
StateIndicator* simulation_state = (StateIndicator *)entity->GetAttributePointer("SimulationState");
if (simulation_state != NULL && draw_component != NULL)
switch(resource_type)
{
case L4VideoObject::Object:
@@ -7219,7 +7231,8 @@ void
component = new StateInstanceSwitchRenderable(
entity, // Entity to attach the renderable to
StateInstanceSwitchRenderable::Watcher, // How/when to execute the renderable
this_instance, // the instance to control
draw_component, // the draw component to control
this_object, // its drawable (restored on show)
False, // true to turn on in this state, false for off
simulation_state, // State dial we use to control the on/off
CulturalIcon::BurningState); // State that we look for
@@ -7230,7 +7243,8 @@ void
component = new StateInstanceSwitchRenderable(
entity, // Entity to attach the renderable to
StateInstanceSwitchRenderable::Watcher, // How/when to execute the renderable
this_instance, // the instance to control
draw_component, // the draw component to control
this_object, // its drawable (restored on show)
True, // true to turn on in this state, false for off
simulation_state, // State dial we use to control the on/off
CulturalIcon::BurningState); // State that we look for