diff --git a/context/combat-damage.md b/context/combat-damage.md index 8935272..61dd9d9 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -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] diff --git a/context/open-questions.md b/context/open-questions.md index be3b5f9..c781591 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -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 — diff --git a/game/reconstructed/btl4vid.cpp b/game/reconstructed/btl4vid.cpp index 03f0fa7..cdae040 100644 --- a/game/reconstructed/btl4vid.cpp +++ b/game/reconstructed/btl4vid.cpp @@ -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::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; + } + } + } } diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 0ee997c..eff4cf2 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -6025,6 +6025,19 @@ void static float s_zwT = 1.0e9f; static int s_zwZone = -1; static int s_zwShots = 0; + // BT_WALK_ZONES=: 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=" diff --git a/scratchpad/night10/legwalk.sh b/scratchpad/night10/legwalk.sh new file mode 100644 index 0000000..575d103 --- /dev/null +++ b/scratchpad/night10/legwalk.sh @@ -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