#87 dig closure: host response is linear (raw decomp re-read); the pixel curve was div-card firmware

Re-read the material damage watcher RAW (@004573e4 ctor / @00457784
change-push): 16 floats snapshotted, damaged = pristine x 0.1 (literal at
the MakeMechRenderables call site, watching zone+0x158), linear lerp, 13
floats written back, then FUN_0048d4d4 -- which is "flush_material": a dpl
command-stream marshal (opcode 100) to the i860 division card. The
material->ramp->texel response curve was CARD firmware and is not in
BTL4OPT.EXE; logged as an open question (ask: firmware/DIV docs/period
screenshots).

Field reconciliation, measured live: one 25-pt hit (ERLG class) snaps a
77-pt arm 0 -> 0.3247 in a frame -- "a single missile leaves visible
armor damage" holds under the linear curve for heavy hits; 2-pt ERS creep
(the leg-audit weapon) is the invisible case.

Bench additions: BT_WALK_ZONES=<name-list> walker filter (concentrated
per-region audits, scratchpad/night10/legwalk.sh) + BindArmourDamage
ORPHAN inventory under BT_ARMOR_LOG (proved every drawn op is
zone-claimed on the full tree).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-03 13:10:00 -05:00
co-authored by Claude Fable 5
parent fe48accb6b
commit 440cee1e4f
5 changed files with 132 additions and 8 deletions
+18 -6
View File
@@ -1044,12 +1044,24 @@ both sides).
Render-path note (gotcha #23 discharged): the tint itself was pixel-proven with
`BT_ARMOR_FORCE` A/B captures -- all-zones 1.0 renders the hull charcoal (0.1x
floor), so "no discoloration" was never the renderer. Solo/master-side
perception is the authentic ECONOMY: 2-25 pt laser events vs 68-185 pt zone
pools = single-digit % levels = a 2-10% darkening + a doll tick of a few color
indices. The cockpit doll maps all 28 zones (L4GAUGE.CFG:4788-4814 cmArmor
lines -- feet + lower legs included); a "leg shots never show" report is those
zones' levels peaking ~0.08-0.13, not a wiring gap.
floor), forced 0.4 reads visibly grey at close range on a light skin, so "no
discoloration" was never the renderer. Solo/master-side perception is the
authentic ECONOMY: 2-25 pt laser events vs 68-185 pt zone pools. The eye
catches SNAPS, not creep: a single heavy hit (ERLG large laser / missile
class, amt=25) jumps a 77-pt arm 0 -> 0.3247 in one frame (measured live) =
the pod veterans' "a single missile leaves visible armor damage"; 2-pt ERS
fire creeps ~1.6%/hit and stays visually silent for a dozen hits (the
"legs never show" reports -- their levels peak ~0.08-0.13). The cockpit doll
maps all 28 zones (L4GAUGE.CFG:4788-4814 cmArmor lines -- feet + lower legs
included); nothing is unwired (orphan audit 2026-08-03: every drawn op with a
material name is claimed by a zone).
Fidelity boundary [T1]: the HOST binary's response is LINEAR (watcher
@004573e4/@00457784: 13 material floats lerped toward x0.1 by level, then
"flush_material" to the division card). The CARD-side material->ramp->texel
curve is i860 firmware absent from BTL4OPT.EXE -- see [[open-questions]]
"i860 DIVISION-CARD firmware". If pod darkening was perceptually steeper at
mid levels, that steepness lived there.
## The hit-location CYLINDER -- raw-bytes audit + chassis->table map (2026-08-02) [T1]
+14
View File
@@ -22,6 +22,20 @@ authentic path scoped.
further assets exist; the runtime `BTL4.RES` with 8 maps + full anim set is the master we have).
- Pod specifics for Phase 8: the 7-monitor driver setup, the RIO cockpit I/O protocol, current
Win10+wrapper pod config. Status: OPEN.
- **The i860 DIVISION-CARD firmware / DIV render docs (the material→pixel response curve).**
Established 2026-08-03 (#87 dig): the HOST binary lerps 13 material colour floats linearly
toward ×0.1 by zone damageLevel (watcher @004573e4/@00457784, 0.1 literal at the
MakeMechRenderables call site) and then just **marshals the material to the card**
(`FUN_0048d4d4` = "flush_material", dpl command opcode 100 — siblings flush_ramp/flush_texture).
The card-side shading (how material colour selected/scaled the 256-row material ramps → final
texels) is i860 firmware, NOT in BTL4OPT.EXE — so the PERCEPTUAL damage-darkening curve of the
1995 pods cannot be recovered from this binary. Our port applies the host's linear factor at
draw (TFACTOR modulate, L4D3D). Field testimony ("a single missile leaves visible armor
damage") is arithmetically consistent with the linear curve — one 25-pt hit snaps a 68-90-pt
zone by 0.28-0.37 (measured live: amt=25 → rarm 0→0.3247) — but if the card's ramp mapping was
steeper than linear, mid-level creep damage read stronger on the pods than in the port.
ASK: div-card firmware image, DIV/dpl render docs, or period screenshots showing a
known-damage-level mech. Status: OPEN.
- **Did the pod throttle quadrant have MECHANICAL detents** (how many / lever positions)? The
software path is notch-free [T1, [[pod-hardware]]], but the gait SM's walk/run dead band
([[locomotion]]) only makes sense if the hardware discouraged parking the lever mid-band —
+27
View File
@@ -1451,9 +1451,36 @@ void
}
if (log)
{
DEBUG_STREAM << "[armor] entity " << (void *)entity << " skel="
<< tree.skeletonType << ": " << tree.dmgBinds.size()
<< " material binding(s)\n" << std::flush;
// ORPHAN inventory: drawn ops NO zone claimed. These panels never
// darken -- if the big hull materials land here (a .DZM name the mesh
// doesn't carry, or per-zone material instancing collapsed at load),
// the mech reads "no darkening" live even while every bound op logs
// perfectly (the #87 visibility gap).
for (std::map<int, MechRenderTree::SegPick>::iterator si = tree.segPick.begin();
si != tree.segPick.end(); ++si)
{
d3d_OBJECT *obj = si->second.obj;
if (obj == NULL)
continue;
for (int op = 0; op < obj->GetDrawOpCount(); ++op)
{
L4DRAWOP *draw_op = obj->GetDrawOp(op);
if (draw_op->dzMatName[0] == '\0')
continue;
int claimed = 0;
for (size_t b = 0; b < tree.dmgBinds.size() && !claimed; ++b)
if (tree.dmgBinds[b].obj == obj && tree.dmgBinds[b].op == op)
claimed = 1;
if (!claimed)
DEBUG_STREAM << "[armor] ORPHAN op seg=" << si->first
<< " '" << draw_op->dzMatName << "'\n" << std::flush;
}
}
}
}
+39 -2
View File
@@ -6025,6 +6025,19 @@ void
static float s_zwT = 1.0e9f;
static int s_zwZone = -1;
static int s_zwShots = 0;
// BT_WALK_ZONES=<comma-list>: restrict the walk to zones whose NAME
// contains one of the tokens (e.g. "leg,foot,hip" -- the concentrated
// per-region audit; repeated passes pile damage into few zones so the
// doll movement is unmistakable). Empty/absent = walk everything.
static const char *s_zwFilter = 0;
static int s_zwFilterInit = 0;
if (!s_zwFilterInit)
{
s_zwFilterInit = 1;
s_zwFilter = getenv("BT_WALK_ZONES");
if (s_zwFilter != 0 && *s_zwFilter == '\0')
s_zwFilter = 0;
}
if (s_zwPeriod <= 0.0f)
{
s_zwPeriod = (float)atof(getenv("BT_ZONE_WALK"));
@@ -6119,8 +6132,32 @@ void
s_zwZone = -1; // wrap
continue;
}
if (segIdx >= 0)
break;
if (segIdx < 0)
continue;
if (s_zwFilter != 0) // BT_WALK_ZONES name filter
{
int match = 0;
const char *tok = s_zwFilter;
while (*tok != '\0' && !match)
{
const char *end = tok;
while (*end != '\0' && *end != ',')
++end;
char buf[64];
int len = (int)(end - tok);
if (len > 0 && len < (int)sizeof(buf))
{
memcpy(buf, tok, len);
buf[len] = '\0';
if (strstr(zname, buf) != 0)
match = 1;
}
tok = (*end == ',') ? end + 1 : end;
}
if (!match)
continue;
}
break;
}
DEBUG_STREAM << "[walk] ZONE " << s_zwZone << " '" << zname
<< "' seg=" << segIdx << " target="
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# LEG/FOOT AUDIT -- the zone-walk restricted to the six leg/foot zones
# (BT_WALK_ZONES name filter), so repeated passes concentrate fire and the
# victim's ARMOR DAMAGE doll movement is unmistakable. Node B captures its
# OWN screen every ~10 s (zwB_*.png) -- the doll's climb is in the pixel
# record; node A's captures (zwA_*.png) record the hull darkening the
# observer now receives (the fe48acc level-crossing send).
# READ: python scratchpad/night10/digest_walk.py (zw_a/zw_b logs as usual)
set -x
. /c/git/bt411/scratchpad/night6/bench_common.sh
cd /c/git/bt411/content || exit 1
bt_assert_player_env
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
rm -f zw_a.log zw_b.log zwA_*.png zwB_*.png
bt_expert_egg MP.EGG ZW.EGG
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=madcat/" ZW.EGG
# TARGET first (right/back window): spins, logs every damage application,
# films its own cockpit (the doll is bottom-center of every zwB frame).
BT_SPIN_SELF=15 BT_DMG_LOG=1 BT_DEATH_LOG=1 BT_MP_LOG=1 BT_MATCHLOG=1 \
BT_SHOT_EVERY=300 BT_SHOT_PREFIX=zwB \
bt_launch zw_b.log ZW.EGG 0x0C -net 1601
sleep 2
# SHOOTER second (foreground -- owns the pick focus): legs + feet only,
# 5 s per zone => a full 6-zone cycle every ~30 s.
BT_ZONE_WALK=5 BT_WALK_ZONES=leg,foot BT_PICK_LOG=1 \
BT_GOTO=enemy BT_GOTO_STOP=90 BT_KEY_NOFOCUS=1 \
BT_DMG_LOG=1 BT_RANGE_LOG=1 BT_MP_LOG=1 BT_MATCHLOG=1 \
BT_ARMOR_LOG=1 BT_SHOT_EVERY=300 BT_SHOT_PREFIX=zwA \
bt_launch zw_a.log ZW.EGG 0x03 -net 1501
sleep 5
python ../tools/btconsole.py ZW.EGG 127.0.0.1:1501 127.0.0.1:1601 > zw_relay.log 2>&1 &
sleep 3600 # session stays up for observation