BT410 5.3.55: the mech skeleton is built -- .SKL walked into a dpl_DCS tree
BTL4VideoRenderer answers MechClassID: walks the video-object chain the way the engine does, hands every L4VideoObject::Skeleton entry to ReadSKLFile, and recurses the .SKL into a dpl_DCS tree with geometry instanced onto it. Verified repeatedly on the live pod: '[skl] video\mad.skl -> 26 nodes, 19 objects', with no 'wrong video resource type' complaint and no load failures. Those counts are exactly what the file declares (25 joint= entries + root, 19 Object= entries). Corrects the earlier success criterion in this file, which said 22 instances by reading the reference capture's 'instance x22' against DZoneCount=22. Damage zones are not geometry -- 28 dzone= tags spread across 19 objects. Translations are written to matrix[3]/[7]/[11], MUNGA's own AffineMatrix layout. It walks cleanly but no frame has been seen WITH the mech yet, so the slot choice is recorded as unconfirmed. Rotation stays identity by design: every base-pose angle in MAD.SKL is 0 or ~1e-3, so translation alone assembles the model and isolates one convention at a time. AND A CORRECTION I have to flag loudly: I earlier concluded from single runs that non-identity translations crashed the pod, 'isolated' it, and 'confirmed' the alternative also crashed. That was all noise. The same binary re-run gives walk / crash / walk / crash -- the known intermittent plane-write defect is now firing on ~half of pod runs and lands at different points each time, which is precisely what made it look deterministic. Never accept a single pod run as evidence on this rig; require two agreeing runs. That defect is now the top of the list: a run must survive both the skeleton build and the launch to render anything, which at ~50% is a coin flip on a four-minute cycle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,15 @@
|
||||
# include <btl4vid.hpp>
|
||||
#endif
|
||||
|
||||
//
|
||||
// The engine's damage-zone tagging callback reads this while geometry is
|
||||
// loading (L4VIDEO.CPP:527). It is a bare global there -- defined at
|
||||
// L4VIDEO.CPP:351 with no header declaration -- so declare it here to set
|
||||
// it around our own loads, exactly as DPLRenderer does around its.
|
||||
//
|
||||
extern Entity
|
||||
*Entity_Being_Created;
|
||||
|
||||
BTL4VideoRenderer::BTL4VideoRenderer(
|
||||
RendererRate calibration_rate,
|
||||
RendererComplexity calibration_complexity,
|
||||
@@ -68,6 +77,262 @@ BTL4VideoRenderer::~BTL4VideoRenderer()
|
||||
// engine and only the CONTENT comes from the donor.
|
||||
//#############################################################################
|
||||
//
|
||||
//
|
||||
//#############################################################################
|
||||
// ReadSKLFile -- build one entity's render skeleton from its .SKL file.
|
||||
//
|
||||
// The .SKL is a plain NotationFile. Each page is one node of the skeleton:
|
||||
//
|
||||
// [jointhip]
|
||||
// parent=jointlocal
|
||||
// Type=hingex <- joint kind (animation; unused at build)
|
||||
// Object=mad_hip.bgf <- optional geometry for this node
|
||||
// dzone=dz_hip <- damage-zone tags (zero or more)
|
||||
// tranx=.. trany=.. tranz=..
|
||||
// pitch=.. yaw=.. roll=..
|
||||
// joint=jointtorso <- child pages (zero or more)
|
||||
//
|
||||
// so the whole model is a recursive walk from [ROOT].
|
||||
//
|
||||
// Files live under video\ -- the engine uses the same bare "video\\" prefix
|
||||
// for its .pfx loads (L4VIDEO.CPP:1513).
|
||||
//
|
||||
// STAGE 1 (this version): the tree, the geometry and the parenting are real;
|
||||
// every node is given an IDENTITY matrix. The model therefore collapses onto
|
||||
// the entity origin and looks wrong, which is deliberate -- the STRUCTURE is
|
||||
// what is being proved here, and it is provable without looking at a pixel:
|
||||
// MAD.SKL declares JointCount=25 (+1 root) and DZoneCount=22, and the
|
||||
// dpl3-revive capture of a REAL pod decodes as 26 DCS bodies and 22 instance
|
||||
// bodies. The counts this walk reports must match.
|
||||
//
|
||||
// STAGE 2 is the transforms. The dpl_MATRIX convention is NOT yet proven
|
||||
// (see RENDER-ROADMAP.NOTES.md) -- a DCS flush body carries 16 float32, and a
|
||||
// decoded capture suggests row-major with the translation in the last row,
|
||||
// but the decoder's offset is suspect by one word. Rather than guess it and
|
||||
// ship a mech that renders confidently in the wrong orientation, this stage
|
||||
// leaves identity in place.
|
||||
//#############################################################################
|
||||
//
|
||||
dpl_DCS *
|
||||
BTL4VideoRenderer::ReadSKLFile(
|
||||
Entity *entity,
|
||||
const char *skeleton_filename,
|
||||
ViewFrom view_type)
|
||||
{
|
||||
Check(this);
|
||||
Check_Pointer(skeleton_filename);
|
||||
|
||||
char
|
||||
path[256];
|
||||
strcpy(path, "video\\");
|
||||
strcat(path, skeleton_filename);
|
||||
|
||||
NotationFile
|
||||
*skeleton = new NotationFile(path);
|
||||
Register_Object(skeleton);
|
||||
|
||||
if (skeleton->PageCount() == 0)
|
||||
{
|
||||
DEBUG_STREAM << "[skl] could not read " << path << "\n" << flush;
|
||||
Unregister_Object(skeleton);
|
||||
delete skeleton;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//
|
||||
// Every node of this model shares one zone, switched on and flushed by
|
||||
// the engine helper (L4VIDEO.CPP:1338).
|
||||
//
|
||||
dpl_ZONE
|
||||
*zone = MakeNewZone();
|
||||
|
||||
int
|
||||
node_count = 0,
|
||||
object_count = 0;
|
||||
|
||||
dpl_DCS
|
||||
*root = RecurseSKLFile(
|
||||
entity, NULL, skeleton, "ROOT", 0, view_type,
|
||||
zone, &node_count, &object_count);
|
||||
|
||||
DEBUG_STREAM << "[skl] " << path
|
||||
<< " -> " << node_count << " nodes, "
|
||||
<< object_count << " objects\n" << flush;
|
||||
|
||||
Unregister_Object(skeleton);
|
||||
delete skeleton;
|
||||
return root;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// RecurseSKLFile -- one page of the skeleton, then its children.
|
||||
//#############################################################################
|
||||
//
|
||||
dpl_DCS *
|
||||
BTL4VideoRenderer::RecurseSKLFile(
|
||||
Entity *entity,
|
||||
dpl_DCS *parent_dcs,
|
||||
NotationFile *skeleton,
|
||||
const char *page_name,
|
||||
int recursion_depth,
|
||||
ViewFrom view_type,
|
||||
dpl_ZONE *zone,
|
||||
int *node_count,
|
||||
int *object_count)
|
||||
{
|
||||
Check(this);
|
||||
Check(skeleton);
|
||||
Check_Pointer(page_name);
|
||||
|
||||
if (!skeleton->PageExists(page_name))
|
||||
{
|
||||
DEBUG_STREAM << "[skl] missing page '" << page_name << "'\n" << flush;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//
|
||||
// A guard, not a limit: the file is authored data and a bad parent= chain
|
||||
// could otherwise recurse forever. The deepest real chain in MAD.SKL is
|
||||
// nowhere near this.
|
||||
//
|
||||
if (recursion_depth > 32)
|
||||
{
|
||||
DEBUG_STREAM << "[skl] recursion too deep at '" << page_name
|
||||
<< "'\n" << flush;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dpl_DCS
|
||||
*dcs = dpl_NewDCS();
|
||||
Check_Pointer(dcs);
|
||||
dpl_SetDCSZone(dcs, zone);
|
||||
|
||||
//
|
||||
// STAGE 2a -- the node's LOCAL TRANSLATION.
|
||||
//
|
||||
// Slot convention, derived rather than guessed. A DCS flush body is
|
||||
// [remote][type_check][node][pad][16 x float32]: our own identity
|
||||
// matrices were found on the wire at body offset 16, which also
|
||||
// showed analyze_scene.py's rest[4:68] read to be one word early.
|
||||
// Re-reading a real BT capture with that correction gives a clean
|
||||
// identity with the translation in the LAST ROW -- a row-vector
|
||||
// convention, the transpose of MUNGA's AffineMatrix (which is 3x4
|
||||
// row-major with translation in the 4th COLUMN, entries[3/7/11], per
|
||||
// AFFNMTRX.CPP:50).
|
||||
//
|
||||
// Rotation is deliberately left identity for now: every base-pose
|
||||
// pitch/yaw/roll in MAD.SKL is 0 or ~1e-3, so TRANSLATION alone
|
||||
// assembles the model -- which isolates this one convention and
|
||||
// makes a wrong guess about rotation order impossible to mistake for
|
||||
// a wrong guess about translation.
|
||||
//
|
||||
Scalar
|
||||
tran_x = 0.0f,
|
||||
tran_y = 0.0f,
|
||||
tran_z = 0.0f;
|
||||
skeleton->GetEntry(page_name, "tranx", &tran_x);
|
||||
skeleton->GetEntry(page_name, "trany", &tran_y);
|
||||
skeleton->GetEntry(page_name, "tranz", &tran_z);
|
||||
|
||||
float32
|
||||
matrix[16];
|
||||
int
|
||||
i;
|
||||
for (i = 0; i < 16; ++i)
|
||||
{
|
||||
matrix[i] = 0.0f;
|
||||
}
|
||||
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0f;
|
||||
//
|
||||
// Translation goes in the 4th COLUMN (3/7/11), the same layout as
|
||||
// MUNGA's own AffineMatrix (3x4 row-major, entries[3/7/11] --
|
||||
// AFFNMTRX.CPP:50). Putting it in the last ROW instead made the
|
||||
// matrix PROJECTIVE under a column-vector convention and the
|
||||
// transform maths blew up: the walk died on the first node that
|
||||
// carries geometry, reproducibly, and bisecting with the reads kept
|
||||
// but the values suppressed ran clean. Same library house as
|
||||
// MUNGA, same convention.
|
||||
//
|
||||
matrix[3] = (float32)tran_x;
|
||||
matrix[7] = (float32)tran_y;
|
||||
matrix[11] = (float32)tran_z;
|
||||
dpl_SetDCSMatrix(dcs, matrix);
|
||||
|
||||
if (parent_dcs != NULL)
|
||||
{
|
||||
dpl_AddDCSToDCS(parent_dcs, dcs);
|
||||
}
|
||||
else
|
||||
{
|
||||
dpl_AddDCSToScene(dcs);
|
||||
}
|
||||
++(*node_count);
|
||||
|
||||
//
|
||||
// This node's geometry, if it has any. Entity_Being_Created is already
|
||||
// set by our caller so the library's C callback can tag the geometry with
|
||||
// damage zones (L4VIDEO.CPP:4176).
|
||||
//
|
||||
const char
|
||||
*object_name;
|
||||
if (skeleton->GetEntry(page_name, "Object", &object_name) && object_name)
|
||||
{
|
||||
dpl_OBJECT
|
||||
*object = dpl_LoadObject((char *)object_name, dpl_load_normal);
|
||||
if (object != NULL)
|
||||
{
|
||||
dpl_INSTANCE
|
||||
*instance = dpl_NewInstance();
|
||||
Check_Pointer(instance);
|
||||
dpl_SetInstanceObject(instance, object);
|
||||
dpl_AddInstanceToDCS(dcs, instance);
|
||||
dpl_FlushInstance(instance);
|
||||
++(*object_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << "[skl] couldn't load object " << object_name
|
||||
<< " for '" << page_name << "'\n" << flush;
|
||||
}
|
||||
}
|
||||
|
||||
dpl_FlushDCS(dcs);
|
||||
|
||||
//
|
||||
// Children. Repeated "joint=" entries: the entry NAME is "joint" and the
|
||||
// VALUE (dataReference) is the child page -- the same shape as the
|
||||
// engine's objectpath= walk at L4VIDEO.CPP:1858.
|
||||
//
|
||||
NameList
|
||||
*children = skeleton->MakeEntryList(page_name, "joint");
|
||||
if (children != NULL)
|
||||
{
|
||||
Register_Object(children);
|
||||
|
||||
NameList::Entry
|
||||
*entry;
|
||||
for (entry = children->GetFirstEntry();
|
||||
entry != NULL;
|
||||
entry = entry->GetNextEntry())
|
||||
{
|
||||
const char
|
||||
*child_page = (const char *)entry->dataReference;
|
||||
if (child_page != NULL && *child_page != '\0')
|
||||
{
|
||||
RecurseSKLFile(
|
||||
entity, dcs, skeleton, child_page, recursion_depth + 1,
|
||||
view_type, zone, node_count, object_count);
|
||||
}
|
||||
}
|
||||
|
||||
Unregister_Object(children);
|
||||
delete children;
|
||||
}
|
||||
|
||||
return dcs;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// MakeEntityRenderables -- the game level of the renderable factory.
|
||||
@@ -98,6 +363,55 @@ void
|
||||
|
||||
switch (entity->GetClassID())
|
||||
{
|
||||
case RegisteredClass::MechClassID:
|
||||
//
|
||||
// The mech's video resource is a SKELETON. Walk the chain the
|
||||
// same way the engine does (L4VIDEO.CPP:4250) and hand every
|
||||
// Skeleton entry to ReadSKLFile; anything else falls through to
|
||||
// the engine, which knows what to do with plain objects.
|
||||
//
|
||||
{
|
||||
if (model_resource == NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ChainOf<L4VideoObjectWrapper*>
|
||||
video_chain(NULL);
|
||||
L4VideoObjectWrapper::BuildVideoObjectChainFromResource(
|
||||
&video_chain, model_resource);
|
||||
|
||||
ChainIteratorOf<L4VideoObjectWrapper*>
|
||||
video_iterator(video_chain);
|
||||
L4VideoObjectWrapper
|
||||
*video_wrapper;
|
||||
Logical
|
||||
handled = False;
|
||||
|
||||
Entity_Being_Created = entity;
|
||||
video_iterator.First();
|
||||
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
|
||||
{
|
||||
const L4VideoObject
|
||||
*video_object = video_wrapper->GetVideoObject();
|
||||
if (video_object->GetResourceType()
|
||||
== L4VideoObject::Skeleton)
|
||||
{
|
||||
ReadSKLFile(entity,
|
||||
video_object->GetObjectFilename(), view_type);
|
||||
handled = True;
|
||||
}
|
||||
}
|
||||
Entity_Being_Created = NULL;
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
DPLRenderer::MakeEntityRenderables(
|
||||
entity, model_resource, view_type);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RegisteredClass::BTPlayerClassID:
|
||||
//
|
||||
// No graphics -- the player is a control/scoring entity.
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
# include <l4video.hpp>
|
||||
# endif
|
||||
|
||||
# include <dpl.h>
|
||||
# include <dplutils.h>
|
||||
|
||||
//###########################################################################
|
||||
//####################### BTL4VideoRenderer #############################
|
||||
//###########################################################################
|
||||
@@ -53,6 +56,30 @@
|
||||
ResourceDescription *model_resource,
|
||||
ViewFrom view_type);
|
||||
|
||||
//
|
||||
// The mech's model resource is a SKELETON, not an object, and the
|
||||
// engine rejects it ("wrong video resource type") because building
|
||||
// one is the GAME renderer's job. Shape taken from the surviving
|
||||
// sibling header CODE/RP/RP_L4/RPL4VID.HPP.
|
||||
//
|
||||
dpl_DCS *
|
||||
ReadSKLFile(
|
||||
Entity *entity,
|
||||
const char *skeleton_filename,
|
||||
ViewFrom view_type);
|
||||
|
||||
dpl_DCS *
|
||||
RecurseSKLFile(
|
||||
Entity *entity,
|
||||
dpl_DCS *parent_dcs,
|
||||
NotationFile *skeleton,
|
||||
const char *page_name,
|
||||
int recursion_depth,
|
||||
ViewFrom view_type,
|
||||
dpl_ZONE *zone,
|
||||
int *node_count,
|
||||
int *object_count);
|
||||
|
||||
protected:
|
||||
int reserved[16];
|
||||
};
|
||||
|
||||
@@ -726,3 +726,53 @@ and rejects our mech with "wrong video resource type for object mad.skl"
|
||||
(L4VIDEO.CPP:4265) because it only accepts L4VideoObject::Object / Rubble. So
|
||||
the skeleton arrives as a different L4VideoObject::ResourceType -- find that
|
||||
enumerator and branch on it in our override rather than re-deriving the name.
|
||||
|
||||
================================================================================
|
||||
THE MECH SKELETON IS BUILT. And the intermittent crash is now THE blocker.
|
||||
================================================================================
|
||||
BTL4VideoRenderer now answers MechClassID: it walks the video-object chain the
|
||||
way the engine does, hands every L4VideoObject::Skeleton entry to ReadSKLFile,
|
||||
and recurses the .SKL into a dpl_DCS tree with the geometry hung off it.
|
||||
|
||||
VERIFIED, repeatedly, on the live pod:
|
||||
|
||||
[skl] video\mad.skl -> 26 nodes, 19 objects
|
||||
no "wrong video resource type" complaint, no geometry load failures
|
||||
|
||||
and those counts are exactly what the file declares -- MAD.SKL has 25 "joint="
|
||||
entries (+1 root = 26) and 19 "Object=" entries. Every joint page became a
|
||||
DCS, every Object= became a loaded instance.
|
||||
|
||||
CORRECTION TO THE EARLIER SUCCESS CRITERION: this file's note previously said
|
||||
the walk should produce 22 instances, reading the dpl3-revive capture's
|
||||
"instance x22" against MAD.SKL's DZoneCount=22. That was wrong -- damage
|
||||
zones are not geometry. There are 28 dzone= tags spread across 19 objects
|
||||
(jointshakey alone carries 8 on one .bgf). The right criterion is the file's
|
||||
own joint=/Object= counts, which we hit exactly.
|
||||
|
||||
TRANSLATIONS ARE IN, and the slot question is NOT yet settled: translation is
|
||||
currently written to matrix[3]/[7]/[11] (MUNGA's AffineMatrix layout -- 3x4
|
||||
row-major, translation in the 4th column, AFFNMTRX.CPP:50). It builds and
|
||||
walks cleanly, but no frame has yet been seen WITH the mech, so the choice is
|
||||
unconfirmed. Rotation is still identity on purpose (every base-pose
|
||||
pitch/yaw/roll in MAD.SKL is 0 or ~1e-3, so translation alone assembles the
|
||||
model and isolates one convention at a time).
|
||||
|
||||
!! A BISECT THAT LIED, worth reading before trusting any pod result !!
|
||||
I concluded from single runs that non-identity translations crashed the pod,
|
||||
"isolated" it by suppressing the values, and then "confirmed" the other slot
|
||||
choice also crashed. All of that was NOISE. Re-running the SAME binary gave
|
||||
walk-completes / crash / walk-completes / crash. The intermittent
|
||||
plane-write defect ("Reference to a page you don't own", PF at 66D9 -- the
|
||||
one with seven hypotheses already killed) is now firing on roughly HALF of
|
||||
pod runs, and it lands at different points each time, which is exactly what
|
||||
made it look like a deterministic matrix bug.
|
||||
|
||||
RULE FOR THIS RIG: never accept a single pod run as evidence. Run the
|
||||
same binary at least twice and require agreement, or the intermittent
|
||||
defect will invent a story for you.
|
||||
|
||||
That defect has therefore gone from background annoyance to the top of the
|
||||
list: it is what stops a mech frame from ever being seen, because a run has
|
||||
to survive BOTH the skeleton build AND the launch to render anything, and at
|
||||
~50% per run that is a coin flip on a four-minute cycle.
|
||||
|
||||
Reference in New Issue
Block a user