KB sweep: the game is NOT '1995' -- era witnesses say BT 4.10 shipped later, and the 1995 belief traced to the MUNGA library RCS strings inside BTL4OPT.EXE (library code date, not the game; the exe has no game-copyright year). ~120 era references across CLAUDE.md/context/glossary/handout rewritten to 'the original'/'the shipped machine'; genuine dated facts kept (RCS $Id strings, Borland 1994, the 30-Nov-1995 asset timestamp); dating note + evidence added to project-overview.md
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
> detail is preserved verbatim in **`docs/PROGRESS_LOG.md`** (the old monolithic CLAUDE.md) — the
|
||||
> ultimate fallback if a topic digest is thin. Migration history: `phases/phase-01-context-restructure.md`.
|
||||
|
||||
**Project:** Port VWE's 1995 arcade **BattleTech** (BT) pod game (MUNGA engine, Tesla platform
|
||||
**Project:** Port VWE's arcade **BattleTech** (BT) pod game (MUNGA engine, Tesla platform
|
||||
4.10) to modern Windows + the pod hardware, by reconstructing the missing BT game logic from the
|
||||
`BTL4OPT.EXE` binary on top of the working WinTesla engine.
|
||||
**Repo of record:** the top-level `CMakeLists.txt` + `README.md` build `btl4.exe`. Layout:
|
||||
@@ -24,13 +24,13 @@ POLISH list (#156 fade, weapon-state presentation). Details + what's-next:
|
||||
|
||||
## YOU ARE THE BATTLETECH PORT EXPERT
|
||||
|
||||
**You (Claude) are the expert on this port** — the 1995 MUNGA/BT engine, its asset formats, the
|
||||
**You (Claude) are the expert on this port** — the original MUNGA/BT engine, its asset formats, the
|
||||
decompilation, and the reconstruction state. The `context/*.md` files, `reference/glossary.yaml`,
|
||||
`reference/decomp/` (the raw pseudocode), and the `docs/*.md` ledgers are YOUR knowledge base.
|
||||
|
||||
**ALWAYS consult the context system before answering a question about the engine, formats, a
|
||||
subsystem, or the reconstruction state — and before writing reconstruction code.** Do not rely on
|
||||
training data; this is a specific, reverse-engineered 1995 codebase. The knowledge here is more
|
||||
training data; this is a specific, reverse-engineered 1990s codebase. The knowledge here is more
|
||||
precise than anything you can infer.
|
||||
|
||||
### How to answer a question
|
||||
@@ -93,7 +93,7 @@ precise than anything you can infer.
|
||||
| Offsets, ClassIDs, addresses, resource types, env vars | `context/decomp-reference.md` |
|
||||
| What we don't know / deferred systems | `context/open-questions.md` |
|
||||
| Term / acronym definitions | `reference/glossary.yaml` |
|
||||
| The ORIGINAL 1995 player manual (controls, per-mech stats, coolant loops) | `reference/manual/Tesla40_BT_manual.pdf` (+ alignment audit in `context/pod-hardware.md` §Manual) |
|
||||
| The ORIGINAL player manual (controls, per-mech stats, coolant loops) | `reference/manual/Tesla40_BT_manual.pdf` (+ alignment audit in `context/pod-hardware.md` §Manual) |
|
||||
| The complete verbatim detail (fallback) | `docs/PROGRESS_LOG.md` (the old 2236-line CLAUDE.md) |
|
||||
| Detailed running ledgers | `docs/RECONCILE.md`, `docs/GAUGE_COMPOSITE.md`, `docs/HARD_PROBLEMS.md`, `docs/SUBSYS_PLAN.md`, `docs/P3_LOCOMOTION.md`, `docs/RESOURCE_AUDIT.md`, `docs/VEHICLE_SUBSYSTEMS.md`, `docs/BGF_FORMAT.md`, `docs/ASSET_PIPELINE.md`, `docs/BT_SOURCE_STATUS.md`, `docs/WAVE_PLAN.md`, `docs/GLASS_COCKPIT.md`, `docs/REVOLVING_DOOR_PLAN.md`, `docs/INPUT_PATH_AUDIT.md`, `docs/RESPAWN_REARM_PLAN.md`, `docs/KD_SCOREBOARD_PLAN.md`, `docs/DIST_LAYOUT_PLAN.md`, `docs/MOUSELOOK_PLAN.md`, `docs/GHOST_MECH_ANALYSIS.md`, `docs/AUDIO_FIDELITY.md`; multi-agent audit ledgers (2026-08-11): `docs/KD_CHAIN_AUDIT_20260811.md`, `docs/MISSION_END_AUDIT_20260811.md`, `docs/RAM_LEAK_AUDIT_20260811.md`, `docs/GEN_WARNING_AUDIT_20260811.md` |
|
||||
|
||||
@@ -119,7 +119,7 @@ Tag individual claims (not sections) inline: `[T1]`, `[T2]`, … A claim inherit
|
||||
---
|
||||
|
||||
## Conventions (CRITICAL — full detail in `context/reconstruction-gotchas.md`)
|
||||
- **Never raw-read a compiled object's offsets** (`*(T*)(obj+0xNN)`) — our layout ≠ the 1995 binary.
|
||||
- **Never raw-read a compiled object's offsets** (`*(T*)(obj+0xNN)`) — our layout ≠ the original binary.
|
||||
Use named members / accessors / a **bridge** fn in a complete-type TU. (databinding trap)
|
||||
- **Never re-declare an engine-base field** — it shadows the base (reads `0xCDCDCDCD`) + mis-offsets.
|
||||
- **`/FORCE` hides unresolved symbols** — an unresolved external → runtime AV near `__ImageBase`, not
|
||||
|
||||
@@ -101,7 +101,7 @@ One shared 128×64 texmap: `bmap:` → `VIDEO/MAT/BMAP.BMF` → 6 `TEXTURE` reco
|
||||
`LoadBitSliceTexture` merges a 1bpp egg BitMap into it; `FlushBitSliceTexture`
|
||||
(L4VIDEO.cpp:5682, stubbed in 2007) did `dpl_LookupTexture("bmap:bmap1_tex")` +
|
||||
`dpl_TexmapTexels2D(texmap, buf, 128, 64, 4)` — one upload rewrites ALL SIX slices. So the
|
||||
1995 engine painted the **egg's callsign + ordinal bitmaps** onto these plates every mission;
|
||||
original engine painted the **egg's callsign + ordinal bitmaps** onto these plates every mission;
|
||||
the baked "PLAYER n" text is only the shipped DEFAULT. Two stacked 128×32 large-name bitmaps
|
||||
per slice = exactly the PNAME v-halves; two stacked 128×32 ordinal bitmaps ("1st|2nd") = exactly
|
||||
the PLACE 2×2 grid. Corollaries:
|
||||
|
||||
@@ -86,7 +86,7 @@ run\run.cmd [EGG] # default DEV.EGG; cd's to content\ and runs btl4.exe -eg
|
||||
chain end including a normal quit — it primes players to report quits as crashes; #157 ("did not
|
||||
exit cleanly") was exactly this echo. The arcade-authentic round-end (mission process exits →
|
||||
menu relaunch) itself reads as a crash to first-night players.
|
||||
- **Why this is a BT411-only hazard [T1].** The 1995 pod shipped ONE folder — `BTL4OPT.EXE` sits
|
||||
- **Why this is a BT411-only hazard [T1].** The original pod shipped ONE folder — `BTL4OPT.EXE` sits
|
||||
next to `BTL4.RES`/`VIDEO\`/`GAUGE\`/`AUDIO\` (it is still there in `content\`) — and RP411/RP412
|
||||
keep that shape (`pack-dist.ps1` copies the exe and every asset dir into one dist root). BT411's
|
||||
two-folder split (`build\Release\btl4.exe` + `content\`) is not a design decision: `mkdist.py`
|
||||
@@ -97,7 +97,7 @@ run\run.cmd [EGG] # default DEV.EGG; cd's to content\ and runs btl4.exe -eg
|
||||
authors.** The cwd guard stays either way (it protects the developer tree, where the split is
|
||||
real and permanent).
|
||||
- Interactive: ⚠ under the GLASS profile (`content\bindings.txt`, the default since the cyd
|
||||
merge) drive is the **1995 throttle-lever scheme**, NOT WASD: **SHIFT** = throttle up (lever
|
||||
merge) drive is the **original throttle-lever scheme**, NOT WASD: **SHIFT** = throttle up (lever
|
||||
STICKS where you leave it), **CTRL** = throttle down, **ALT** = reverse thrust, **X** =
|
||||
all-stop, **BACKTICK** = view toggle. ("WASD drive" was the pre-glass dev profile — telling a
|
||||
glass user W is the confirmed way to watch a mech stand perfectly still, 2026-07-29.)
|
||||
@@ -183,7 +183,7 @@ stamps `build=4.11.<n> (<hash>)`, so an archived PDB matches a tester's log exac
|
||||
- **`tools/`** — btconsole.py, disas2.py, map/res scanners. **`context/`** — this knowledge base.
|
||||
|
||||
## Versioning (2026-07-18)
|
||||
`4.10` = the 1995 arcade release; `4.11` = this win32 reconstruction; dev builds =
|
||||
`4.10` = the arcade release; `4.11` = this win32 reconstruction; dev builds =
|
||||
**`4.11.<git commit count>` + short hash**, `+` suffix = built from an uncommitted tree
|
||||
(e.g. `4.11.311 (980c9cd+)`). Stamped every build by `tools/btversion.cmake` →
|
||||
`build/btversion.h` (`BT_VERSION_*` macros); shown in the boot banner (day-log head) and
|
||||
|
||||
+17
-17
@@ -53,7 +53,7 @@ section). Counting the enum gives `ThermalSightClassID = 0xBD9` and `TurretClass
|
||||
(0xBD6) and Searchlight (0xBD8) but disagrees on MechTech (enum 0xBDD vs CLASSMAP 0xBDC), so the
|
||||
port's `VDATA.h` ordering has drifted from the shipped binary somewhere in this range. CLASSMAP is
|
||||
binary-derived and wins for what the runtime does; the true numeric value of `TurretClassID` in the
|
||||
1995 binary is **unknown**. Resolve by disassembling the ClassID each ctor stores before relying on it.
|
||||
original binary is **unknown**. Resolve by disassembling the ClassID each ctor stores before relying on it.
|
||||
|
||||
## No AI — BT is PvP-only (verified, not a recovery gap)
|
||||
The mission system is NETWORKED and BT never had NPCs/AI. Every mech is driven by a `BTPlayer` (or a
|
||||
@@ -85,10 +85,10 @@ unlocated** [T4 candidates]: the un-exported stretch `0x4a1674-0x4a2d48` (touche
|
||||
together near entry `0x4a2971`, which is called from `0x494483`), the two unidentified Mech vtable
|
||||
overrides `+0x18=0x4a122c` / `+0x1c=0x4a0c2c` (both big switch functions), and lone sites
|
||||
`0x4877a4` (byte read via the mission entity table `0x5015c8`), `0x4b0097`, `0x45fb14-24`.
|
||||
**RESOLVED (same dig, deeper): there IS no software pick writer — the 1995 pick was a DPL SCENE
|
||||
**RESOLVED (same dig, deeper): there IS no software pick writer — the original pick was a DPL SCENE
|
||||
INTERSECTION** [T0 + T1 converging]. Evidence: (a) the WinTesla renderer still carries the result
|
||||
members `dplHitInstance/dplHitDCS/dplHitGeoGroup/dplHitGeometry` + `vehicleReticle` (L4VIDEO.cpp
|
||||
ctor, all NULL-init); (b) the stubbed 1995-era renderable constructors each took
|
||||
ctor, all NULL-init); (b) the stubbed original-era renderable constructors each took
|
||||
**`dpl_isect_mode_obj` ("type of intersections to do on this object") + an intersection MASK** —
|
||||
per-renderable intersection configuration against the scene; (c) Auric's account ("the pod's
|
||||
division card cast from the view"); (d) VGL Lynx's LOD warning reads as firsthand knowledge — the
|
||||
@@ -97,7 +97,7 @@ binary or the pseudocode ever writes `rayIntersection/targetEntity/targetDamageZ
|
||||
only reads, initializes (the Mech ctor @0x4a1674 constructs the embedded Reticle), gates
|
||||
(`FUN_004afd10`, the look-state machine, toggles `pickPointingOn` per view with the π rear case),
|
||||
and SUBMITS the reticle to the board for drawing (`FUN_00460a7c` packages +0x37c/+0x390 into dpl).
|
||||
⇒ **Aimed fire in 1995 had PER-PART precision**: scene ray → struck triangle on the current LOD →
|
||||
⇒ **Aimed fire in the original had PER-PART precision**: scene ray → struck triangle on the current LOD →
|
||||
the DCS = the struck SEGMENT → its `dzone` (the SKL segment→zone map, `GetSegmentIndex@49db20`) =
|
||||
`targetDamageZone`. The cylinder lottery (STEP 6) was only ever the UNAIMED path. The port's
|
||||
whole-mech box pick + "STEP-6 zone under the boresight" funnels AIMED fire through the unaimed
|
||||
@@ -127,7 +127,7 @@ patch CENTROIDS so the zone walker can exercise per-panel. Bench: [[test-harnes
|
||||
drawn geometry -- the any-object SPHERE fallback and the caller's whole-mech AABB fallback are
|
||||
gone (sphere only for a mesh the reader cannot parse -- none exist, counters clean; AABB only as
|
||||
the pre-tree replicant grace, `BTMechSegmentPick` returns 1/0/-1). They were port stand-ins the
|
||||
1995 card never had, and their signature regime was a LEVEL boresight over a SHORT mech: the
|
||||
original card never had, and their signature regime was a LEVEL boresight over a SHORT mech: the
|
||||
blackhawk's mesh tops out below eye-ray height, so rays cleared every triangle while threading
|
||||
the fat cull spheres -- lock ring lit with the reticle above its head (night-12 field report,
|
||||
operator-witnessed on the sweep bench). Instruments: `BT_LOCK_SWEEP` (torso-pan lock envelope,
|
||||
@@ -156,7 +156,7 @@ stale). **Selection is SPECIFICITY-FIRST: among pierced spheres the SMALLEST rad
|
||||
mesh's sphere (r≈4.1 on the MadCat vs shoulders r≈1.0) envelops the mech, so nearest-entry always
|
||||
faces you AND normalized distance rewards the giant sphere (a 0.45u-off-axis ray scores 0.012 vs
|
||||
it). Limbs nest inside the envelope; smallest-pierced picks the most specific part on the aim line
|
||||
— the per-part semantic the 1995 mesh intersection produced. `mech4.cpp` tries the segment pick
|
||||
— the per-part semantic the original mesh intersection produced. `mech4.cpp` tries the segment pick
|
||||
per candidate (box `PickRayHit` + zone −1 → the unaimed lottery survives only as fallback:
|
||||
no tree / wrecked / structure occlusion), and the winner's zone rides `MECH_TARGET_SUBIDX` +
|
||||
`targetReticle.targetDamageZone` into `SendDamageMessage` — aimed hits now dispatch a REAL zone.
|
||||
@@ -333,7 +333,7 @@ an enclosing box), no crash. The interactive boresight (`BTGetAimRay`) can't be
|
||||
|
||||
## Firing arc + muzzles
|
||||
There is NO firing-arc field in the binary (`Emitter::FireWeapon` fires whenever HasActiveTarget) —
|
||||
the 1995 game got away with it because it was **cockpit-only** (a beam behind you is off-camera). The
|
||||
the original game got away with it because it was **cockpit-only** (a beam behind you is off-camera). The
|
||||
port's external camera exposes it, so the arc is gated on the **torso twist range**
|
||||
(`Torso::GetHorizontalReach`, 0 for the twist-disabled Blackhawk) + a base aim tolerance
|
||||
(`BT_FIRE_ARC`, a labeled PORT presentation param). Beams fire from the mech's real gun-port SITE
|
||||
@@ -549,13 +549,13 @@ measuring a PORT bug. The truth [T1, raw disasm @0x4ac7bb + live-verified]:
|
||||
own error strings).
|
||||
Consequences: `MechSubsystem::TakeDamage @0x4ac0bc` (now real, was a stub) accumulates authored
|
||||
per-type damage on subsystems; crits land and destroy subsystems; and **the Myomers un-powered
|
||||
self-repair `@004b8bb9` was LIVE in 1995 and is live in the port now** — the earlier "dead code in
|
||||
self-repair `@004b8bb9` was LIVE in the original and is live in the port now** — the earlier "dead code in
|
||||
the original too" note in [[subsystems]] WAVE 6 is superseded. `DistributeCriticalHit`'s direct
|
||||
`damageLevel = 1.0f` pin remains the ammo-explosion path, not the general mechanism.
|
||||
|
||||
## ⚠ ZONE SELECTION IS A WEIGHTED LOTTERY — pixel-precise limb damage does not exist (2026-07-29) [T1]
|
||||
Issue #73 ("fired only at the left arm, damage credited elsewhere, no crits") is **largely the
|
||||
authored 1995 model, not a bug**. The chain (`dmgtable.cpp`, binary-faithful, byte-verified stream
|
||||
authored original model, not a bug**. The chain (`dmgtable.cpp`, binary-faithful, byte-verified stream
|
||||
format): impact point → `WorldToLocal` → **height layer** (`floor(layerCount·y/heightRef)`) →
|
||||
**pie slice** (`atan2(local.z, local.x)`, optionally rotated by live torso twist —
|
||||
`rotateWithTorso` per layer) → `DamageZonePercentTable::SelectZone()` = **`RandomUnit()` against
|
||||
@@ -616,8 +616,8 @@ btplayer.hpp, static_assert-locked; `scoreAward`=APPLIED tally in all three):
|
||||
`killCount++`: **the #134 panic penalty, live** (bench: `type=2 award=-39.00 kills=0`).
|
||||
- **B @0x4a05d9, not newly killed && tally≠0** → type 0 to the shooter: the **per-hit INFLICTED
|
||||
credit**, and it is LIVE. ⚠ **CORRECTED 2026-08-07** — this entry previously read "the ONLY
|
||||
registered 0x16 receiver Verify-rejects type 0 → 1995 folded an UNINITIALIZED stack float into
|
||||
the shooter's score on every non-lethal hit (real 1995 bug — @0x4c0200 … is in NO table entry:
|
||||
registered 0x16 receiver Verify-rejects type 0 → the original folded an UNINITIALIZED stack float into
|
||||
the shooter's score on every non-lethal hit (real original-binary bug — @0x4c0200 … is in NO table entry:
|
||||
dead code)". **That was wrong**, and build 787 retired the port's per-hit crediting on the
|
||||
strength of it (the scoring regression players reported on 4.11.817). `BTPlayer` overrides
|
||||
**`Dispatch` — vtable `@00513300` slot 3 = `FUN_004bffa0`** — and splits type 0 off BEFORE base
|
||||
@@ -636,7 +636,7 @@ btplayer.hpp, static_assert-locked; `scoreAward`=APPLIED tally in all three):
|
||||
Reports carry the LOOP-ENTRY zone (msg+0x24, never rewritten mid-loop), the vital-wreck flag, and
|
||||
`inflictingSubsystemID` (msg+0x5c, engine T0 name).
|
||||
|
||||
**Score model consequence** [T1, ⚠ REWRITTEN 2026-08-07]: 1995 pod scoring = **per-hit inflicted
|
||||
**Score model consequence** [T1, ⚠ REWRITTEN 2026-08-07]: original pod scoring = **per-hit inflicted
|
||||
credit + kill awards + received-damage penalties + death costs**. The previous text here claimed
|
||||
"No per-hit inflicted credit … inventions riding the dead @0x4c0200 channel; both retired" — that
|
||||
followed from the dead-code misreading corrected in report B above, and retiring the credit IS the
|
||||
@@ -1235,10 +1235,10 @@ code involved; grammar = the binary ctors @0x49ea48/@0x49e5e4). Results:
|
||||
W1|W2 SEAM. (Corrects the earlier #92 comment that put frontal hits in W6.)
|
||||
* **⚠ PORT FRAME ADAPTER (#124, fixed 2026-08-03; corrected same day by the
|
||||
MUZZLE-ANCHOR probe)**: that +Z-forward (and +X-right, from the W0/W7
|
||||
"Right*" names) is the **1995 resolver frame**. OUR engine frame, measured
|
||||
"Right*" names) is the **original resolver frame**. OUR engine frame, measured
|
||||
with the mech's own asymmetric geometry as the anchor (`BT_ASPECT_TEST`:
|
||||
four world-cardinal self-impacts + one at each weapon's physical muzzle):
|
||||
**RIGHT = +X, same as 1995; FORWARD = −Z, flipped** — the LEFT missile pod
|
||||
**RIGHT = +X, same as the original; FORWARD = −Z, flipped** — the LEFT missile pod
|
||||
sits at raw local x=−2.32, the RIGHT at +2.32, and `WorldToLocal` is a clean
|
||||
R(yaw) with no hidden terms (verified at yaws −30°/150°/40°). The frames
|
||||
differ by a **Z-NEGATION ONLY — a reflection** (the first commit's π
|
||||
@@ -1268,7 +1268,7 @@ the per-frame UpdateDeathState and a same-frame direct+splash lethal pair re-ran
|
||||
block (duplicate kill credit + duplicate VehicleDead) — caught by the 4-node bench, fixed by
|
||||
running the once-per-death transition inline at the death tail (1b6d247). Full class write-up:
|
||||
[[reconstruction-gotchas]] §25; stress verification: kd4_bench 9/9/9/9 + kd8_stress 26/26/26/26,
|
||||
SWALLOWED=0 both. The receiver-side dedup remains a TRIPWIRE only (1995 has none; +0x290 never
|
||||
SWALLOWED=0 both. The receiver-side dedup remains a TRIPWIRE only (the original has none; +0x290 never
|
||||
written) — if it ever fires again, a new duplicate source exists upstream.
|
||||
|
||||
## Key Relationships
|
||||
@@ -1278,7 +1278,7 @@ written) — if it ever fires again, a new duplicate source exists upstream.
|
||||
|
||||
## The K/D-score AUTHORITY MODEL -- settled once and for all (2026-08-11, #162) [T1]
|
||||
|
||||
The 1995 chain has ONE emitter and NO receiver dedup; every counter is single-owner.
|
||||
The original chain has ONE emitter and NO receiver dedup; every counter is single-owner.
|
||||
Fixed twice piecemeal (#150 kills, #162 deaths) before the full audit settled it -- do
|
||||
not touch any single limb of this chain again without re-reading this section.
|
||||
|
||||
@@ -1307,7 +1307,7 @@ part_013.c:10519) -- the #81-era port put it in the VehicleDead arm by misattrib
|
||||
disarming the port's defensive latch (corrected 2026-08-11).
|
||||
|
||||
**Replication:** the player update record carries currentScore(+0x1c8) +
|
||||
dropZoneLocation ONLY. killCount/deaths never cross the wire in 1995; each node's
|
||||
dropZoneLocation ONLY. killCount/deaths never cross the wire in the original; each node's
|
||||
scoreboard columns are locally accumulated (the port's SBMIRROR is a deliberate
|
||||
deviation that makes boards consistent).
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
Score@0x4c02e4** (the type-1/2 switch — BT shadows the engine id with its override), **0x17
|
||||
VehicleDead@0x4c05c4, 0x18 MissionStarting@0x4bfbe8, 0x19 MissionEnding@0x4bfc20, 0x1a
|
||||
ScoreUpdate@0x4c02a8** (= Player::NextMessageID). **@0x4c0200 ("ScoreInflicted", type-0-only,
|
||||
Verify line 0x18b) is in NO entry — dead code in 1995.** The 1995 type-0 path therefore lands in
|
||||
Verify line 0x18b) is in NO entry — dead code in the original.** The original type-0 path therefore lands in
|
||||
@0x4c02e4's Verify arm and folds an UNINITIALIZED [ebp-0xc] into currentScore (real bug; port
|
||||
banks 0). @0x4c02e4 internals [T1 raw]: `+0x40==4` MissionEnding gate; registry-find msg+0x34;
|
||||
case 1 = `(ownTonnage/senderTonnage) × role->CalcDamageReceivedScore(basis)` + the
|
||||
@@ -329,7 +329,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
call to engine Player::ScoreMessageHandler @0x42da20 (never dispatched, never on the wire —
|
||||
no matchlog receipt exists; the port's `[deathcost]` DEBUG line is the only witness).
|
||||
**deathPending(+0x290) is NEVER written non-zero anywhere in BTL4OPT.EXE** (three writes,
|
||||
all zero-stores: ctor @0x4c0c07, respawn helper @0x4c01e5, DropZoneReply tail) — 1995 has NO
|
||||
all zero-stores: ctor @0x4c0c07, respawn helper @0x4c01e5, DropZoneReply tail) — the original has NO
|
||||
receiver-side death dedup; the sender's one-shot edge is the whole guarantee. The clear pair
|
||||
`{+0x290=0, +0x258=0}` lives in **FUN_004c012c = DropZoneReply**'s tail (part_013.c:10519-23)
|
||||
— NOT the VehicleDead arm (the #81-era port misattribution, corrected 2026-08-11). Latch
|
||||
@@ -342,7 +342,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
tail; gotcha §25). `advancedDamageOn(+0x264)` ← the mission egg's
|
||||
per-pilot `advancedDamage=` key (every stock egg stamps 1 on every page); Player update
|
||||
record replicates ONLY currentScore(+0x1c8) + dropZoneLocation — killCount/deaths never
|
||||
cross the 1995 wire (the port's #45 record extension mirrors them owner→replicant; benched
|
||||
cross the original wire (the port's #45 record extension mirrors them owner→replicant; benched
|
||||
convergent at 4 and 8 nodes, kd4_bench/kd8_stress).
|
||||
- **HeatableSubsystem** @0x50E41C: {3, "ToggleCooling"→@004ad6f8}. **Disassembled 2026-07-20**
|
||||
(`tools/disas2.py 0x4ad6f8`): a per-subsystem coolant on/off TOGGLE (NOT a multi-level "cycle
|
||||
@@ -360,7 +360,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
GeneratorA-D — unreconstructed).
|
||||
- **Searchlight** @0x51117C: {3, "ToggleLamp"→**@004b838c**}; **ThermalSight** @0x51120C: {3,
|
||||
"ToggleLamp"→**@004b860c**}. ⚠ **CORRECTED 2026-07-25 (#61)** — these were swapped here (and in
|
||||
`searchlight.cpp:51`, `hud.cpp:282`), which propagated a false "1995 latent bug" across four
|
||||
`searchlight.cpp:51`, `hud.cpp:282`), which propagated a false "original latent bug" across four
|
||||
context files; see the WAVE-4 note in [[subsystems]]. The two TUs emit PARALLEL shared-data blocks
|
||||
with identical stride — msg entry, +0x5C attribute entries, +0x84 Performance triple:
|
||||
| TU | msg entry | handler | attributes | Performance |
|
||||
@@ -500,7 +500,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
warning; respawn-with-tripped-gen RTIS (4,0) FIRES it (ghost warning during
|
||||
respawn); crit-death fires enter-Idle one frame late; 4→1 (cooled while switched
|
||||
off) double-fires. The generators' inv+Start is the ONLY inverse-Start fault
|
||||
trigger in the game (census) — a 1995 authoring slip [T4 intent]. Port machinery
|
||||
trigger in the game (census) — an original authoring slip [T4 intent]. Port machinery
|
||||
is faithful end-to-end. **DELIBERATE DEVIATION shipped 2026-08-11 (user-approved):**
|
||||
the AudioStateTrigger ctor (AUDWTHR.cpp) flips the GeneratorState (trigState=4,
|
||||
inv=1) record to inv=0 at stream time — warning now fires at the TRIP, silent at
|
||||
@@ -560,7 +560,7 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
with `scoreAward = −ScenarioRole::specialCaseDeathPenalty` (role+0x20, the 4.10-only
|
||||
key; restored to SCNROLE.h/.cpp — record slot [1] after KillBonus, fail-hard read).
|
||||
Direct call, NOT dispatched — bypasses the BT handlers' type Verifies. NUANCE [T4]:
|
||||
the 1995 engine adds at Player+0x1c8 while every BT scoreboard reads +0x278 — the
|
||||
the original engine adds at Player+0x1c8 while every BT scoreboard reads +0x278 — the
|
||||
pod's death cost may never have displayed; our single-cell port shows it. Shipped
|
||||
content authors NO role keys, so the cost is 0 in the field. **CORRECTED 2026-08-07 —
|
||||
that sentence was wrong on both halves.** The role's scoring fields are not authored via
|
||||
|
||||
@@ -14,7 +14,7 @@ open_questions:
|
||||
# Experience levels / simulation modes
|
||||
|
||||
The egg's per-pilot `experience` field is the pod's **simulation-fidelity tier** — the thing the
|
||||
1995 site operator set per player at sign-up. It is NOT the BAS/MID/ADV control mode (a free
|
||||
original site operator set per player at sign-up. It is NOT the BAS/MID/ADV control mode (a free
|
||||
in-cockpit choice, [[locomotion]]/[[pod-hardware]]) and NOT the scenario role (scoring modifiers).
|
||||
Researched end-to-end 2026-07-18; this file supersedes the older "role display toggles" /
|
||||
"ROOKIE-role lockout" readings (see Corrections below).
|
||||
|
||||
@@ -333,7 +333,7 @@ screen paint over the shared Eng plane, pinning it on the highest screen). Pixel
|
||||
lists SCORING players (flag cleared when the mech links its player, mech.cpp:688, all
|
||||
nodes). Both loops now use `Player::IsScoringPlayer()`; also closes a `pilotIDs[1]` heap
|
||||
overrun the undercount exposed.
|
||||
2. **Latch race**: the binary's build-once latch assumes 1995 synchronous pod loads; over
|
||||
2. **Latch race**: the binary's build-once latch assumes the original's synchronous pod loads; over
|
||||
async relay TCP the faster node ticks InterpretControls before the peer's replicants
|
||||
arrive (rig-proven: staggered node logged roster 1 → then peer arrival). [T3
|
||||
accommodation, demand-latch precedent]: rebuild whenever the scoring CENSUS changes —
|
||||
@@ -641,7 +641,7 @@ and every instrument is now live [T2]:**
|
||||
(class key 0x46) = the engine's **`CameraShipHUDRenderable`**, the camera/broadcast seat's
|
||||
followed-name banner + ranking window, which uses PNAME **and** PLACE1-8 as sorted
|
||||
`[ordinal][callsign]` rows and is **already live** as screen quads (see [[multiplayer]] camera
|
||||
seat). Its 1995 source is likewise commented out in `L4VIDRND.cpp:2562-2876` — the literal
|
||||
seat). Its original source is likewise commented out in `L4VIDRND.cpp:2562-2876` — the literal
|
||||
`dpl_*` calls, a Rosetta stone for the binary's node API.
|
||||
- **Simple-X mode** (PrimaryHudOn off, mask 0x20): the minimal reticle — a small green cross
|
||||
(±0.02..0.08 arms) riding the aim translate (ctor @4689-4705 [T1]) — swapped for the full HUD
|
||||
@@ -674,7 +674,7 @@ BITMAPS from the egg, not text — format, renderers, and the operator-console g
|
||||
**The gauge blit addresses SOURCE rows bottom-up, and the vertical strip artwork is authored
|
||||
to match.** `Video16BitBuffered::DrawBitMapOpaque` converts the incoming source rect with
|
||||
`sTop = map_max_y - sTop; sBottom = map_max_y - sBottom` and walks rows upward
|
||||
(`engine/MUNGA_L4/L4VB16.cpp:3846-3850`, `UP_BITMAP` :2155) [T0]; the 1995 display blit
|
||||
(`engine/MUNGA_L4/L4VB16.cpp:3846-3850`, `UP_BITMAP` :2155) [T0]; the original display blit
|
||||
@0046bdfc performs the IDENTICAL flip [T1 disasm], and the PCC→BitMap loader stores the PCX
|
||||
top scanline at memory row 0 unflipped (`GRAPH2D.cpp:27-174`). Net: in `OneOfSeveral::Execute`
|
||||
`sy0 = row*frameHeight` selects frames counting from the **BOTTOM** of the image as authored.
|
||||
@@ -780,7 +780,7 @@ Or/Xor ignore the mask entirely and And clears foreign planes -- all audited):
|
||||
entry=0xffffff00 mask=0x3f`, at exactly the artifact positions in the
|
||||
operator's screenshot. FIX: zero the table in the ctor + cycle the in-plane
|
||||
pattern across entries [2^bits..255] (high-index art degrades to its
|
||||
index-mod-2^bits colour IN-PLANE, can never leak). The 1995 binary ships the
|
||||
index-mod-2^bits colour IN-PLANE, can never leak). The original binary ships the
|
||||
SAME 64-entry fill and relied on 6-bit art discipline -- garbage is not a
|
||||
preservable behaviour, so the cycle-fill is a guarded PORT deviation.
|
||||
|
||||
|
||||
@@ -387,10 +387,10 @@ It lands on BT's addresses better than it deserves to: `1-4`+`QWER` are the ENTI
|
||||
(Condensers 1-6, flush, balance), `F6`/`F7` the display/control-mode cycles, `F9`-`F12` Generators
|
||||
A-D, `F4` the crouch button.
|
||||
|
||||
⚠ **What it costs, deliberately.** A bound key is removed from the authentic 1995 typed-hotkey
|
||||
⚠ **What it costs, deliberately.** A bound key is removed from the authentic original typed-hotkey
|
||||
channel to stop double-dispatch, and this board binds nearly everything — so `5` (Quad page), `z`
|
||||
(Eng1), `t/y/u/i/o` (pilot select) and `+`/`-` (target zoom) are given up. Unbind a key to get its
|
||||
1995 meaning back.
|
||||
original meaning back.
|
||||
|
||||
⚠ **New rule: a bindings row WINS over a built-in convenience key** (`PadRIO::KeyHasBinding`).
|
||||
`V` and `J/K/L` are board buttons now, so the view-toggle and preset-cycle polls stand down for
|
||||
@@ -518,7 +518,7 @@ assigned axes excluded), derives invert from the move direction (throttle from F
|
||||
position), auto-adds the hat look cluster, and rewrites bindings.txt between
|
||||
`# >>> BT_JOYCONFIG` markers (rest of the file preserved). Sign conventions: JoystickX + =
|
||||
twist right, JoystickY + = aim up (wizard binds flight-style forward=down via invert), Turn +
|
||||
= right. Legacy `L4DINPUT.cpp DIJoystick` (L4CONTROLS=DIJOYSTICK, 1995 `Joystick` iface)
|
||||
= right. Legacy `L4DINPUT.cpp DIJoystick` (L4CONTROLS=DIJOYSTICK, legacy `Joystick` iface)
|
||||
left untouched. Verified: grammar accept/reject by line, no-device + XInput-only polling
|
||||
clean 30 s in-mission, zero DI cost with no joy rows. NOT yet verified: real stick axes
|
||||
(no hardware here) — needs a tester with a stick.
|
||||
@@ -590,7 +590,7 @@ copied from them.
|
||||
(right-click = hold latch) — the merged default profile lives in `L4PADBINDINGS.cpp`
|
||||
(auto-writes `content\bindings.txt`; W/S/A/D/Q/E/R/F/X drive, 1-4/Space/Ctrl fire, M/N/H/C =
|
||||
pod buttons 0x18/0x15/0x2C/0x2F, `/V view toggle + J/K/L preset cycles hardcoded in PadRIO).
|
||||
Bound keys are SUPPRESSED from the typed 1995 channel (`PadRIO::SuppressKey`, the btinput
|
||||
Bound keys are SUPPRESSED from the typed original channel (`PadRIO::SuppressKey`, the btinput
|
||||
pattern — ended the glass double-dispatch: 'w'=pilot-select, numpad/F-key key-up aliases);
|
||||
unbound keys keep their authentic meanings (5/z presets, t-o pilot select, +/- zoom).
|
||||
In BAS control mode the pod turns with the STICK (Q/E) and pedals are inactive — authentic;
|
||||
@@ -654,7 +654,7 @@ copied from them.
|
||||
streamed Eng-page `.CTL` dispatches subsystem msg **id 3 / 0xb** to whatever subsystem is shown,
|
||||
and a **weapon (Emitter)** registers only PoweredSubsystem 4-8 + MechWeapon 9-10 — so id 3 is a
|
||||
gap below `entryCount`, and `Receiver::MessageHandlerSet::Build`'s `new HandlerEntry[]` left it
|
||||
as heap garbage → `(this->*garbage)(msg)`. The 1995 binary has the identical non-zeroing
|
||||
as heap garbage → `(this->*garbage)(msg)`. The original binary has the identical non-zeroing
|
||||
`new[]` and survived only on fresh-heap-zero luck (id 3 on a weapon was always meant to be
|
||||
IGNORED — it's a Condenser/Reservoir action in a different subsystem branch; the uniform
|
||||
Eng-page button template makes buttons for unimplemented actions authentically inert). The new
|
||||
|
||||
@@ -185,7 +185,7 @@ Declared-separate members don't see each other's writes — alias them.
|
||||
Replicants (task #50): travel = engine DeadReckon; leg channel driven joints-only from the replicated
|
||||
worldLinearVelocity -> speedDemand (mech4.cpp REPLICANT MOTION) — detail in [[multiplayer]]. [T2]
|
||||
|
||||
## The AUTHENTIC 1995 ground model (default-on, `BT_GROUND_REAL`)
|
||||
## The AUTHENTIC original ground model (default-on, `BT_GROUND_REAL`)
|
||||
Decoded by the 10-agent `ground-model-decode` workflow (binary `FUN_004a9b5c` = the master perf).
|
||||
**NO GRAVITY anywhere** — accelerations zeroed every frame. Vertical position = an ABSOLUTE per-frame
|
||||
ground SNAP:
|
||||
@@ -462,7 +462,7 @@ trap never fired while the value changed) resets the sim cell between damage eve
|
||||
1 Hz bench harness it restarted the warning before the 1.8 s voice note; sporadic real-play
|
||||
damage is unaffected. Needs a cdb write-watchpoint dig (candidates: a recon raw +0x2c-family
|
||||
write or a struct copy spanning it).
|
||||
### The gimp cell is REPLICATED STATE — peers limped for free in 1995 (#82, fixed 2026-07-29)
|
||||
### The gimp cell is REPLICATED STATE — peers limped for free in the original (#82, fixed 2026-07-29)
|
||||
Field: "peers see a limping mech **skating**" (first-person limp + voice correct; bystanders saw a
|
||||
normal walk sliding at limp speed; a stop/start did not clear it). Root cause chain, all [T1/T2]:
|
||||
- The binary's one `mech+0x40` **is** `Simulation::simulationState`, which
|
||||
|
||||
+12
-12
@@ -357,7 +357,7 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`.
|
||||
— the relay fans out server-side (kills the mesh's N−1× duplication at the seam where
|
||||
`EntityBroadcastToReplicants` already serialized once). Verified: relay `tcp rx==tx`, one
|
||||
copy per broadcast.
|
||||
- **UDP channel = the 1995 NETNUB reliable/unreliable split RESTORED (not invented)**:
|
||||
- **UDP channel = the original NETNUB reliable/unreliable split RESTORED (not invented)**:
|
||||
`Receiver::Message` defaults `messageFlags=ReliableFlag` (RECEIVER.h:195); the ~60Hz update
|
||||
path sets flags=0 (ENTITY.cpp:590); `Mode(UnreliableMode)` at LoadingMission (APP.cpp:704,
|
||||
the 2007 port's `Mode()` was a no-op — now STORES the mode). The routing gate is
|
||||
@@ -397,7 +397,7 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`.
|
||||
SEAT_REQUEST branch (btconsole.py). Verified: 8/8 stub tests (distinct seats, FULL on
|
||||
exhaustion, claim clears reserve, duplicate-HELLO refused) + 2-node localhost e2e with NO
|
||||
BT_SELF on either pod → both seated, full ladder, launch, UDP flowing.
|
||||
- **WALK-UP CALLSIGN/MECH REQUEST (2026-07-22) [T2]**: the 1995 front-desk conversation, over
|
||||
- **WALK-UP CALLSIGN/MECH REQUEST (2026-07-22) [T2]**: the original front-desk conversation, over
|
||||
the wire. join.bat/join_lan.bat set `BT_FE_JOIN=1` and launch ZERO-ARG -> the FE menu in JOIN
|
||||
trim (btl4fe.cpp `FeJoinOnly()`: CALLSIGN edit + the 18-mech list + JOIN; screenshot-verified)
|
||||
-> `BTFeLaunchJoinRelay` relaunches with the universal join args + `BT_CALLSIGN`/`BT_MECH` env
|
||||
@@ -438,7 +438,7 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`.
|
||||
default case `Fail()`ed = abort = a SILENT crash (c0000409, no WER record; cdb stack:
|
||||
ucrtbase!abort <- RunMissionMessageHandler <- RoutePacket; 3/3 reproducible in the
|
||||
console-shaped run, and the field report's 464 crash died mid-LOD-load with the same
|
||||
signature). The 1995 console never hit this state (operator-paced launches). FIX
|
||||
signature). The original console never hit this state (operator-paced launches). FIX
|
||||
(L4APP.cpp): the default case now DEFERS -- re-posts the message to itself at +1s (the
|
||||
VehicleDead re-post pattern) until the ladder reaches WaitingForLaunch; bounded at 120
|
||||
deferrals so a wedged load still surfaces the original Fail. `[launch] ... deferred +1s`
|
||||
@@ -513,9 +513,9 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`.
|
||||
is the console pad -- the game socket doesn't exist yet, so the first fix's RelayGameDown
|
||||
hook could never fire (caught by the verification itself). Detection now lives in
|
||||
`HostDisconnectedMessageHandler` case ConsoleHostType (relayMode && !scene-presented ->
|
||||
`BTRelayRejoinNow`); mesh keeps 1995 console-less behavior. Verified 2x in one run.
|
||||
`BTRelayRejoinNow`); mesh keeps the original console-less behavior. Verified 2x in one run.
|
||||
2. **Post-egg pre-mission relay loss**: RelayGameDown -> rejoin (the original fix). ✓
|
||||
3. **Mid-mission relay loss = forever-mission**: the STOP is relay-sent (1995: console owns
|
||||
3. **Mid-mission relay loss = forever-mission**: the STOP is relay-sent (original design: console owns
|
||||
the clock), so a pod losing its relay mid-match played alone forever. Now RelayGameDown
|
||||
(scene presented) posts `StopMissionMessage` at +15s -> normal end path -> lobby relaunch
|
||||
-> patient wait. Verified: scene LIVE -> relay killed -> 15s -> clean RunMissions exit.
|
||||
@@ -598,14 +598,14 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`.
|
||||
drives the pod countdown + the final-30s ranking window; the relay now SENDS
|
||||
`Application::StopMissionMessage` (clientID 4, msgID 6, exitCode 0) at length+2s grace, or on
|
||||
the operator's ⏹ END MISSION button (stdin `stop`). Was half-implemented — nothing fired it in
|
||||
1995 the console owned the stop. Pod runs the authentic fade → mission-loop exit → clean
|
||||
the original — the console owned the stop. Pod runs the authentic fade → mission-loop exit → clean
|
||||
process exit. Verified live (40s test mission ended on the clock).
|
||||
> **The console/relay now has its own topic: [[operator-console]]** (`context/operator-console.md`)
|
||||
> — which of the two programs is which, ports, the seat model, the full round/launch lifecycle and
|
||||
> its wedge states, plus liveness. Read that first for anything console-side; the notes below are
|
||||
> the historical record of how it got built. Sysop how-to: `docs/OPERATOR_GUIDE.md`.
|
||||
|
||||
- **OPERATOR CONSOLE (2026-07-18)**: `tools/btoperator.py` (PySide6) — the lost 1995 operator
|
||||
- **OPERATOR CONSOLE (2026-07-18)**: `tools/btoperator.py` (PySide6) — the lost original operator
|
||||
station recreated: mission editor with ALL values validated live from BTL4.RES
|
||||
(`tools/eggmodel.py`: maps=type14∩26, colors/badges/patches from the type-25 vehicletable,
|
||||
vehicles from ModelLists), relay/mesh mode switch, live pod-status lights parsed from the
|
||||
@@ -637,7 +637,7 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`.
|
||||
— the egg's `[mission] length=` (600 = the 10-min pod mission) drives the pod-side countdown
|
||||
(`secondsRemainingInGame`, APP.cpp:652) and the ranking-window display for the final 30s
|
||||
(DIRECTOR.cpp:113), and the full end chain exists (StopMission id 6 → EndingMission → player
|
||||
fade → Stop) — but NOTHING fired it: in 1995 the CONSOLE sent StopMission at expiry (mission
|
||||
fade → Stop) — but NOTHING fired it: in the original the CONSOLE sent StopMission at expiry (mission
|
||||
length enforcement is console-side). Now reconstructed: the relay arms the clock at RunMission
|
||||
#2 and sends `Application::StopMissionMessage` (clientID 4, msgID 6, +int exitCode 0) at
|
||||
length+2s grace; the operator app gets an ⏹ END MISSION button (stdin `stop`, like `launch`).
|
||||
@@ -713,7 +713,7 @@ carousel, Phase 2 = true drop-in). The durable engine facts found:
|
||||
existing ROSTER host to OnLine whenever it connects — arrival order/timing is free (this is
|
||||
how slow loaders already work), and the relay's PEER_UP exchange is bidirectional for
|
||||
newcomers (btconsole.py:1202-1209).
|
||||
- **Late ENTITY sync was never finished in 1995**: entity MakeMessages are broadcast ONCE at
|
||||
- **Late ENTITY sync was never finished in the original**: entity MakeMessages are broadcast ONCE at
|
||||
creation and never retained. `InterestManager::NotifyOfEntityCreation` (INTEREST.cpp:694-763
|
||||
→ DynamicEntityCreation :839+) sends immediately; the interest-zone bookkeeping is
|
||||
`#if 0 "HACK - Partial implementation"`; and `HostManager::NotifyOfReplacementEntityCreation`
|
||||
@@ -769,7 +769,7 @@ transition, HUD all landed since P6): console egg → mesh → RunningMission on
|
||||
deathPending); without one → engine base @0042db80 (PLAYER.cpp:214: closest non-"win"
|
||||
DropZone → AssignDropZone → 2s re-post probe) → DropZoneReply → CreatePlayerVehicle (a NEW
|
||||
mech; `deathCount = 0`) + viewpoint relink (Verify(viewpointEntity==NULL) is compiled out at
|
||||
DEBUG_LEVEL 0 — release overwrites, same as the 1995 build). @0049fe0c = roster-wide
|
||||
DEBUG_LEVEL 0 — release overwrites, same as the original build). @0049fe0c = roster-wide
|
||||
slot-11 DeathShutdown broadcast (the death transition already runs that pass).
|
||||
CORRECTIONS to the earlier scouting: BTL4.RES **type 12 = InternalAudioStream** (the engine's
|
||||
own resource-type table) — `*int.scp` are internal-AUDIO scripts, NOT per-mech intro scripts;
|
||||
@@ -895,7 +895,7 @@ transition, HUD all landed since P6): console egg → mesh → RunningMission on
|
||||
|
||||
|
||||
## Authentic coupled peer motion — DONE (task #50, 2026-07-14) [T1]
|
||||
Decomp-verified (workflows w0odszxro/wh1h5gnmc, 3 make-or-break claims adversarially CONFIRMED; committed c52a1ad + default-promoted). The 1995 peer per-frame path FUN_004ab430 -> FUN_004ab1c8 (IntegrateMotion) drives motion SINGLE-SOURCE:
|
||||
Decomp-verified (workflows w0odszxro/wh1h5gnmc, 3 make-or-break claims adversarially CONFIRMED; committed c52a1ad + default-promoted). The original peer per-frame path FUN_004ab430 -> FUN_004ab1c8 (IntegrateMotion) drives motion SINGLE-SOURCE:
|
||||
- LINEAR position = GAIT. IntegrateMotion advances the BODY gait channel FUN_004a5678 (FUN_004a5028 is DEAD CODE, zero call sites), turns its cycleDistance into velocity `{0,0,-cd/dt}`, rotates by heading, integrates into projectedOrigin.linear@0x260 (part_012.c:14994/14997/15008); localOrigin.linear@0x100 is copied VERBATIM from it (FUN_00408440, part_012.c:15131 -- NOT a lerp). updateVelocity.linear is never used for position.
|
||||
- ANGULAR/heading = replicated-velocity slerp (the ONLY thing Mover::DeadReckon-style velocity extrapolation drives): projectedOrigin.angular@0x26c built from updateVelocity.angular@0x2d4 (FUN_004ab188), localOrigin.angular slerped toward it.
|
||||
- 0x260 aliasing: "motionDelta@0x260" and "projectedOrigin@0x260" are LITERALLY THE SAME field (base Mover::projectedOrigin, Origin 0x1c); "worldPose@0x26c" is its quaternion half. Our reconstruction's shadow names hid this and drove the false velocity-vs-gait "contradiction".
|
||||
@@ -913,7 +913,7 @@ The scoring authority model (full map: [[combat-damage]] + `docs/KD_CHAIN_AUDIT_
|
||||
the VICTIM's master emits everything on its death edge (kill credit to the shooter via
|
||||
replicant-Dispatch reroute, received penalty to itself, one VehicleDead); the killer's master
|
||||
increments `killCount` when the rerouted credit lands; **killCount/deathTally never crossed the
|
||||
1995 wire** — the port's #45 update-record extension mirrors them one-way owner→replicant
|
||||
original wire** — the port's #45 update-record extension mirrors them one-way owner→replicant
|
||||
(read guarded replicant-only, mixed-version length guard, 2s heartbeat re-dirty). Bystander
|
||||
nodes learn tallies ONLY through that mirror. Verified: `kd4_bench.sh` (bystander convergence +
|
||||
no-mirror-exceeds-authority, receipt-derived per player) and `kd8_stress.sh` (8-node all-vs-all
|
||||
|
||||
+13
-13
@@ -29,7 +29,7 @@ authentic path scoped.
|
||||
(`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
|
||||
original 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
|
||||
@@ -64,7 +64,7 @@ authentic path scoped.
|
||||
relay session — replicated player links (`bmp=1+veh`), director locks the goal (30s timer),
|
||||
camera ship tracks the moving mech (`sees=1`) and CUTS between authored cameras
|
||||
(screenshots). Egg: pilot page `hostType=1` + `vehicle=camera`. **RANKING WINDOW LIVE (same day)**: the stubbed
|
||||
1995 overlay reimplemented as screen-space quads in `CameraShipHUDRenderable::Render` — the
|
||||
original overlay reimplemented as screen-space quads in `CameraShipHUDRenderable::Render` — the
|
||||
followed player's callsign banner (bottom center, the Execute index is ALREADY 0-based) + a
|
||||
rank-ordered row per scoring player ([ordinal][callsign], right of center), visibility = the
|
||||
Director's flash logic (10s on/15s off, solid final 30s), live `playerRank` pointers re-sort
|
||||
@@ -162,7 +162,7 @@ New unaccounted functionality no prior list knew (addresses verified absent from
|
||||
launcher's `ExplosionModelFile`. The MissileThruster parser `FUN_004bf8ec` writes `"SplashRadius"` to
|
||||
+0x50 (size-0x54 record). Burst falloff `baseBurst / dist^exp` floored at 1 (arcade 1.25 / WinTesla
|
||||
`EXPLODE.cpp:209` 1.2f). Only missiles splash (AC tracer is not a Missile). The engine `EXPLODE.cpp`
|
||||
diff is DONE — the T0 source confirmed the model; the only 1995-vs-WinTesla drift is the exponent.
|
||||
diff is DONE — the T0 source confirmed the model; the only original-vs-WinTesla drift is the exponent.
|
||||
Port: `BTResolveSplashRadius`/`BTApplySplashDamage` (mech4.cpp), see [[combat-damage]]. **Remaining
|
||||
T3:** the per-player enable sub-gate `missile+0x360 = BTPlayer+0x264` (its writers read as a per-frame
|
||||
toggle, not a config flag; the `showDamageInflicted@0x264` label is a guess) — port treats authored
|
||||
@@ -181,7 +181,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
the port now implements it byte-grounded ([[subsystems]] WAVE 6 correction). LESSON: a
|
||||
"no callers in the decomp" claim is only as good as the export coverage — sweep the raw image
|
||||
for the operand pattern before declaring a data path dead ([[reconstruction-gotchas]]).
|
||||
Original entry below for the record:** The 1995 manual (p20/p22 + stat sheets) says seek changes TOP SPEED (gear 4 =
|
||||
Original entry below for the record:** The original manual (p20/p22 + stat sheets) says seek changes TOP SPEED (gear 4 =
|
||||
"Supercharge", expert-only; Loki/Thor print Normal 143 / Super Charged 182 / Gimped 40 kph).
|
||||
The 4.10 binary provably has NO runtime myomer→speed coupling (full data-flow closure [T1]:
|
||||
demand fn @004afd10 all branches, all speedDemand writers, AvailableOutput's 2 callers, the
|
||||
@@ -193,7 +193,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
authorized multiplication in the demand path. [[subsystems]] [[pod-hardware]]
|
||||
- **Environment gravity unwired — the myomer CLIMB-heat term is inert (found 2026-07-31, #85).**
|
||||
`Mover::localEnvironment` (MOVER.h:271) is declared and never populated anywhere in the port;
|
||||
`GetEnvironment()->gravityConstant` is the named analog of the 1995 Mover's gravity POINTER at
|
||||
`GetEnvironment()->gravityConstant` is the named analog of the original Mover's gravity POINTER at
|
||||
`+0x250` (`FUN_00421e2c` does `vy -= **(+0x250)` per tick), which the myomer drive-heat
|
||||
integrator uses for its `m·g·|vy|·dt` climb-work term. The bridge (`BTMechMyomerMotionSample`)
|
||||
null-guards it, so climbing currently generates no myomer heat (flat maps: moot; cavern slopes:
|
||||
@@ -239,9 +239,9 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
`SetFogStyle(searchLightOn/OffFogStyle)` with the binary's inverted-cache seed (authentic DARK
|
||||
night start), plus the external spot.bgf beam cone (btfx `brighten` additive veil) on the
|
||||
searchlight site joint, MP-replicated. Historical attribution notes kept below — the
|
||||
"ORIGINAL 1995 LATENT BUG" premise was ❌ RETRACTED 2026-07-25 (#61).** The arcade swaps fog between `fog=` (lights on) and
|
||||
"ORIGINAL LATENT BUG" premise was ❌ RETRACTED 2026-07-25 (#61).** The arcade swaps fog between `fog=` (lights on) and
|
||||
`nosearchlightfog=` (off) via `PullFogRenderable` watching the Searchlight's `lightState`.
|
||||
**The old entry claimed the 1995 binary itself could never light the lamp. That was wrong** — it
|
||||
**The old entry claimed the original binary itself could never light the lamp. That was wrong** — it
|
||||
compared Searchlight's Performance (@004b841c, reads `requestedOn`@0x1E0) against **ThermalSight's**
|
||||
`ToggleLamp` (@004b860c → `0x1DC`), i.e. two different classes, and so invented a missing
|
||||
"0x1DC→0x1E0 bridge". Searchlight's real handler is **@004b838c** (table @0x51117C) and toggles
|
||||
@@ -251,7 +251,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
carries no novice gate. All T1.]
|
||||
Searchlight's handler set is WIRED and **verified live**: pad `0x14` → `requestedOn 0→1` →
|
||||
`lightState 0→1`; the fog watcher + beam cone landed 2026-08-05 (see the header above), so no
|
||||
remaining work rides this entry. (NB the 1995 searchlight carries NO novice gate — that
|
||||
remaining work rides this entry. (NB the original searchlight carries NO novice gate — that
|
||||
lockout is ThermalSight's; raw disasm @004b838c.)
|
||||
- **`HandleMessage` is vtable slot 8/9 in the binary but NON-virtual across the reconstruction --
|
||||
FILED AS GITEA #65
|
||||
@@ -305,7 +305,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
- **PlayerStatus (the mission-review panels) reads the compiled player at RAW BINARY OFFSETS --
|
||||
FILED AS GITEA #63 (2026-07-25, found via #48) [T1 code-read].** btl4gau3.cpp `PlayerStatus::Execute`:
|
||||
`player+0x1FC` (vehicle), `+0x1C8` (score, drawn as a numeric), `+0x1C4` (alive/dead -- a
|
||||
state flip draws a 160x240 filled box) -- all 1995 offsets, garbage on our compiled layout
|
||||
state flip draws a 160x240 filled box) -- all original-binary offsets, garbage on our compiled layout
|
||||
(the databinding trap). DORMANT in-game (the review context no longer survives the viewpoint
|
||||
swap -- the #48 teardown fix) but LIVE the moment the mission-review screen actually runs at
|
||||
round end. Bridge all three through btplayer.cpp accessors before enabling the review screen.
|
||||
@@ -702,7 +702,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
|
||||
- **Subsystem-panel online/offline gate — fields unidentified [T4] (2026-07-12).** The binary's
|
||||
SubsystemCluster draw-state reads `*(subsystem+0x40)==1` and `*(subsystem+0x278)!=4`; neither
|
||||
1995 field is identified (weapon simulationState is 0/2/3/4 -- +0x40 isn't it). The port pins
|
||||
original field is identified (weapon simulationState is 0/2/3/4 -- +0x40 isn't it). The port pins
|
||||
the panel ONLINE (GetDrawState()==1) because the old raw reads were layout NOISE that flapped
|
||||
the state and black-filled the panel over the recharge arc (the frozen-PPC-dial bug; the
|
||||
WeaponCluster repaint-heal also landed). Identify both fields to restore the authentic
|
||||
@@ -948,7 +948,7 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
- **Collision-death tail fallthrough is inspection-tier [T3]:** the divert now falls to the death
|
||||
tail (VehicleDead + blast) per @0x4a0375, but no live wall-death bench has run; field
|
||||
wall-deaths exercise it nightly (watch for DEAD_NOTIFY on collision deaths).
|
||||
- **The 1995 type-0 uninit-award bug** (documented in [[decomp-reference]] §BTPlayer table): the
|
||||
- **The original type-0 uninit-award bug** (documented in [[decomp-reference]] §BTPlayer table): the
|
||||
shipped binary folds an uninitialized stack float into the shooter's score on every non-lethal
|
||||
hit's type-0 report. Era scoreboards looked sane — worth understanding WHY (dispatch path may
|
||||
deterministically leave ~0 at [ebp-0xc]; or replicant-player reroute delivery differs). Port
|
||||
@@ -979,7 +979,7 @@ readout being wrong — it is a real behavioural gap, not a cosmetic one.
|
||||
(`btfx:firesmoke1_scr_tex`) — all effects share the radial-masked grit sheet; decoding FIRE.BSL
|
||||
would give the authored sheet; (c) beams draw before particles, so a beam through thick smoke
|
||||
reads too bright (pass-ordering); (d) `colorWarp/alphaWarp` applied as `t^(1/warp)` [T4 convention];
|
||||
(e) emission uses rate×releasePeriod batching [T4 convention vs the exact 1995 emitter].
|
||||
(e) emission uses rate×releasePeriod batching [T4 convention vs the exact original emitter].
|
||||
- Replacement-LOD selection for NON-additive multi-LOD models (needs the board's hot-spot/
|
||||
reference-point semantics; leads: `s_dplobject.lod_ranges[16]/lod_hot_spot`, 0x2047/0x2048).
|
||||
- Day/night MATERIAL path priority (BMF/IMG) — infrastructure done, gated `BT_MATPRI` (over-applies
|
||||
@@ -1104,7 +1104,7 @@ Trace it live with `BT_AMMO_LOG=1` (+ `BT_PROJ_LOG=1` for per-tick heat/recoil).
|
||||
## Night-9 playtest intelligence (build 4.11.693, 2026-08-01)
|
||||
|
||||
**A MEMBER OF THE ORIGINAL TEAM IS NOW TESTING.** "Ronin" (log user `torak`,
|
||||
machine TOWER_OF_TERROR) worked on the 1995 game and has played the pods
|
||||
machine TOWER_OF_TERROR) worked on the original game and has played the pods
|
||||
recently, including in Japan. He is a **primary source** — treat his comparisons
|
||||
against the pods as high-value evidence, above reconstruction inference. Two
|
||||
contributions already:
|
||||
|
||||
@@ -14,7 +14,7 @@ open_questions:
|
||||
# The Operator Console + Relay
|
||||
|
||||
The system-operator station: build a mission, run a session, watch pods arrive, launch rounds.
|
||||
This is the piece the 1995 archive lost and we rebuilt.
|
||||
This is the piece the original archive lost and we rebuilt.
|
||||
|
||||
**What survives of the original, and why so little:** the console APPLICATION is absent from every
|
||||
archive (and Route A is closed). What we have is the pod's half of the conversation --
|
||||
@@ -22,7 +22,7 @@ archive (and Route A is closed). What we have is the pod's half of the conversat
|
||||
defining the Console* messages the game sends TO the console (MechKilled / MechDamaged /
|
||||
ScoreUpdate / TeamScoreUpdate / DeathWithoutHonor / EndMission) -- plus the engine's receive-side
|
||||
handlers (egg / RunMission / StopMission). Its changelog line *"06/03/95 GAH -- Added
|
||||
corresponding Macintosh message definitions"* implies the 1995 sysop station was a **Macintosh
|
||||
corresponding Macintosh message definitions"* implies the original sysop station was a **Macintosh
|
||||
application**, a separate codebase that was never in the pod build tree -- which is why no archive
|
||||
has it. `btconsole.py`/`btoperator.py` are therefore a wire-faithful RECREATION built from the
|
||||
surviving parser side (+ BT_NET_PROBE against the real exe), not a port of the lost program.
|
||||
@@ -292,7 +292,7 @@ no UDP fan-out, no control channel). All sends now go through `_send_all_guarded
|
||||
|
||||
- **Relay (internet)** — `btconsole.py --relay <port> <egg>`; pods dial out. The normal mode.
|
||||
- **Mesh (LAN legacy)** — `btconsole.py <egg> <host:port> ...`; the console dials OUT to each pod's
|
||||
`-net` port. The authentic 1995 cabinet direction.
|
||||
`-net` port. The authentic original cabinet direction.
|
||||
- **Remote relay / PARKED RELAY** — the GUI drives an already-running relay over the control port
|
||||
instead of spawning a child. **This is a COMPLETE, SUPPORTED deployment mode** (fixed 2026-07-26 —
|
||||
do not re-derive it; see §Parked relay below). The intended production shape: the relay lives
|
||||
|
||||
@@ -63,7 +63,7 @@ Searchlight + Searchlight2 `ToggleLamp` (id 3, table @0x51117C fn @004b838c)**
|
||||
2026-07-25 (#61)**, previously default-constructed blackholes; verified live (0x14 →
|
||||
`lightState 0→1`, 0x12 → `thermalActive 0→1`). ⚠ The fn addresses were **swapped** in this file
|
||||
before #61 (@004b860c is ThermalSight's) — see [[decomp-reference]] for the table evidence — and
|
||||
that swap had propagated a false "1995 latent bug" (retracted, [[subsystems]] WAVE 4).
|
||||
that swap had propagated a false "original latent bug" (retracted, [[subsystems]] WAVE 4).
|
||||
An unhandled message is SILENTLY ignored
|
||||
(Receiver::Receive finds no handler and does nothing — dead buttons produce NO log).
|
||||
0x10/0x11 = map ZoomIn/Out, 0x15 = CycleDisplayMode, 0x18 = CycleControlMode (#6). Buttons
|
||||
@@ -77,7 +77,7 @@ Full decode: [[gauges-hud]] §preset pages; the complete 72-button census + veri
|
||||
button <addr>|axis <axis> deflect|rate <n>|keypad pilot|external <0-15>|pckey <char>|action
|
||||
<name>`; `pad <button>` same targets; `padaxis <src> axis ... [invert] [deadzone d] [rate n]`.
|
||||
Axes: Throttle (lever, rate-walks + sticks), LeftPedal/RightPedal/Pedals (turn, springs),
|
||||
JoystickX (torso twist), **JoystickY (torso ELEVATION — pitch aim; every 1995 control mode
|
||||
JoystickX (torso twist), **JoystickY (torso ELEVATION — pitch aim; every original control mode
|
||||
routes it to Torso::SetAnalogElevationAxis; keys R/F + pad LeftStickY in the default map;
|
||||
verified to the authentic VerticalLimitTop clamp, 20° on the Blackhawk; aims the GUNS — reads
|
||||
on the HUD elevation tape, the eyepoint stays level)**.
|
||||
@@ -96,7 +96,7 @@ dynamically (xinput1_4 → 9_1_0; disconnected-pad probing rate-limited to 1 Hz)
|
||||
a binding are SUPPRESSED from the legacy WM_CHAR/KEYUP feed** (`BTInputSuppressKey`, hook at
|
||||
L4CTRL.cpp keyboard read) — this ended the historic DOUBLE-DISPATCH: 'w' drove AND selected pilot
|
||||
0; F5's key-up value 0x74 aliased to the 't' hotkey; letter key-ups (uppercase) fed the developer
|
||||
fake-event dispatcher. Unbound keys keep their authentic 1995 dispatcher meaning (reachable from
|
||||
fake-event dispatcher. Unbound keys keep their authentic original dispatcher meaning (reachable from
|
||||
any key via `pckey`). The old dual-use 'V' (view toggle + look-behind) is split: V = ViewToggle,
|
||||
B = LookBehind. Verified live: bindings load, W/NumPad8 drive (spd rises), X all-stop, aux-button
|
||||
emission ([input] 0x2f PRESS), suppression both directions ('r' delivered / 'w' swallowed),
|
||||
@@ -227,7 +227,7 @@ each mono CRT exactly its picture. Grouping follows `L4GAUGE.CFG`: *VGA Port A*
|
||||
Mfd2, *VGA Port B* = Mfd1 + Mfd3; the radar keeps real colour on its own port.
|
||||
`SVGA16::ExpandPlaneToBGRA` does the per-plane extraction, and `BT_POD_RGB` implies
|
||||
`BT_POD_SURFACES`. Two companion gates came out of the bring-up: **`BT_POD_CHANMAP=<Port>=<colour>,…`**
|
||||
remaps a surface's colour line when a cab's harness disagrees with the 1995 config (it did — see
|
||||
remaps a surface's colour line when a cab's harness disagrees with the original config (it did — see
|
||||
§ALPHA-MR), and **`BT_POD_IDENT`** / **`BT_POD_CHANTEST`** paint identifying/cycling test patterns
|
||||
so you can tell which physical CRT is which without trusting the desktop layout.
|
||||
**SETTLED:** Nick's cart IS splitter-wired — its three 640×480 "displays" are VGA outputs, each
|
||||
@@ -235,7 +235,7 @@ feeding a three-monitor splitter, and the composite is what lit the glass correc
|
||||
|
||||
## MFD PANELS ON REAL HARDWARE — the bring-up path (2026-08-06) [T2 local / T4 on-pod]
|
||||
Nick's crash cart (pod hardware + Chrome Remote Desktop on a burner account) is the first chance
|
||||
to drive the real panels. **The 1995 display path is NOT the way in.** That rig spanned the five
|
||||
to drive the real panels. **The original display path is NOT the way in.** That rig spanned the five
|
||||
MFDs as ONE 1280×480 surface via **NVIDIA Horizontal Span, which every driver after XP dropped**;
|
||||
`SVGA16::BuildWindows` also wants an exclusive-fullscreen D3D device per adapter, which is
|
||||
fragile on modern drivers and over a remote session. Both are still in-tree and still the
|
||||
@@ -341,13 +341,13 @@ colour planes (§THE RGB SPLIT). Desktop-coordinate order says NOTHING about phy
|
||||
the first assignment, made by sorting monitors left-to-right, was wrong. Only eyes on the glass
|
||||
settle it.
|
||||
|
||||
**Two deviations from the 1995 config, both real, both this cab's wiring:**
|
||||
**Two deviations from the original config, both real, both this cab's wiring:**
|
||||
- **`BT_POD_CHANMAP=Comm=blue,Heat=red`** — kills/deaths and coolant loops came up on each
|
||||
other's panel. `L4GAUGE.CFG` has Comm on red and Heat on blue; THIS cab's splitter harness
|
||||
wires them the other way. The env override remaps a port's colour channel without touching the
|
||||
authentic config file. [T2]
|
||||
- **`BT_GAUGE_SEC_ROT=0`** — the radar here is a LANDSCAPE LCD, so the 640×480 source needs no
|
||||
rotation. The 1995 pod's 270° rotation existed because its secondary CRT was mounted PORTRAIT;
|
||||
rotation. The original pod's 270° rotation existed because its secondary CRT was mounted PORTRAIT;
|
||||
it is a mounting fact, not a format fact. (En route: 180° looked plausible and was upside down.)
|
||||
|
||||
**Frozen in `content/environ.ini`**, not in a launcher — `scratchpad/pod/podprofile.ini` is the
|
||||
@@ -515,7 +515,7 @@ newest-install auto-resolve worked, no hand-edits. The cab genuinely upgrades li
|
||||
second FTDI (A995JTOYA); **two ghost Prolific PL2303 entries also claim "COM1"** from earlier
|
||||
plug-ins (clean them in Device Manager). When the adapter's **USB side** drops mid-operation:
|
||||
brief drop = analog polls go mute (the field "encoder dropouts", revived by hand-jostle /
|
||||
re-enumeration — NOT by button protocol); drop with a write in flight = the 1995 code's
|
||||
re-enumeration — NOT by button protocol); drop with a write in flight = the original code's
|
||||
**unbounded `GetOverlappedResult` wait in `PCSerialPacket::SendPacket` parked the whole game at
|
||||
0 CPU forever** (three boots on 2026-08-10; stack: NtWaitForSingleObject ← GetOverlappedResult ←
|
||||
SendPacket ← RIO ctor). **Hardened in 4.11.881**: 500 ms bounded write + CancelIo + 3 s sick
|
||||
@@ -557,7 +557,7 @@ banner still says "hardware RIO" (the banner is the PROFILE, not the init result
|
||||
the BT411Run task an idempotent restart — but any hand-rolled launcher on a future cab needs the
|
||||
same line, and a mid-mission `/run` now RESTARTS the game rather than failing quietly.
|
||||
|
||||
## The 1995 player manual — alignment audit (2026-07-18) [T1, primary source]
|
||||
## The original player manual — alignment audit (2026-07-18) [T1, primary source]
|
||||
`reference/manual/Tesla40_BT_manual.pdf` (34pp, from Nick). CONFIRMS the reconstruction on
|
||||
every checked control behavior:
|
||||
- **Control modes are named BAS / MID / ADV** (basic/middle/advanced; our Basic/Standard/Veteran
|
||||
|
||||
@@ -9,11 +9,20 @@ key_terms: [BT, RP, MUNGA, Tesla-platform, WinTesla, pod]
|
||||
|
||||
# Project Overview
|
||||
|
||||
Port VWE's arcade **pod** games (Tesla platform, release 4.10, ~1995-96) to modern Windows and,
|
||||
Port VWE's arcade **pod** games (Tesla platform, release 4.10; 1990s, exact release year unestablished) to modern Windows and,
|
||||
ultimately, the actual arcade pod hardware. Primary target: **BattleTech (BT)**; **Red Planet
|
||||
(RP)** shares the engine. Full detail: `docs/PROGRESS_LOG.md §1-3`.
|
||||
|
||||
The ORIGINAL 1995-96 team — the initials in the source headers matched to names by VWE veterans
|
||||
**Dating note (2026-08-12):** the KB previously called this "the 1995 game" — WRONG, per era
|
||||
witnesses (playtesters who ran the original pods say BT 4.10 shipped later). The 1995 belief
|
||||
came from the MUNGA asset-reader library RCS `$Id ... 1995/..` strings inside `BTL4OPT.EXE`,
|
||||
which date the *library code*, not the game; the exe carries no game-copyright year (its PE
|
||||
link timestamp is stamped 2019 — a later re-link/tool artifact, not a release date), and a
|
||||
content-file timestamp of 30-Nov-1995 likewise dates assets, not the release. Until a real
|
||||
date surfaces (ask Nick / era witnesses), say "the original" / "the shipped machine" — never a
|
||||
year. [T2 for the library strings; T4 for any specific release year]
|
||||
|
||||
The ORIGINAL team — the initials in the source headers matched to names by VWE veterans
|
||||
(dev team ~8, art ~6, MUNGA by a smaller internal team) — is credited in **`docs/CREDITS.md`**.
|
||||
|
||||
## The stack
|
||||
@@ -42,7 +51,7 @@ content → `content/`, raw decomp → `reference/decomp/`). [T2]
|
||||
## Current state (2026-08-06) — core gameplay reconstruction COMPLETE; polish phase
|
||||
Field-tested nightly at 4-8 pods over Steam (builds 4.11.622→801). Authentic + benched: the full
|
||||
SP+MP loop; per-panel mesh-true targeting/damage (+ crits, cylinder lottery, armour darkening);
|
||||
the 1995 SCORING model (kill awards + received penalties + panic cost — the id-0x16 report tail);
|
||||
the original SCORING model (kill awards + received penalties + panic cost — the id-0x16 report tail);
|
||||
death (blast/splash, wreck, burial) + respawn (full re-arm audit); locomotion (two-channel gait,
|
||||
gimp limp, knockdowns, CROUCH F4); the night kit (fog-swap SEARCHLIGHT F5 + beam cone); heat/
|
||||
power/myomers (incl. the seek-4 freeze); replication (masters/replicants, kill/death columns,
|
||||
|
||||
@@ -12,7 +12,7 @@ open_questions:
|
||||
# Reconstruction Gotchas
|
||||
|
||||
The reconstruction is a **layout + linkage** problem as much as a logic problem. Our compiled
|
||||
classes are NOT byte-identical to the 1995 binary, and the BT link uses `/FORCE`, so a whole
|
||||
classes are NOT byte-identical to the original binary, and the BT link uses `/FORCE`, so a whole
|
||||
family of bugs is **silent** — garbage that happens to be non-fatal, or a runtime AV with no
|
||||
link error. When a reconstructed class misbehaves, walk this checklist FIRST; the answer is
|
||||
usually here, not in the logic.
|
||||
@@ -65,7 +65,7 @@ every hit is either dead code or a vanished cache.
|
||||
|
||||
## 3. Databinding trap — raw offsets read garbage
|
||||
|
||||
Our compiled layout != the 1995 binary, so `*(T*)(obj+0xNN)` reads garbage for ANY object we
|
||||
Our compiled layout != the original binary, so `*(T*)(obj+0xNN)` reads garbage for ANY object we
|
||||
compile. This is WHY shadow fields fail and why raw subsystem reads (e.g. a gauge reading
|
||||
`owner+0x438`) return junk. **Fix:** use compiled named members/accessors; for a cross-TU raw
|
||||
op, use a **bridge** (§8). A `+0x128`-style owner offset in subsystem code is the
|
||||
@@ -133,7 +133,7 @@ unresolved-by-design until bridged per-module ([[open-questions]]). **Rule: when
|
||||
wall exists, `tail` on the build output hides the fleet — always grep the FULL log; and verify a
|
||||
stub's member TYPE against `dumpbin /symbols` of the defining obj, not against what looks right.**
|
||||
**Duplicate-GLOBAL corollary (glass per-display windows, 2026-07-20):** a global DEFINED in two
|
||||
libs (the 1995 headers declare free globals without `inline`/`extern`, so `application`,
|
||||
libs (the original headers declare free globals without `inline`/`extern`, so `application`,
|
||||
`ghWnd`, … exist in BOTH `munga_engine` and `bt410_l4`) links under `/FORCE:MULTIPLE` with
|
||||
per-object binding that is **non-deterministic across links** — a given `.obj` can resolve
|
||||
`application` to the copy the game ASSIGNS (correct) or to the other copy (stays NULL). A
|
||||
@@ -203,7 +203,7 @@ real PoweredSubsystem). Keep the alloc SIZE + special-cache when swapping a fact
|
||||
always use it. Related trap: the dense handler table (Build indexes slots by id-1) leaves
|
||||
GAP slots (skipped ids) as uninitialized heap -- the NAME-based `Find(const char*)`
|
||||
strcmp-walks every slot and AVs on a gap's garbage entryName (the id-based Find is safe).
|
||||
The 1995 binary's own tables carry the same holes. [T2]
|
||||
The original binary's own tables carry the same holes. [T2]
|
||||
|
||||
## 10. Container-Execute must override (gauges)
|
||||
|
||||
@@ -242,7 +242,7 @@ Trigger: the streamed Eng-page `.CTL` dispatches subsystem msg **id 3 / 0xb** to
|
||||
subsystem is shown on that MFD page; a **weapon (Emitter)** registers only PoweredSubsystem 4-8 +
|
||||
MechWeapon 9-10, so id 3 (slot[2], < entryCount 10) is a gap. Clicking the glass Engineering
|
||||
panel button 0x21 on an Eng page reached it live (`eip=cdcdcdcd` debug / `0x01048748` release).
|
||||
**The 1995 binary has the IDENTICAL non-zeroing `new[]`** (part_002.c Build, `FUN_004022b0` =
|
||||
**The original binary has the IDENTICAL non-zeroing `new[]`** (part_002.c Build, `FUN_004022b0` =
|
||||
operator new) and only survived on fresh-OS-heap-zero luck (a zero slot == NullHandler ==
|
||||
ignored) — a weapon receiving id 3 was *always meant to ignore it* (id 3 = a Condenser/Reservoir
|
||||
action in a different subsystem branch; the Eng-page button template is uniform, so buttons for
|
||||
@@ -262,7 +262,7 @@ Receive`, suspect a gap slot, not a missing symbol. [T2]
|
||||
|
||||
## 12. Frame-pacing trap — the binary assumes a LOCKED 60 fps (task #11)
|
||||
|
||||
The 1995 pod ran frame-locked; reconstructed per-frame logic can carry HIDDEN frame-rate
|
||||
The original pod ran frame-locked; reconstructed per-frame logic can carry HIDDEN frame-rate
|
||||
assumptions that variable dt violates. Archetype: the Emitter Loading tick — the charge
|
||||
integrates toward the generator's 10000V and the Loading→Loaded transition only fires while
|
||||
`rechargeLevel` crosses the ±0.01 snap window around seekV (~0.25s of travel ≈ 15 pod frames —
|
||||
@@ -366,10 +366,10 @@ corrupts the metric.
|
||||
canopies are open lattices. Namespace edge keys by patch identity (and remember l/r patches are
|
||||
MIRRORED — winding handedness flips, so no global winding choice can be right; orient per-face).
|
||||
|
||||
## 16. Engine-facility drift: 2007 terrain-solids amplify 1995 per-contact physics (the MP ram one-shot)
|
||||
## 16. Engine-facility drift: 2007 terrain-solids amplify original per-contact physics (the MP ram one-shot)
|
||||
|
||||
`Mover::StaticBounce` [T0] MUTATES `worldLinearVelocity` (`+= delta_v`, a ×(1+e) reflection) on
|
||||
every call, and `ProcessCollisionList` calls it once PER CONTACTED SOLID in the frame. In the 1995
|
||||
every call, and `ProcessCollisionList` calls it once PER CONTACTED SOLID in the frame. In the original
|
||||
binary the ground was a heightfield probe (FUN_0040e5f0 lineage) — never a collision-list entry —
|
||||
so a mech's list held ~one solid and the mutation was harmless. The 2007 WinTesla engine models
|
||||
TERRAIN AS COLLISION SOLIDS: a mech touching ground + rock + another mech reflects 2-4× in ONE
|
||||
@@ -387,7 +387,7 @@ one-shots (mp_a.log:32651, 2026-07-12: a pristine mech killed by a walking bump)
|
||||
~13-62), CONSTANT repeated values (a stable grind oscillation), or spikes scaling with how many
|
||||
solids surround the contact. Damage = `0.0005 × (1−e²) × impact² × moverMass` [T0 MOVER.cpp] —
|
||||
invert it to read the implied impact speed; >100 m/s means amplified/garbage velocity, not motion.
|
||||
- **Class rule:** when a 1995 per-event computation reads MUTABLE engine state, audit what ELSE the
|
||||
- **Class rule:** when an original per-event computation reads MUTABLE engine state, audit what ELSE the
|
||||
2007 engine feeds that state within the same event batch. (Family of gotcha #12's frame-pacing
|
||||
trap: the binary's physics assumed its own engine's event granularity.)
|
||||
|
||||
@@ -435,7 +435,7 @@ DontExecute). **Rule:** zero every hand-built resource struct; never rely on a p
|
||||
the tester's `'\'` dev key instantly "crashed the game". **Cause:** `Entity::Dispatch`
|
||||
(ENTITY.cpp:236) WRITES `entityID` + `interestZoneID` into the incoming message at
|
||||
`Entity::Message` offsets before routing. A bare `ReceiverDataMessageOf<T>` (Receiver-sized) on
|
||||
the stack gets written PAST ITS END → the caller's frame is corrupted → wild jump. The 1995
|
||||
the stack gets written PAST ITS END → the caller's frame is corrupted → wild jump. The original
|
||||
binary performs the identical overwrite and survived on stack-layout luck. Subsystem/Receiver
|
||||
`Dispatch` does NOT stamp — only the Entity level. **Rule:** any message dispatched at an
|
||||
`Entity` must have `Entity::Message`-sized backing (derive from `Entity::Message`, or placement-
|
||||
@@ -554,7 +554,7 @@ The Ghidra export (`reference/decomp/`) has coverage gaps (#60), and BTL4.RES co
|
||||
appears in it at all — so "no readers", "no caller", "no such clip/asset", and "unreconstructed"
|
||||
claims made by grepping the export or the port ALONE are structurally unsound. This class has
|
||||
produced four wrong conclusions, two of them [T1]-tagged at the time:
|
||||
1. **2026-07-25 searchlight "1995 latent bug"** — compared Searchlight's Performance against
|
||||
1. **2026-07-25 searchlight "original latent bug"** — compared Searchlight's Performance against
|
||||
THERMALSIGHT's handler (wrong class) and invented a missing bridge; retracted (#61).
|
||||
2. **2026-07-2x `DuckState` "has no CODE consumer, authentic [T1]" + "no SQUAT clip survives"** —
|
||||
the consumer was the un-exported master-perf posture block (@0x4aa011), and the clips lived in
|
||||
@@ -596,7 +596,7 @@ was never a regression -- it shipped with the feature.)
|
||||
|
||||
**The bug class:** a piece of state is installed around a resource load, mutates the load's
|
||||
*output*, and is then torn down — so any LATER re-load of that resource silently produces a
|
||||
different (wrong) result. Our port re-loads things the 1995 engine loaded once, which is
|
||||
different (wrong) result. Our port re-loads things the original engine loaded once, which is
|
||||
exactly where this bites.
|
||||
|
||||
**The archetype — mech paint.** The per-pilot colour/badge/patch is applied by REWRITING
|
||||
@@ -755,7 +755,7 @@ treat the disagreement as a hole in YOUR evidence first, not in theirs.
|
||||
|
||||
## §22 — The SPLIT CELL: one binary offset, two port members (only one gets written)
|
||||
**(2026-07-31, gitea #86 "destroyed weapons keep firing".)** The binary's weapon fire gates test
|
||||
`subsystem+0x40`. In the 1995 layout that offset is *inside* the embedded status alarm
|
||||
`subsystem+0x40`. In the original layout that offset is *inside* the embedded status alarm
|
||||
(`statusAlarm@0x2C` + the indicator's level at `+0x14` = `0x40`) — **one cell**, written by
|
||||
`ForceCriticalFailure` when a zone's crit-cascade kills the subsystem. The port models the same
|
||||
address as TWO independent members: `AlarmIndicator statusAlarm` **and** a plain
|
||||
@@ -773,13 +773,13 @@ base field): same failure shape — two cells where the binary has one, and the
|
||||
dead one.
|
||||
|
||||
## §23 — Scaling `D3DMATERIAL9` does NOTHING for BGF geometry (the vertex-colour diffuse source)
|
||||
**(2026-07-31, gitea #87 "mech armour panels don't darken".)** The 1995 armour damage darkens a
|
||||
**(2026-07-31, gitea #87 "mech armour panels don't darken".)** The original armour damage darkens a
|
||||
mech by scaling its MATERIALS' colour terms (`dpl_SetMaterialAmbient/Emissive/Diffuse/Specular`).
|
||||
The obvious port translation — scale the draw op's `D3DMATERIAL9` before `SetMaterial` — renders
|
||||
**byte-identically**: measured **0 changed pixels** at a full 0.1x scale.
|
||||
**Why:** D3D9 defaults `D3DRS_DIFFUSEMATERIALSOURCE` to **`D3DMCS_COLOR1`** (and `D3DRS_COLORVERTEX`
|
||||
to TRUE), so when a vertex carries a diffuse colour the material's Diffuse is never consulted.
|
||||
Every BGF vertex carries a baked colour (that IS the 1995 shading model — no-normal geometry is
|
||||
Every BGF vertex carries a baked colour (that IS the original shading model — no-normal geometry is
|
||||
unlit and coloured by vertex/ramp), so for essentially all world geometry `SetMaterial` is inert
|
||||
for colour. The material only matters for lit, normal-bearing meshes.
|
||||
**Fix pattern:** modulate the FINAL fragment instead — `D3DRS_TEXTUREFACTOR` + a
|
||||
@@ -876,7 +876,7 @@ whole segment table marking every entry dirty, then clears the joint flag.
|
||||
The binary agrees exactly. `MechWeapon::GetMuzzlePoint @004b9948` ends in
|
||||
`FUN_00424da8(owner, segment, out)`, which IS `GetSegmentToWorld`
|
||||
instruction-for-instruction (`GetJointSubsystem` → `if (AreJointsModified())` →
|
||||
mark all → `ModifyJoints(False)` → `× localToWorld`). **So in the 1995 image
|
||||
mark all → `ModifyJoints(False)` → `× localToWorld`). **So in the original image
|
||||
every muzzle query performs the joints→segments refresh.** [T1]
|
||||
|
||||
Four port sites had replaced that with `mw.Multiply(seg->GetSegmentToEntity(),
|
||||
|
||||
@@ -73,7 +73,7 @@ DECLOUDS must stay in the sky pass). Authored TEXTURE **SCROLL** (BMF TEXTURE ta
|
||||
`SetFogStyle(searchLightOn/OffFogStyle)`). TWO independent reasons it never fires:
|
||||
(1) **Port:** `PullFogRenderable` is NEVER CONSTRUCTED (grep: only its .h decl + .cpp def; no `new`);
|
||||
`searchLightOn/Off` is called from nowhere else (BT_FOG_LOG: over 22s only `SetFogStyle(0)`, zero 2/3).
|
||||
(2) ~~**Original 1995 LATENT BUG**~~ — ❌ **RETRACTED 2026-07-25 (#61). There is no 1995 bug.** The
|
||||
(2) ~~**Original LATENT BUG**~~ — ❌ **RETRACTED 2026-07-25 (#61). There is no original-binary bug.** The
|
||||
claim rested on a **swapped table attribution**: @004b860c is **ThermalSight's** `ToggleLamp`
|
||||
(table @0x51120C), not Searchlight's, so pitting its `0x1DC` write against Searchlight's
|
||||
`SearchlightSimulation` (@004b841c, reads `requestedOn`@0x1E0) compared two different classes and
|
||||
@@ -154,7 +154,7 @@ The per-weapon impact visuals are fully AUTHORED in `content/BTDPL.INI` `[effect
|
||||
CannonHit/GroundHit/MinorHit/MajordHit/Fireball — "old-style" (version-1) descriptor sections
|
||||
(`texture/type/size/velocity/v_bias/y_off/cook(3f)/varience/gravity/cool(2f)/count/repeats`),
|
||||
most on `btfx:firesmoke1_scr_tex` (MissileHit size 12 ×7; Fireball size 14, gravity −1.5 rises;
|
||||
Chunks1/2 = ballistic debris vel 40-55 grav 34-35; Sparks NOTEX). [T1: the 1995 binary parses
|
||||
Chunks1/2 = ballistic debris vel 40-55 grav 34-35; Sparks NOTEX). [T1: the original binary parses
|
||||
them — part_008.c:1546-1590, field strings @004f25e4-004f2663 → installer FUN_00490210 packs a
|
||||
0x44-byte board command via FUN_0048e2c0.] **Simulation semantics (type/cook/cool) live in the
|
||||
i860 firmware** `content/VREND.MNG` (same decode workflow as the task-#55 'damageize' handler).
|
||||
@@ -331,7 +331,7 @@ A mech's armour panels **darken as their damage zone takes damage** — a per-zo
|
||||
colour ramp-down, NOT a texture swap, a filter, or geometry. Players reported the port showing only
|
||||
the limb swap ("the actual enemy mech in external view is not showing darkened armor panels").
|
||||
|
||||
**The 1995 chain** [T1]:
|
||||
**The original chain** [T1]:
|
||||
- **`.DZM`** (`VIDEO\<mech>SKIN.DZM`, 40 shipped) lists, per damage zone, the materials that zone
|
||||
paints: `[dz_ltorso] material=avaskin:avat2_dz_ltorso_mtl`. One file per SKIN VARIANT — the
|
||||
`dzm`/`dzms`/`dzmt`/`dzmo`/`dzma`/`dzmb`/`dzmc`/`dzmd` key family (same suffix set as the
|
||||
@@ -400,7 +400,7 @@ the VISUALS were the gap, decoded from MakeMechRenderables @004cef28 `case 0xbd8
|
||||
reads as a modest push-back of the night murk at mid/long range, NOT a flashlight. Geometry
|
||||
brightness comes from the page's ambient= + ramps and never changes with the lamp -- a close
|
||||
wall is identical lit/unlit. First eyeball matched exactly ("kinda sorta illuminates the
|
||||
area... didn't illuminate the wall" -- that IS the 1995 behavior). No illuminating dynamic
|
||||
area... didn't illuminate the wall" -- that IS the original behavior). No illuminating dynamic
|
||||
light exists anywhere in BT's render path: ChildLightRenderable has ZERO instantiations
|
||||
engine-wide, and the FUN_0048d060 cluster (@4568f8/@456a0c/@456b4c/@4586d0) is light-POINT
|
||||
sprites (beacons/nav strobes, palette-tinted), not geometry lights. Set player expectations
|
||||
@@ -409,7 +409,7 @@ the VISUALS were the gap, decoded from MakeMechRenderables @004cef28 `case 0xbd8
|
||||
the searchlight SITE joint (mount = subsystem `mountSegment`@0x1DC == resource segmentIndex;
|
||||
madcat 20, loki 21), shown/hidden per frame from the replicated LightOn (@0045612c watcher ->
|
||||
the SetDrawObj swap). Site segments now build geometry-less DCS children (posed + parentable --
|
||||
the 1995 graph gave every site a DCS node; our tree used to skip sites entirely; this also
|
||||
the original graph gave every site a DCS node; our tree used to skip sites entirely; this also
|
||||
enables future site-hung effects). Wrecks go dark through the authentic gate (host shutdown
|
||||
forces lightState 0). Cross-node verified: B presses, A logs `[spot] cone SHOWN (seg 20)`.
|
||||
- Benches: `scratchpad/night12/searchfog.sh` (solo cockpit chain + red-fog end-to-end probe),
|
||||
|
||||
@@ -6,7 +6,7 @@ source_sections: "PROGRESS_LOG.md §5a; docs/BT_SOURCE_STATUS.md"
|
||||
related_topics: [wintesla-port, reconstruction-method, decomp-reference, open-questions]
|
||||
key_terms: [BTL4OPT, decomp, oracle, WinTesla, BT]
|
||||
open_questions:
|
||||
- "Backfill the missing 1995 engine headers/TUs (vdata.hpp first) by back-dating the WinTesla/BT412 versions — the compile-closure blocker"
|
||||
- "Backfill the missing original engine headers/TUs (vdata.hpp first) by back-dating the WinTesla/BT412 versions — the compile-closure blocker"
|
||||
---
|
||||
|
||||
# Source Completeness — the gating blocker
|
||||
@@ -20,7 +20,7 @@ This is THE gating fact that shapes the whole project. Full map: `docs/BT_SOURCE
|
||||
`.cpp` (PLAYER, NETWORK, GRAPH2D, OBJSTRM, SCNROLE, EXPLODE, L4SPLR, L4APP + ~15 smaller) AND at
|
||||
least one missing engine HEADER (`vdata.hpp`, included by plug.hpp — exists only as the drifted
|
||||
WinTesla/BT412 `VDATA.h`). `CODE/BT/MUNGA` is a PARTIAL BT-drift overlay (69 files) over the
|
||||
fuller `CODE/RP/MUNGA` (305); a 1995 build needs the merged BT-over-RP view. [T1]
|
||||
fuller `CODE/RP/MUNGA` (305); an original-era build needs the merged BT-over-RP view. [T1]
|
||||
- **BT game `.hpp` headers: PARTIAL, not complete** (2026-07-19 correction — the old "all headers
|
||||
present" claim was WRONG): only 17 of ~36 bt-side `.hpp` survive (mech.hpp, mech2-4, heat.hpp,
|
||||
mechmppr.hpp, torso.hpp, gyro.hpp, hud.hpp, myomers.hpp, powersub.hpp, btplayer.hpp are
|
||||
@@ -78,11 +78,11 @@ projtile, missile); toolchain = **Borland BCC/tlib/tlink32** (from the makefiles
|
||||
targets** for future drive dumps. Full log: `phases/phase-03-bt410-source-manifest.md`.
|
||||
|
||||
## The literal 4.10 tree — `TeslaRel410/restoration/source410/` (started 2026-07-18)
|
||||
The archival-grade recreation of the missing TUs in 1995 house style against the surviving
|
||||
1995 engine headers (distinct from OUR WinTesla port). Rules + status in its README; every
|
||||
The archival-grade recreation of the missing TUs in the original house style against the surviving
|
||||
original engine headers (distinct from OUR WinTesla port). Rules + status in its README; every
|
||||
file carries a `.NOTES.md` evidence sidecar; binary `Fail()` file:line records are treated
|
||||
as LINE CONSTRAINTS. **Pilot complete: `BT_L4/BTL4APP.CPP`** — 12/12 functions, all decomp
|
||||
constants resolved to named 1995 expressions (`ApplicationID` BTL4=1 + an unreleased `BTW4`
|
||||
constants resolved to named original expressions (`ApplicationID` BTL4=1 + an unreleased `BTW4`
|
||||
slot in APPMSG.HPP; `ModeInitial`=0x50421; `AddModeMask` inlines byte-exact), Fail lands on
|
||||
its recorded line 400 exactly. [T1/T3 per the sidecar]
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER
|
||||
default-constructed blackholes (systemic cause #1, `docs/INPUT_PATH_AUDIT.md`). Each now chains
|
||||
`PowerWatcher::GetMessageHandlers()` with its own id-3 `ToggleLamp`. **Verified live**: pad `0x14`
|
||||
→ `requestedOn 0→1` → `lightState 0→1`; pad `0x12` → `requestedOn 0→1` → `thermalActive 0→1`.
|
||||
- ❌ **RETRACTED: the "ORIGINAL 1995 latent bug" (old task #63) NEVER EXISTED.** It was an artifact of
|
||||
- ❌ **RETRACTED: the "ORIGINAL latent bug" (old task #63) NEVER EXISTED.** It was an artifact of
|
||||
a **swapped table attribution**: the claim compared Searchlight's Performance (@004b841c, reads
|
||||
`requestedOn`@0x1E0) against **ThermalSight's** `ToggleLamp` (@004b860c, toggles `0x1DC`) and
|
||||
concluded "no 0x1DC→0x1E0 bridge, so the lamp can never light". @004b860c is not Searchlight's.
|
||||
@@ -131,7 +131,7 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER
|
||||
thermalMass=2.5e5. Live-verified (solo arena, BT_MYO_LOG): standstill ≈ 0 generation, hard
|
||||
circling drove T 77→1225 (past degradation) and the coolant loop pulled it back to ~520
|
||||
equilibrium on slowing. ⚠ Two carried caveats: (1) the kinetic work term has **no dt** in the
|
||||
binary (per-tick accumulate, a 1995 fixed-frame assumption) → heat/s scales mildly with frame
|
||||
binary (per-tick accumulate, an original fixed-frame assumption) → heat/s scales mildly with frame
|
||||
rate; kept verbatim, field-calibrate against pod veterans. (2) the climb term is wired but
|
||||
inert: `Mover::localEnvironment` is never populated in the port (EnvironmentZone res type 23
|
||||
unwired), so g reads 0 — see [[open-questions]].
|
||||
@@ -160,7 +160,7 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER
|
||||
thermal curve and zone damage; `@004b8ceb` run the inner integrator **only when `outputVoltage > 0`**.
|
||||
Verified live: healthy `outV=10000 speed=1`, un-powered `outV=0 speed=0`, and 96/96 torso samples at
|
||||
`elec=4` across two death/respawn cycles.
|
||||
✅ **CORRECTED 2026-07-29 (#80): `@004b8bb9` — the un-powered self-repair — is LIVE, in 1995 and
|
||||
✅ **CORRECTED 2026-07-29 (#80): `@004b8bb9` — the un-powered self-repair — is LIVE, in the original and
|
||||
now in the port.** The 2026-07-28 "dead code in the original too" verdict here was wrong about the
|
||||
original: the frozen-at-0.6 experiment was measuring a PORT bug (the subsystem ctor wrote the
|
||||
zone's armour/scales through the `ReconDamageZone` proxy at struct offsets +4/+8 instead of the
|
||||
|
||||
@@ -12,7 +12,7 @@ open_questions:
|
||||
|
||||
# Translocation warp (the respawn vortex)
|
||||
|
||||
The "blue whirlwind" respawn effect from the 1995 pod: on death the pilot is sucked into a spinning
|
||||
The "blue whirlwind" respawn effect from the original pod: on death the pilot is sucked into a spinning
|
||||
energy vortex; on respawn it blows outward and the reborn mech's world snaps back. VWE calls it
|
||||
**translocate** (not "whirlwind" — that keyword search is why an earlier scout wrongly declared it
|
||||
"not a shipped asset"). It is a pure **render-side** effect watching the player's `SimulationState`
|
||||
@@ -37,7 +37,7 @@ The loader **doubles** every tri (fwd `a b c` + rev `a c b`) → 80 tris (`emitT
|
||||
`tessellateWarpCone()` subdivides 4:1 × `BT_WARP_TESS` (default 3) projecting each new midpoint onto
|
||||
the true cone surface (radius `10.5*(1-|z|/21)`) → ~5120 smooth tris. Untessellated, the 12 flat
|
||||
facets read as a "12-sided funnel"; tessellation is what makes the swirl smooth on a modern display.
|
||||
(The 1995 low-res CRT + phosphor + fast spin hid the facets for free.)
|
||||
(The original low-res CRT + phosphor + fast spin hid the facets for free.)
|
||||
|
||||
## The material — `btfx:tsphere_mtl` (in `VIDEO/MAT/<tod>/BTFX.BMF`) [T1]
|
||||
`tsphere_mtl` → texture **`tsphere_scr_tex`** (a scrolling texture, SPECIAL `SCROLL 0.0 0.0 0.1 0.5`) →
|
||||
|
||||
@@ -50,7 +50,7 @@ on top of it. Full detail: `docs/PROGRESS_LOG.md §5b, §8`.
|
||||
`alDeleteSources`) is working. A dropped voice there competes with ~256 already sounding, so it is
|
||||
very likely sub-perceptual — treat the log noise as a symptom to watch, **not** as the old bug.
|
||||
Mechanism if it ever does matter: `alcCreateContext(device, NULL)` passes no attribute list, so
|
||||
OpenAL Soft applies its DEFAULT 256 mono sources (an OpenAL default, unrelated to the 1995 audio
|
||||
OpenAL Soft applies its DEFAULT 256 mono sources (an OpenAL default, unrelated to the original audio
|
||||
hardware), while a busy match logs ~7200 explosions × 3 `DPLIndependantEffect` voices.
|
||||
⚠ **Raising the cap is deliberately OPT-IN (`BT_AUDIO_SOURCES=<n>`, no rebuild needed)** — the 256
|
||||
ceiling doubles as a **governor**. The steal loop only steals when the incoming source outranks a
|
||||
@@ -224,7 +224,7 @@ on top of it. Full detail: `docs/PROGRESS_LOG.md §5b, §8`.
|
||||
(3) Failed acquisitions stranded partial sets forever — dropped transients are never released by
|
||||
anything (the dtor didn't release; maintenance only touches running sources). Fix: handback via
|
||||
`ReleaseChannels()` at both failure sites (StartRequest + dormant-resume) + a dtor backstop.
|
||||
Context: the port's `RequestAudioChannels` has NO channel budget (the 1995 per-card budget is
|
||||
Context: the port's `RequestAudioChannels` has NO channel budget (the original per-card budget is
|
||||
the commented-out block) — it alGenSources until OpenAL Soft's ~256 cap; on failure a transient
|
||||
is silently dropped (StartRequest early-return), a sustained goes dormant — which is exactly the
|
||||
"cutting in and out" presentation. Priority-steal (`RequestAudioResources`) only engages at cap.
|
||||
|
||||
@@ -114,7 +114,7 @@ ALSO (#152 rider): did Mid/Adv auto-recentre the torso in the pod, or only
|
||||
-----------------------------------------------------------------------------
|
||||
D. DESIGN CALLS -- group opinions wanted
|
||||
-----------------------------------------------------------------------------
|
||||
#151 Panic eject costs -499: verified REAL 1995 scoring (your death scored
|
||||
#151 Panic eject costs -499: verified REAL original scoring (your death scored
|
||||
as suicide + death penalty). Keep authentic, or house-rule a flat cost?
|
||||
#23 Non-glass screen layout: Oracle proposes widescreen-aware sizing --
|
||||
world view wider + taller, moved up, allowed to overlap the radar.
|
||||
@@ -147,7 +147,7 @@ F. INTERNAL ENGINEERING BACKLOG (nothing to test)
|
||||
#14 Glass panel unreconstructed button handler backlog (census)
|
||||
#17 Cavern upper-butte halves lack a collision stream (authenticity TBD)
|
||||
#28 Confirm the vital-subsystem-crit death path can fire (machinery in)
|
||||
#63 PlayerStatus reads raw 1995 offsets (databinding trap, dormant)
|
||||
#63 PlayerStatus reads raw original-binary offsets (databinding trap, dormant)
|
||||
#64 damageZone shadow field (half-fixed; consequences disarmed)
|
||||
#65 HandleMessage non-virtual across 10 classes (latent dispatch trap)
|
||||
#72 BTLoadPump swallows WM_QUIT during load (latent)
|
||||
|
||||
@@ -26,7 +26,7 @@ L4:
|
||||
|
||||
BT:
|
||||
kind: concept
|
||||
definition: BattleTech — the primary port target. VWE arcade pod game, Tesla platform release 4.10 (~1995-96). Game logic = CODE/BT (mostly MISSING as source; reconstructed from the binary).
|
||||
definition: BattleTech — the primary port target. VWE arcade pod game, Tesla platform release 4.10 (1990s; exact release year unestablished). Game logic = CODE/BT (mostly MISSING as source; reconstructed from the binary).
|
||||
related_terms: [RP, MUNGA, BTL4OPT, source-completeness]
|
||||
related_topics: [project-overview, source-completeness]
|
||||
|
||||
@@ -344,7 +344,7 @@ shadow-field:
|
||||
|
||||
databinding-trap:
|
||||
kind: concept
|
||||
definition: Our compiled class layout != the 1995 binary, so a RAW offset read *(T*)(obj+0xNN) reads garbage. Use compiled named members / accessors / a bridge fn in a complete-type TU. The systemic reason raw-offset reads fail.
|
||||
definition: Our compiled class layout != the original binary, so a RAW offset read *(T*)(obj+0xNN) reads garbage. Use compiled named members / accessors / a bridge fn in a complete-type TU. The systemic reason raw-offset reads fail.
|
||||
related_terms: [shadow-field, bridge, Wword-trap]
|
||||
related_topics: [reconstruction-gotchas]
|
||||
|
||||
@@ -422,7 +422,7 @@ TakeDamage:
|
||||
|
||||
ground-model:
|
||||
kind: concept
|
||||
definition: The authentic 1995 vertical-position model (FUN_004a9b5c). NO GRAVITY — an ABSOLUTE per-frame ground SNAP via the zone BoundingBoxTree (FindBoundingBoxUnder), lift = 5% of volume X-width. Walls block by FULL FRAME REJECTION; crushable props pass with a crunch.
|
||||
definition: The authentic original vertical-position model (FUN_004a9b5c). NO GRAVITY — an ABSOLUTE per-frame ground SNAP via the zone BoundingBoxTree (FindBoundingBoxUnder), lift = 5% of volume X-width. Walls block by FULL FRAME REJECTION; crushable props pass with a crunch.
|
||||
related_terms: [SLD, BoundingBoxTree, MoveAndCollide]
|
||||
related_topics: [locomotion]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user