PARALLAX (#16): the pick/fire ray was anchored at mech.y+5.0 (a port improvisation) while the sight line ran from the eyepoint (y=6.23) -- two parallel rays whose constant offset grew into the reported low-miss as range closed (measured ry +0.072 @50u -> +1.54 @2.7u). The decomp's sight and pick share the eye origin (HudSimulation @4b7830 chain). Fix: the viewpoint mech's cockpit eye owns the aim-camera publish in BOTH views, origin = its own eye translation; leveling + deliberate elevation untouched; chase view now converges to cockpit ballistics (V cannot change where shots land). After: pick pinned to the crosshair (ry <= 5e-6) from 50u to point-blank; 26 laser + 8 missile center-mass hits at 3/4-screen. Awaiting the reporters' approach-test. VERDICT INSTRUMENTATION (#4 closed authentic, #5 verdict posted): BT_RANGE_LOG per-frame pick tracing + BTGroundRayHitExact analytic cross-check (0/8000 arena fall-throughs; cavern 6/8400 single-frame grazes -- the 'crazy sliding' is the authentic world-pick + 500 m/s slide over depth discontinuities); BT_AUD_TAIL StopNote/fade timing + BT_FIRE_PULSE single-shot driver (the energy 'buzz' is the AUTHORED charging loop: crescendo through recharge, 1.309s authored release, measured within one frame). CAVERN.EGG: solo cavern test egg. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d7158f1f82
commit
70eea6c1a4
@@ -0,0 +1,229 @@
|
||||
[mission]
|
||||
adventure=BattleTech
|
||||
map=cavern
|
||||
scenario=freeforall
|
||||
time=day
|
||||
weather=clear
|
||||
temperature=27
|
||||
length=600
|
||||
[ordinals]
|
||||
bitmap=Ordinal::BitMap::1
|
||||
bitmap=Ordinal::BitMap::2
|
||||
bitmap=Ordinal::BitMap::3
|
||||
bitmap=Ordinal::BitMap::4
|
||||
[Ordinal::BitMap::1]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=000038000003C000001FF00000000F00
|
||||
bitmap=0000F8000003C000003FF80000000F00
|
||||
bitmap=0001F8000003C00000707C0000000F00
|
||||
bitmap=0001F8000003C00000603C0000000F00
|
||||
bitmap=00007801FC0FF00000003C3DF807FF00
|
||||
bitmap=00007803FE0FF00000003C3FFC0FFF00
|
||||
bitmap=00007803C703C00000003C3E3C1F0F00
|
||||
bitmap=000078078303C0000000783C1E1E0F00
|
||||
bitmap=000078078003C0000000783C1E1E0F00
|
||||
bitmap=00007807C003C0000000F03C1E1E0F00
|
||||
bitmap=00007807F003C0000001E03C1E1E0F00
|
||||
bitmap=00007803FC03C0000003C03C1E1E0F00
|
||||
bitmap=00007801FE03C0000007803C1E1E0F00
|
||||
bitmap=000078007F03C000000F003C1E1E0F00
|
||||
bitmap=000078001F03C000001E003C1E1E0F00
|
||||
bitmap=000078000F03C000003C003C1E1E0F00
|
||||
bitmap=000078060F03C0000078003C1E1E0F00
|
||||
bitmap=000078071E03E0000078003C1E1F1F00
|
||||
bitmap=00007803FE01F000007FFC3C1E0FFF00
|
||||
bitmap=00007801FC00F000007FFC3C1E07EF00
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[Ordinal::BitMap::2]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=001FFF0000003C0000003C03C0780000
|
||||
bitmap=001FFF0000003C0000007C03C0780000
|
||||
bitmap=00000E0000003C000000FC03C0780000
|
||||
bitmap=00003C0000003C000001BC03C0780000
|
||||
bitmap=0000700F3E1FFC000003BC0FF07BF000
|
||||
bitmap=0001E00F7E3FFC0000073C0FF07FF800
|
||||
bitmap=0003800FFE7C3C0000063C03C07C7800
|
||||
bitmap=0007F80FFE783C00000C3C03C0783C00
|
||||
bitmap=0007FE0F80783C0000183C03C0783C00
|
||||
bitmap=00001E0F00783C0000383C03C0783C00
|
||||
bitmap=00000F0F00783C0000703C03C0783C00
|
||||
bitmap=00000F0F00783C00007FFF03C0783C00
|
||||
bitmap=00000F0F00783C00007FFF03C0783C00
|
||||
bitmap=00000F0F00783C0000003C03C0783C00
|
||||
bitmap=00000F0F00783C0000003C03C0783C00
|
||||
bitmap=00000F0F00783C0000003C03C0783C00
|
||||
bitmap=00180F0F00783C0000003C03C0783C00
|
||||
bitmap=001C1F0F007C7C0000003C03E0783C00
|
||||
bitmap=000FFE0F003FFC0000003C01F0783C00
|
||||
bitmap=0007FC0F001FBC0000003C00F0783C00
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[Ordinal::BitMap::3]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=001FFF03C07800000000FC03C0780000
|
||||
bitmap=001FFF03C07800000003FC03C0780000
|
||||
bitmap=001E0003C07800000007C003C0780000
|
||||
bitmap=001E0003C0780000000F0003C0780000
|
||||
bitmap=001E000FF07BF000000F000FF07BF000
|
||||
bitmap=001E000FF07FF800001E000FF07FF800
|
||||
bitmap=001E0003C07C7800001E0003C07C7800
|
||||
bitmap=001FFC03C0783C00001EFC03C0783C00
|
||||
bitmap=001FFE03C0783C00001FFE03C0783C00
|
||||
bitmap=00001F03C0783C00001F1F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00180F03C0783C00001E0F03C0783C00
|
||||
bitmap=001C1F03E0783C00001F1F03E0783C00
|
||||
bitmap=000FFE01F0783C00000FFE01F0783C00
|
||||
bitmap=0007FC00F0783C000007FC00F0783C00
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[Ordinal::BitMap::4]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=001FFF03C07800000007FC03C0780000
|
||||
bitmap=001FFF03C0780000000FFE03C0780000
|
||||
bitmap=00000F03C0780000001F1F03C0780000
|
||||
bitmap=00000F03C0780000001E0F03C0780000
|
||||
bitmap=00000F0FF07BF000001E0F0FF07BF000
|
||||
bitmap=00001F0FF07FF800001E0F0FF07FF800
|
||||
bitmap=00001E03C07C7800001E0F03C07C7800
|
||||
bitmap=00003E03C0783C00001E0F03C0783C00
|
||||
bitmap=00003C03C0783C00000F1E03C0783C00
|
||||
bitmap=00003C03C0783C000007FC03C0783C00
|
||||
bitmap=00007803C0783C000007FC03C0783C00
|
||||
bitmap=00007803C0783C00000F1E03C0783C00
|
||||
bitmap=00007803C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003E0783C00000F1E03E0783C00
|
||||
bitmap=0000F001F0783C00000FFE01F0783C00
|
||||
bitmap=0000F000F0783C000007FC00F0783C00
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[pilots]
|
||||
pilot=200.0.0.96
|
||||
[200.0.0.96]
|
||||
hostType=0
|
||||
advancedDamage=1
|
||||
loadzones=1
|
||||
name=Aeolus
|
||||
bitmapindex=1
|
||||
experience=expert
|
||||
badge=VGL
|
||||
patch=Yellow
|
||||
role=Role::Default
|
||||
dropzone=one
|
||||
vehicle=bhk1
|
||||
vehicleValue=1000
|
||||
color=White
|
||||
[largebitmap]
|
||||
bitmap=BitMap::Large::Aeolus
|
||||
[BitMap::Large::Aeolus]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=000003FE0000000001E0000000000000
|
||||
bitmap=000007FF0000000001E0000000000000
|
||||
bitmap=00000F8F8000000001E0000000000000
|
||||
bitmap=00000F078000000001E0000000000000
|
||||
bitmap=00000F0781FF00FF81E0F0781FC00000
|
||||
bitmap=00000F0783FF81FFC1E0F0783FE00000
|
||||
bitmap=00000F0787C7C3E3E1E0F0783C700000
|
||||
bitmap=00000F078783C3C1E1E0F07878300000
|
||||
bitmap=00000F078783C3C1E1E0F07878000000
|
||||
bitmap=00000FFF8783C3C1E1E0F0787C000000
|
||||
bitmap=00000FFF8783C3C1E1E0F0787F000000
|
||||
bitmap=00000F0787FFC3C1E1E0F0783FC00000
|
||||
bitmap=00000F0787FFC3C1E1E0F0781FE00000
|
||||
bitmap=00000F07878003C1E1E0F07807F00000
|
||||
bitmap=00000F07878003C1E1E0F07801F00000
|
||||
bitmap=00000F07878003C1E1E0F07800F00000
|
||||
bitmap=00000F078780C3C1E1E0F07860F00000
|
||||
bitmap=00000F0787C1C3E3E1E078F871E00000
|
||||
bitmap=00000F0783FF81FFC1E07FF83FE00000
|
||||
bitmap=00000F0781FF00FF81E03F781FC00000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[smallbitmap]
|
||||
bitmap=BitMap::Small::Aeolus
|
||||
[BitMap::Small::Aeolus]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=0000000000000000003E00000C000000
|
||||
bitmap=006300000C00000000631F0F8CC63C00
|
||||
bitmap=00633198CCC66200007F3198CCC66000
|
||||
bitmap=00633F98CCC6780000633018CCC61E00
|
||||
bitmap=00633018CCC6060000633098CCC64600
|
||||
bitmap=00631F0F8C7E3C000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=64
|
||||
y=16
|
||||
width=4
|
||||
[Role::Default]
|
||||
model=dfltrole
|
||||
[Role::NoReturn]
|
||||
model=noretun
|
||||
@@ -79,11 +79,39 @@ jointtorso — NO explicit eye-side compose exists, one briefly added on false "
|
||||
probe readings double-rotated the view and is retired, see [[cockpit-view]]); the crosshair sits
|
||||
at screen center (`gBTAimX = 0` — the old `BTTwistToReticleX` slew was THE bug: the camera
|
||||
already yawed, so the crosshair counter-slid to HULL-forward, "twisting leaves the crosshairs
|
||||
behind"; `BT_AIM="x y"` still pins it for headless tests) → `BTGetAimRay` (the ACTIVE eye
|
||||
behind"; `BT_AIM="x y"` still pins it for headless tests) → `BTGetAimRay` (the BORESIGHT eye
|
||||
publishes pos+basis via `BTSetAimCamera`, leveled in pitch, the twist yaw riding in the basis) →
|
||||
`Mech::PickRayHit` (world ray → local frame → `BoundingBox::HitBy` slab test vs the collision
|
||||
template's ExtentBox → world hit point).
|
||||
|
||||
**PICK-RAY ORIGIN = the TRUE EYEPOINT (Gitea #16 "shots land LOW, worse up close" — FIXED
|
||||
2026-07-20) [T1 evidence / T2 fix].** The task-#48 publish anchored the pick ray at
|
||||
`mech.y + 5.0` (a port improvisation) while the crosshair's sight line runs from the eyepoint
|
||||
(~y 6.2 on the DEV mech) — two parallel rays with a constant vertical offset, so the pick point
|
||||
slid DOWN the reticle as range closed (measured pre-fix via `BT_PARALLAX_LOG`, the pick point
|
||||
back-projected through the RENDER view: ry +0.07 @ 50u → +0.17 @ 16u → +0.32 @ 10u → +1.5 @
|
||||
2.7u; lasers AND missiles consume the pick, so both landed low). The binary's sight math is
|
||||
EYE-ORIGINATED [T1]: HudSimulation's designator HotBoxVector = target position transformed by
|
||||
`inverse(eyeSegToWorld)` ∘ `inverse(R(EyepointRotation))` where eyeSegToWorld = the HUD's mount
|
||||
SEGMENT frame × mech localToWorld (`@004b7830` part_013.c:5688-5702 — Ghidra drops the segment
|
||||
arg, disasm shows `hud+0xdc`; the seg→world composer is `FUN_00424da8` @00424da8); the VIEW is
|
||||
`inverse(jointtorso→jointeye→siteeyepoint)` (`FUN_004c22c4` part_013.c:11742); and the fired
|
||||
beam CONVERGES from the muzzle segment to the picked point `mech+0x37c` (`@004bace8`
|
||||
part_013.c:7742-7752) — so pick ray and sight line share the eye origin (the pod's division
|
||||
card cast from the view per Auric; the 0x37c/0x388 writer itself is in the un-exported gap).
|
||||
**The fix:** the viewpoint mech's cockpit `siteeyepoint` eye is marked the **BORESIGHT eye**
|
||||
(`DPLEyeRenderable::SetBoresightEye`, set in btl4vid.cpp) and owns the `BTSetAimCamera`
|
||||
publish in BOTH views (it executes in the mech's draw traversal every frame), origin = its own
|
||||
eye translation (eyeW row 4 — the exact frame the crosshair projects from); the leveling +
|
||||
`gBTEyeElev` re-application are unchanged. Chase ballistics are therefore IDENTICAL to cockpit
|
||||
(verified: chase-view pick lands at y=6.23076 = the cockpit eye height while the chase camera
|
||||
sits at y=11); the legacy `+5` anchor survives only as the no-boresight-eye fallback
|
||||
(spectator). Verified after: ry ≤ 5e-6 from 50u to point-blank 2.2u in cockpit AND glass
|
||||
builds; 26 laser `[dmghit]`s + 8 missile hits at range 1.7-2.2 on center-mass/upper zones with
|
||||
the lock ring on the target chest (`scratchpad/i16_laser.png`/`i16_missile.png`). Diags:
|
||||
`BT_PARALLAX_LOG` (pick→render-view back-projection via `BTProjectToRenderView`),
|
||||
`BT_GOTO_STOP`/`BT_GOTO_THR` (approach-test stop radius / walk throttle).
|
||||
|
||||
**THE WORLD-PICK TARGET MODEL (task #41, the reconciliation — binary + pod-video evidence).**
|
||||
The target slot `mech+0x388` holds **whatever entity is downrange of the torso boresight** — a
|
||||
mech OR a WORLD entity (terrain/mesa/building). Evidence: (a) the weapon fire path is DOUBLY
|
||||
@@ -387,6 +415,18 @@ thing: an EXPLICIT `JointedMover::~JointedMover()` in the reconstructed `~Mech`
|
||||
chain TWICE (the dtor-epilogue rule, [[reconstruction-gotchas]] §7) → a double-free of `collisionLists`
|
||||
+ the segment table. Removing it fixed BOTH the death-row crash AND app-exit crashes. [T2]
|
||||
|
||||
**⚠ The SILENT death-crash (Gitea #12, FIXED 2026-07-20) was NOT a mech-teardown bug at all — it
|
||||
was the DEATH-EXPLOSION AUDIO.** A silent hard crash (no WER/markers) a few seconds after ANY mech
|
||||
death — solo dummy, MP self-death, MP peer PEER_DOWN — deterministic on the first kill. Root cause:
|
||||
the death boom is the **AllExplosion preset (25 layered zones)**, but the port's OpenAL
|
||||
`SourceSet.sources[]` array was only **5** wide, so acquiring channels for it overwrote adjacent
|
||||
heap; the corruption turned fatal seconds later at the death Explosion's own audio-entity teardown
|
||||
(`FryDeathRow → Explosion::~Explosion → …DestroyEntityAudioObjects → ~Dynamic3DPatchSource →
|
||||
operator delete`) or the next sequenced virtual dispatch (`AudioControlSequence::RunSequence →
|
||||
Send`, garbage vtable). Fixed by sizing `sources[]` to the real max — see [[reconstruction-gotchas]]
|
||||
§21 (Gitea #12). NOT the held #16 boresight-eye dtor (exonerated), NOT the `rev=-64` mppr value
|
||||
(a `ControlsButton` negative = release, benign). [T2]
|
||||
|
||||
## Death SEQUENCE reconstruction — the map (2026-07-08)
|
||||
Full decomp map of the death path. Two tiers:
|
||||
**EXPORTED (reconstructable) [T1 read]:**
|
||||
|
||||
@@ -131,7 +131,10 @@ valid for reading the decomp, NOT for our compiled layout (see [[reconstruction-
|
||||
### BTPlayer (0x294) experience/game-mode flag block (ctor @4c0bc8; see [[experience-levels]]) [T1]
|
||||
|
||||
Seeded on the master branch from `btMission(+0x1f8)->experienceLevel(+0xe4)` (egg `experience`,
|
||||
parse @4d2f3e → `BTMission+0xe4`) and `advancedDamageOn(+0xf0)`:
|
||||
parse @4d2f3e → `BTMission+0xe4`) and `advancedDamageOn(+0xf0)`. The +0x1f8 mission pointer is
|
||||
the inherited `Player::playerMission`. Port members (issue #2): `simLive`/`heatModelOn`/
|
||||
`advancedDamageOn`(+2)/`levelFlag26c`/`levelFlag270`/`experienceLevel`; bridges
|
||||
`BTPlayerExperienceSimLive`/`…HeatModelOn`/`BTPlayerRoleLocksAdvanced` (btplayer.cpp):
|
||||
|
||||
| Offset | Value by level (nov/std/vet/exp) | Verified readers |
|
||||
|---|---|---|
|
||||
@@ -321,6 +324,8 @@ default-ON (`'0'` disables).
|
||||
| `BT_CRIT_PROBE=<zone>` | hammer one own-mech zone every 4s (crit-propagation diag, task #2; mech4.cpp) |
|
||||
| `BT_REPL_LOG` | replicant/MP replication log (mech4.cpp) |
|
||||
| `BT_GOTO="enemy"\|"x z"` / `BT_GOTO_LOG` | self-driving beeline (to the enemy or a map coordinate) + its log |
|
||||
| `BT_GOTO_STOP=<u>` / `BT_GOTO_THR=<0..1>` | beeline stop-radius / throttle overrides (Gitea #16 approach test: walk all the way in) |
|
||||
| `BT_PARALLAX_LOG` | 2 Hz `[parallax]`: the pick point back-projected through the RENDER view (`BTProjectToRenderView`, L4VIDEO.cpp) — a healthy boresight holds (rx,ry)≈(0,0) at every range (Gitea #16; [[combat-damage]] Targeting) |
|
||||
| `BT_MP_LOG` / `BT_MP_NET` / `BT_MP_FORCE_DMG` | MP diagnostics / forced cross-pod damage |
|
||||
| `BT_AUTODRIVE=<0..1>` | headless throttle |
|
||||
| `BT_KEY_NOFOCUS=1` | accept key input without window focus |
|
||||
|
||||
+21
-1
@@ -337,7 +337,27 @@ and every instrument is now live [T2]:**
|
||||
**GREEN width-1 caret triangle** (ctor @4550-4551 sets green/1 AFTER the yellow bar call —
|
||||
transcription color bug caught by a period reference screenshot, 2026-07-09; same for the
|
||||
bottom bowtie carets @4569-4570); pegs at 1200 with no target; the DISPLAYED range slides at
|
||||
**500 m/s** toward the true pick range (HudSimulation :5652 [T1]). ⚠ A period pod screenshot
|
||||
**500 m/s** toward the true pick range (HudSimulation :5652 [T1]).
|
||||
**VERDICT (Gitea #4, 2026-07-20): the "range slides in/out crazily while walking" report is
|
||||
AUTHENTIC behavior, not a bug [T2 measured].** Per-frame `BT_RANGE_LOG` traces (mech4.cpp, with
|
||||
an independent Möller-Trumbore cross-check `BTGroundRayHitExact` in btvisgnd.cpp) on scripted
|
||||
walks: ARENA1 garage cluster — every frame a real structure-face pick, true range legitimately
|
||||
hopping 40↔600 m at silhouette edges, the caret in motion 74% of frames / ~24 direction
|
||||
reversals per 10 s; CAVERN butte field — 806 instant >50 m jumps, 776 butte↔1200-default
|
||||
boundary crossings, 106/139 s windows swinging >300 m (max 1143 m), caret reversing ~54×/10 s.
|
||||
Every value tracks real geometry; the 500 m/s slide (authentic) then never rests. Elevation was
|
||||
NOT the driver (arena floor is flat y=0) — depth discontinuities are. Cyd's "ray falls through
|
||||
geometry" was checked: 0 fall-through frames on arena, 6/8400 (0.07%, single-frame silhouette
|
||||
grazes) on cavern. Bounded infidelities noted for follow-up, NOT the reported symptom: (a) butte
|
||||
collision (stepped YCyl tiers + cone, buttee_c.sld) is NARROWER than the sculpted buttee.bgf
|
||||
rock — measured a 0.27 m graze past the r=14.6 tier while visibly inside rock (terrain-march
|
||||
tier backstops it, error a few meters at point-blank); (b) the cavern's `butteeu` upper butte
|
||||
sections (buttee.bgf re-instanced at y=50, VideoModel-only, NO solid stream) are
|
||||
pick-transparent — aiming steeply up at a rock tower's top half reads through it; (c) boot
|
||||
transient: before the aim camera is valid, the max-range fallback designates from uninitialized
|
||||
ray floats → `shown` overshoots (~3400) and slides down for the first ~7 s (cosmetically hidden
|
||||
by the 1200 caret peg). Traces: scratchpad i4_*_walk*.log; issue #4 comment has the summary.
|
||||
⚠ A period pod screenshot
|
||||
(`C:\git\image.webp`, likely a DIFFERENT pod revision — its crosshair is ~2.5× taller with
|
||||
arrowhead arms, NOT our binary's ±0.04..0.16 program) structurally CONFIRMS our layout: yellow
|
||||
bar + green caret + colored pip dots on the right ladder, bottom tape + green bowtie, rotating
|
||||
|
||||
@@ -141,7 +141,12 @@ and the boresight pointed that far DOWN, hitting terrain ~50-100 m short of the
|
||||
started over the mech's shoulder. Fix (L4VIDRND `DPLEyeRenderable::Execute`, the `BTSetAimCamera` publish):
|
||||
LEVEL the boresight (drop the view-dir pitch, rebuild an upright basis with world +Y) AND anchor its ORIGIN
|
||||
at the mech's torso (`myEntity->localToWorld` translation + ~5) instead of the render eye. The guns now fire
|
||||
along the mech's horizontal heading from the torso, independent of view mode and terrain tilt. The reticle X
|
||||
along the mech's horizontal heading from the torso, independent of view mode and terrain tilt.
|
||||
**⚠ ORIGIN part SUPERSEDED (2026-07-20, Gitea #16):** the `+5` torso anchor sat BELOW the sight line →
|
||||
shots landed LOW, worse at close range (boresight parallax). The pick ray origin is now the TRUE
|
||||
EYEPOINT: the viewpoint mech's cockpit `siteeyepoint` eye (the BORESIGHT eye) publishes the aim camera
|
||||
in BOTH views from its own eye translation — see [[combat-damage]] Targeting §parallax. The LEVELING +
|
||||
`gBTEyeElev` re-application stand unchanged. The reticle X
|
||||
still carries the torso twist (`BTTwistToReticleX`); reticle Y any elevation. VERIFIED end-to-end: solo,
|
||||
enemy 120 m ahead, **both chase AND cockpit** view → `[target] MECH under boresight ... mechPicks=59
|
||||
groundPicks=0` → beam → `*** DESTROYED`. Also proved `PickRayHit` resolves a REPLICANT when aimed at it
|
||||
|
||||
@@ -127,6 +127,23 @@ on top of it. Full detail: `docs/PROGRESS_LOG.md §5b, §8`.
|
||||
saturate early and fake "per-stride" rates — always timestamp before inferring cadence.
|
||||
- Still deferred: F21 attack velocity/time (all captured events were default 1), F22 HRTF
|
||||
(user has 2 speakers; enable via alsoft.ini `hrtf=true` for headphones).
|
||||
- **Energy-weapon post-beam buzz = AUTHENTIC (Gitea #5 verdict, 2026-07-20)** [T2, measured]:
|
||||
the reported "buzz/electrical sustaining 1–2 s past the beam" is the AUTHORED charge loop
|
||||
(`LaserACharge01-04`, looped, release 1.309 s), started by the WeaponState==3 (Loading) watcher
|
||||
at beam-off, volume authored to track PercentDone (crescendo to ready), stopped on 3→2 with the
|
||||
authored 1.309 s release fade. Measured live (BT_AUD_TAIL=1 + BT_FIRE_PULSE single shots):
|
||||
beam 203–207 ms (authored DischargeTime 0.2 s); sustain loops cut ≤2 ms after leave-Firing
|
||||
(authored release 0); charge StopNote ≤2 ms after Loading→Loaded; fades 1.311–1.326 s vs
|
||||
authored 1.309 s (≤ 1 frame over); laser recharge 2.3–3.8 s, PPC 8.8–16.3 s under multi-weapon
|
||||
generator load (authentic electrical model). NOT the #12 source overflow — no energy preset
|
||||
exceeds 5 zones (max 3; >5 is only the death/UI families: AllExplosion 25, MechExplosion01/
|
||||
Death01 16, AllButtons 14, AllCoolant/AllProjectile 9, Warnings01/AuxExplosion01/AllHits 8,
|
||||
AllWarning 7). Caveat: the reporter's 2026-07-18 build predated 2edde29 (Emitter::SetDirty
|
||||
DelayWatchersFlag poison retirement — PerformAndWatch skips ExecuteWatchers while set,
|
||||
SIMULATE.cpp:461; cleared only by the encore path, UPDATE.cpp:215), which made emitter audio
|
||||
watcher timing encore-erratic that session. Harness kept (env-gated, additive): `[aud-tail]`
|
||||
logging in `L4AUDLVL.cpp` (PLAY/STOP/fade) + `emitter.cpp` (state transitions), and
|
||||
`BT_FIRE_PULSE="on,off"` single-shot trigger pulsing in `mech4.cpp`.
|
||||
- **L4 HAL on Windows + the VS build system: DONE.** [T1]
|
||||
- **Red Planet game logic: COMPLETE & buildable** — `VTV/VTVMPPR/WEAPSYS/...`. RP is at/near playable. [T1]
|
||||
- **Multiplayer: the DOS `NETNUB` driver is already replaced by a ~3.5k-line WinSock2 TCP
|
||||
|
||||
@@ -17,6 +17,19 @@
|
||||
// per frame from AudioHead::Execute; a fade finishing (or being reclaimed by a
|
||||
// restart) stops + rewinds the source so the next SetupPatch sees AL_INITIAL.
|
||||
//
|
||||
|
||||
// [aud-tail] (Gitea #5 verdict harness, additive): BT_AUD_TAIL=1 gates
|
||||
// timestamped play/stop/fade tracing so the audible tail past beam-off can be
|
||||
// measured against the authored releaseSec. clock() is CRT wall-clock ms and
|
||||
// matches the [aud-tail] emitter state-transition logs (emitter.cpp).
|
||||
#include <ctime>
|
||||
static bool AudTailLog()
|
||||
{
|
||||
static int s_on = -1;
|
||||
if (s_on < 0) s_on = (getenv("BT_AUD_TAIL") != 0) ? 1 : 0;
|
||||
return s_on != 0;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct ReleaseFade
|
||||
@@ -32,6 +45,10 @@ namespace
|
||||
|
||||
void FinishFade(ReleaseFade &fade)
|
||||
{
|
||||
if (AudTailLog())
|
||||
DEBUG_STREAM << "[aud-tail] t=" << (long)clock() << "ms fade-END src="
|
||||
<< fade.source << " after " << fade.elapsed << "/" << fade.duration
|
||||
<< "s\n" << std::flush;
|
||||
alSourceStop(fade.source);
|
||||
alSourceRewind(fade.source);
|
||||
alSourcef(fade.source, AL_GAIN, fade.gain0);
|
||||
@@ -57,6 +74,10 @@ namespace
|
||||
fade.duration = duration;
|
||||
fade.elapsed = 0.0f;
|
||||
fade.active = true;
|
||||
if (AudTailLog())
|
||||
DEBUG_STREAM << "[aud-tail] t=" << (long)clock() << "ms fade-START src="
|
||||
<< source << " dur=" << duration << "s gain0=" << fade.gain0
|
||||
<< "\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -315,6 +336,15 @@ void PatchLevelOfDetail::PlayNote(SourceSet sourceSet, int note)
|
||||
{
|
||||
alSourcePlay(sourceSet.sources[i]);
|
||||
{ static int s_p=0; if (getenv("BT_AUDIO_LOG") && s_p++<30) DEBUG_STREAM << "[audio] PlayNote alSourcePlay src=" << sourceSet.sources[i] << " note=" << note << "\n" << std::flush; }
|
||||
if (AudTailLog())
|
||||
{
|
||||
SAMPLEINFO zinfo = PRESET_getSampleInfo(bankID, patchID, i);
|
||||
DEBUG_STREAM << "[aud-tail] t=" << (long)clock() << "ms PLAY src="
|
||||
<< sourceSet.sources[i] << " bank=" << (int)bankID << " patch="
|
||||
<< (int)patchID << " file=" << (zinfo.file ? zinfo.file : "?")
|
||||
<< " loop=" << (int)(zinfo.loop == LoopAtWill)
|
||||
<< " rel=" << zinfo.releaseSec << "s\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
ALenum error = alGetError();
|
||||
@@ -340,6 +370,12 @@ void PatchLevelOfDetail::StopNote(SourceSet sourceSet)
|
||||
SAMPLEINFO info = PRESET_getSampleInfo(bankID,patchID,i);
|
||||
ALenum state;
|
||||
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &state);
|
||||
if (AudTailLog() && state == AL_PLAYING)
|
||||
DEBUG_STREAM << "[aud-tail] t=" << (long)clock() << "ms STOP src="
|
||||
<< sourceSet.sources[i] << " bank=" << (int)bankID << " patch="
|
||||
<< (int)patchID << " file=" << (info.file ? info.file : "?")
|
||||
<< " rel=" << info.releaseSec << "s -> "
|
||||
<< ((info.releaseSec > 0.05f) ? "fade" : "cut") << "\n" << std::flush;
|
||||
if (state == AL_PLAYING && info.releaseSec > 0.05f)
|
||||
{
|
||||
StartReleaseFade(sourceSet.sources[i], info.releaseSec);
|
||||
|
||||
@@ -133,6 +133,31 @@ static float gBTAimVpW = 0.0f; // D3D viewport (backbuffer) size --
|
||||
static float gBTAimVpH = 0.0f; // the dpl2d frame's pixel space
|
||||
static int gBTAimCamValid = 0;
|
||||
|
||||
// issue #16 (boresight parallax) diagnostics: the RENDER camera pose -- the
|
||||
// frame the crosshair is actually drawn in (screen centre of the ACTIVE view).
|
||||
// Published unconditionally by the active eye each frame; BT_PARALLAX_LOG
|
||||
// projects the PICK point back into this view to measure how far the shot
|
||||
// lands from the crosshair. A healthy boresight keeps the pick point pinned
|
||||
// at reticle (0,0) in cockpit view from max range all the way in.
|
||||
static float gBTRenderCamPos[3] = {0, 0, 0};
|
||||
static float gBTRenderCamX[3] = {1, 0, 0}; // camera right
|
||||
static float gBTRenderCamY[3] = {0, 1, 0}; // camera up
|
||||
static float gBTRenderCamZ[3] = {0, 0, 1}; // camera BACK (view dir = -Z)
|
||||
static int gBTRenderCamValid = 0;
|
||||
|
||||
void BTSetRenderCamera(const float pos[3], const float xax[3],
|
||||
const float yax[3], const float zax[3])
|
||||
{
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
gBTRenderCamPos[i] = pos[i];
|
||||
gBTRenderCamX[i] = xax[i];
|
||||
gBTRenderCamY[i] = yax[i];
|
||||
gBTRenderCamZ[i] = zax[i];
|
||||
}
|
||||
gBTRenderCamValid = 1;
|
||||
}
|
||||
|
||||
void BTSetAimCamera(const float pos[3], const float xax[3],
|
||||
const float yax[3], const float zax[3])
|
||||
{
|
||||
@@ -263,6 +288,46 @@ int BTProjectToReticle(const float world[3], float *rx, float *ry)
|
||||
return 1;
|
||||
}
|
||||
|
||||
//
|
||||
// issue #16 (boresight parallax) diag: world point -> reticle coords through
|
||||
// the RENDER camera (the view the crosshair is drawn in), NOT the aim camera
|
||||
// (projecting the pick point through the aim camera is trivially (0,0) --
|
||||
// the pick point lies ON the aim ray). eyeOut receives the render eye
|
||||
// position so the caller can log the true sight-line range. Same reticle
|
||||
// frame as BTProjectToReticle (+y down; unit = half the viewport height).
|
||||
//
|
||||
int BTProjectToRenderView(const float world[3], float *rx, float *ry,
|
||||
float eyeOut[3])
|
||||
{
|
||||
if (!gBTRenderCamValid || gBTAimP11 <= 0.0f || gBTAimP22 <= 0.0f
|
||||
|| gBTAimVpH <= 0.0f)
|
||||
{
|
||||
*rx = 0.0f; *ry = 0.0f;
|
||||
return 0;
|
||||
}
|
||||
if (eyeOut != 0)
|
||||
for (int i = 0; i < 3; ++i)
|
||||
eyeOut[i] = gBTRenderCamPos[i];
|
||||
float rel[3];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
rel[i] = world[i] - gBTRenderCamPos[i];
|
||||
const float xc = rel[0]*gBTRenderCamX[0] + rel[1]*gBTRenderCamX[1] + rel[2]*gBTRenderCamX[2];
|
||||
const float yc = rel[0]*gBTRenderCamY[0] + rel[1]*gBTRenderCamY[1] + rel[2]*gBTRenderCamY[2];
|
||||
const float zc = rel[0]*gBTRenderCamZ[0] + rel[1]*gBTRenderCamZ[1] + rel[2]*gBTRenderCamZ[2];
|
||||
const float depth = -zc; // camera looks down -Z
|
||||
if (depth < 0.1f)
|
||||
{
|
||||
*rx = (xc >= 0.0f) ? 2.0f : -2.0f;
|
||||
*ry = 0.0f;
|
||||
return 0;
|
||||
}
|
||||
const float ndcX = (xc * gBTAimP11) / depth;
|
||||
const float ndcY = (yc * gBTAimP22) / depth;
|
||||
*rx = ndcX * BTGetPresentAspect();
|
||||
*ry = -ndcY;
|
||||
return 1;
|
||||
}
|
||||
|
||||
//
|
||||
// The target HOTBOX projection (the recovered reticle Execute @4cdff7-4ce0f9
|
||||
// [T1]): project the box hugging the target's extents -- x +-4 around the
|
||||
|
||||
@@ -16,6 +16,12 @@ float gBTEyeTwist = 0.0f;
|
||||
// pitch from the pick ray -- this is the pilot's DELIBERATE pitch, re-applied
|
||||
// after the leveling so aimed shots follow the R/F (stick-Y) elevation.
|
||||
float gBTEyeElev = 0.0f;
|
||||
// issue #16 (boresight parallax): non-zero while a BORESIGHT eye (the
|
||||
// viewpoint mech's cockpit siteeyepoint, marked by SetBoresightEye) is alive
|
||||
// and publishing the aim camera. While set, the active-camera fallback
|
||||
// publish is suppressed -- the cockpit eyepoint owns the pick/sight ray in
|
||||
// BOTH views, so toggling V cannot change ballistics.
|
||||
int gBTBoresightEyeLive = 0;
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4vidrnd.h"
|
||||
@@ -5492,6 +5498,7 @@ DPLEyeRenderable::DPLEyeRenderable(
|
||||
myEntity = This_Entity;
|
||||
// myParentDCS = Parent_DCS;
|
||||
myOrientationMatrix = Offset_Matrix;
|
||||
mIsBoresightEye = false; // issue #16: set via SetBoresightEye
|
||||
myEyepointRotation = eyepoint_rotation;
|
||||
if(myEyepointRotation)
|
||||
{
|
||||
@@ -5610,6 +5617,30 @@ DPLEyeRenderable::~DPLEyeRenderable()
|
||||
//// but Phil is making a patch to allow us to check that.
|
||||
//// dpl_RemoveDCSFromDCS(myParentDCS, myDCS);
|
||||
// dpl_DeleteDCS(myDCS);
|
||||
// issue #16: a deleted boresight eye releases the aim-camera publish so
|
||||
// the active camera's fallback path takes over (spectator / teardown)
|
||||
// instead of the aim ray going permanently stale.
|
||||
if (mIsBoresightEye)
|
||||
{
|
||||
extern int gBTBoresightEyeLive;
|
||||
gBTBoresightEyeLive = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// SetBoresightEye (issue #16 boresight parallax): mark this eye as the
|
||||
// VIEWPOINT mech's cockpit eyepoint -- the owner of the aim-camera (pick/
|
||||
// sight ray) publish in BOTH views. Tracks the process-wide latch that
|
||||
// gates the active-camera fallback publish (a viewpoint without a cockpit
|
||||
// eyepoint, e.g. a spectator camera).
|
||||
//
|
||||
void
|
||||
DPLEyeRenderable::SetBoresightEye(bool is_boresight)
|
||||
{
|
||||
extern int gBTBoresightEyeLive;
|
||||
mIsBoresightEye = is_boresight;
|
||||
if (is_boresight)
|
||||
gBTBoresightEyeLive = 1;
|
||||
}
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// TestInstance for the DPLEyeRenderable
|
||||
@@ -5805,13 +5836,42 @@ void
|
||||
{
|
||||
myDevice->SetTransform(D3DTS_VIEW, &view);
|
||||
|
||||
// issue #16 diag: publish the RENDER camera pose (the frame the
|
||||
// crosshair is drawn in) for BT_PARALLAX_LOG's pick-point
|
||||
// back-projection (BTProjectToRenderView). eyeW rows are
|
||||
// right/up/BACK + translation (view = inverse(eyeW)).
|
||||
{
|
||||
extern void BTSetRenderCamera(const float pos[3], const float xax[3],
|
||||
const float yax[3], const float zax[3]);
|
||||
const float rp[3] = { eyeW._41, eyeW._42, eyeW._43 };
|
||||
const float rxx[3] = { eyeW._11, eyeW._12, eyeW._13 };
|
||||
const float ryy[3] = { eyeW._21, eyeW._22, eyeW._23 };
|
||||
const float rzz[3] = { eyeW._31, eyeW._32, eyeW._33 };
|
||||
BTSetRenderCamera(rp, rxx, ryy, rzz);
|
||||
}
|
||||
|
||||
if (dbgEyeExec < 8)
|
||||
DEBUG_STREAM << "[EYE] ACTIVE eye " << (void *)this
|
||||
<< " (mCamera match) pos.y=" << pos.y << "\n" << std::flush;
|
||||
}
|
||||
|
||||
// aim-ray feed (task #36): publish this eye's world pose --
|
||||
// the LookAtRH basis (zaxis = back, view direction = -zaxis)
|
||||
// -- for the reticle pick ray / designator projection.
|
||||
// aim-ray feed (task #36): publish this eye's world pose --
|
||||
// the LookAtRH basis (zaxis = back, view direction = -zaxis)
|
||||
// -- for the reticle pick ray / designator projection.
|
||||
//
|
||||
// OWNERSHIP (issue #16, boresight parallax): the publish is owned by
|
||||
// the BORESIGHT eye -- the viewpoint mech's cockpit siteeyepoint --
|
||||
// in BOTH views (it executes in the mech's draw traversal every
|
||||
// frame, active camera or not), so the chase view's ballistics are
|
||||
// IDENTICAL to the cockpit's and the V toggle cannot change where a
|
||||
// shot lands. Only when no boresight eye is alive (spectator /
|
||||
// teardown) does the ACTIVE camera publish, with the legacy entity
|
||||
// anchor as the origin.
|
||||
{
|
||||
extern int gBTBoresightEyeLive;
|
||||
const bool aimOwner = mIsBoresightEye
|
||||
|| (!gBTBoresightEyeLive && myRenderer->mCamera == this);
|
||||
if (aimOwner)
|
||||
{
|
||||
extern void BTSetAimCamera(const float pos[3], const float xax[3],
|
||||
const float yax[3], const float zax[3]);
|
||||
@@ -5865,16 +5925,31 @@ void
|
||||
D3DXVec3Normalize(&xax, &xax);
|
||||
D3DXVec3Cross(&yax, &zax, &xax);
|
||||
}
|
||||
// ORIGIN: the guns fire from the MECH's torso, not the render
|
||||
// eye. In CHASE view the eye sits behind+above the mech, so a
|
||||
// ray from `pos` would start over the shoulder (and, being level,
|
||||
// fly above a ground target). Anchor the boresight at the mech's
|
||||
// XZ + a torso/gun height so the pick works in BOTH views (chase
|
||||
// is the default; cockpit toggles with V). In cockpit view this
|
||||
// is ~the eyepoint anyway.
|
||||
float p[3] = { pos.x, pos.y, pos.z };
|
||||
if (myEntity != 0)
|
||||
// ORIGIN (issue #16, boresight parallax): the pick/sight ray
|
||||
// starts at the TRUE EYEPOINT -- this eye's own world
|
||||
// translation (eyeW row 4, the same origin the crosshair's
|
||||
// view projects from). The binary's sight math runs in the
|
||||
// eyepoint-segment frame: HudSimulation computes the
|
||||
// designator HotBoxVector by transforming the target position
|
||||
// with inverse(eyeSegToWorld @00424da8) o inverse(
|
||||
// R(EyepointRotation)) (@004b7830, part_013.c:5688-5702), the
|
||||
// VIEW is inverse(the live jointtorso->jointeye->siteeyepoint
|
||||
// chain) (FUN_004c22c4 @part_013.c:11742), and the fired beam
|
||||
// CONVERGES from the muzzle segment to the picked point
|
||||
// (@004bace8:7742-7752) -- so pick ray and sight line share
|
||||
// the eye origin. The old `mech.y + 5.0` anchor (a port
|
||||
// improvisation) sat BELOW the sight line: a constant
|
||||
// vertical offset whose angular error grew as range closed --
|
||||
// shots landing LOW under the target up close (Gitea #16).
|
||||
// Measured pre-fix: the pick point slid down the reticle
|
||||
// ry=+0.07 @ 50u -> +0.32 @ 10u -> +1.5 @ 2.7u.
|
||||
float p[3] = { eyeW._41, eyeW._42, eyeW._43 };
|
||||
if (!mIsBoresightEye && myEntity != 0)
|
||||
{
|
||||
// no cockpit eyepoint alive (spectator/camera director):
|
||||
// legacy entity anchor -- this camera sits behind/above,
|
||||
// so a ray from its own origin would start over the
|
||||
// shoulder.
|
||||
p[0] = myEntity->localToWorld(3,0);
|
||||
p[1] = myEntity->localToWorld(3,1) + 5.0f; // feet -> torso/gun height
|
||||
p[2] = myEntity->localToWorld(3,2);
|
||||
|
||||
@@ -1716,7 +1716,17 @@ public:
|
||||
virtual void
|
||||
Execute();
|
||||
|
||||
// issue #16 (boresight parallax): mark the VIEWPOINT mech's cockpit
|
||||
// eyepoint (siteeyepoint) as the BORESIGHT eye -- it publishes the aim
|
||||
// camera (pick/sight ray) every frame in BOTH views, so the V toggle
|
||||
// cannot change ballistics. Defined in L4VIDRND.cpp (tracks the
|
||||
// gBTBoresightEyeLive fallback latch).
|
||||
void
|
||||
SetBoresightEye(bool is_boresight);
|
||||
|
||||
protected:
|
||||
bool mIsBoresightEye; // issue #16: this eye owns the aim-camera publish
|
||||
|
||||
dpl_DCS
|
||||
*myDCS,
|
||||
*myParentDCS;
|
||||
|
||||
@@ -466,6 +466,12 @@ HierarchicalDrawComponent*
|
||||
//
|
||||
mEyeCockpit = new DPLEyeRenderable(
|
||||
entity, offset_matrix, parent_DCS, eye_rot);
|
||||
// issue #16 (boresight parallax): the VIEWPOINT mech's cockpit
|
||||
// eyepoint is the BORESIGHT eye -- it owns the aim-camera
|
||||
// (pick/sight ray) publish in BOTH views, from its own eye
|
||||
// origin, so chase and cockpit ballistics are identical (see
|
||||
// DPLEyeRenderable::Execute).
|
||||
mEyeCockpit->SetBoresightEye(true);
|
||||
if (type == insideEntity) // true inside build: it IS the camera
|
||||
mCamera = mEyeCockpit;
|
||||
dbg_eye = 1;
|
||||
|
||||
@@ -329,3 +329,72 @@ bool
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------//
|
||||
// BTGroundRayHitExact -- BT_RANGE_LOG diagnostic (Gitea #4 verdict
|
||||
// instrumentation, uncommitted): ANALYTIC closest-hit ray intersect
|
||||
// (Moller-Trumbore, no backface cull, no step size, no height band) over the
|
||||
// SAME visual triangle set the march samples -- including vertical walls and
|
||||
// ceilings, which SampleBand's XZ point-in-triangle test cannot see. The
|
||||
// march-vs-exact range difference on the same ray is direct evidence for (or
|
||||
// against) the "ray falls through geometry" hypothesis; it is NOT used by any
|
||||
// gameplay path.
|
||||
//---------------------------------------------------------------------------//
|
||||
bool
|
||||
BTGroundRayHitExact(float ox, float oy, float oz,
|
||||
float dx, float dy, float dz,
|
||||
float maxDist, float *hx, float *hy, float *hz)
|
||||
{
|
||||
if (gState == 0)
|
||||
gState = BuildTables() ? 1 : -1;
|
||||
if (gState != 1)
|
||||
return false;
|
||||
|
||||
float bestT = maxDist;
|
||||
bool found = false;
|
||||
for (size_t ii = 0; ii < gInsts.size(); ++ii)
|
||||
{
|
||||
const VgInst &inst = gInsts[ii];
|
||||
// local-space ray (instances are translation-placed only)
|
||||
const float lox = ox - inst.px, loy = oy - inst.py, loz = oz - inst.pz;
|
||||
const VgMesh &m = gMeshes[inst.mesh];
|
||||
for (size_t ti = 0; ti < m.tris.size(); ++ti)
|
||||
{
|
||||
const VgTri &t = m.tris[ti];
|
||||
const float e1x = t.bx - t.ax, e1y = t.by - t.ay, e1z = t.bz - t.az;
|
||||
const float e2x = t.cx - t.ax, e2y = t.cy - t.ay, e2z = t.cz - t.az;
|
||||
// p = dir x e2
|
||||
const float px = dy * e2z - dz * e2y;
|
||||
const float py = dz * e2x - dx * e2z;
|
||||
const float pz = dx * e2y - dy * e2x;
|
||||
const float det = e1x * px + e1y * py + e1z * pz;
|
||||
if (det > -1e-9f && det < 1e-9f)
|
||||
continue; // parallel
|
||||
const float inv = 1.0f / det;
|
||||
const float tx = lox - t.ax, ty = loy - t.ay, tz = loz - t.az;
|
||||
const float u = (tx * px + ty * py + tz * pz) * inv;
|
||||
if (u < 0.0f || u > 1.0f)
|
||||
continue;
|
||||
// q = tvec x e1
|
||||
const float qx = ty * e1z - tz * e1y;
|
||||
const float qy = tz * e1x - tx * e1z;
|
||||
const float qz = tx * e1y - ty * e1x;
|
||||
const float v = (dx * qx + dy * qy + dz * qz) * inv;
|
||||
if (v < 0.0f || u + v > 1.0f)
|
||||
continue;
|
||||
const float tt = (e2x * qx + e2y * qy + e2z * qz) * inv;
|
||||
if (tt > 0.05f && tt < bestT)
|
||||
{
|
||||
bestT = tt;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
{
|
||||
*hx = ox + dx * bestT;
|
||||
*hy = oy + dy * bestT;
|
||||
*hz = oz + dz * bestT;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
#include <mechweap.hpp> // MechWeapon::GetExplosionResourceID (per-round detonation)
|
||||
#include <mislanch.hpp> // MissileLauncher::ClassDerivations (splash-radius gate)
|
||||
#include "btinput.hpp" // the CONTROLS.MAP + XInput binding engine
|
||||
#include <string.h> // [aud-tail] strchr -- BT_FIRE_PULSE "on,off" parse (Gitea #5, instrumentation only)
|
||||
#if !defined(PLAYER_HPP)
|
||||
# include <player.hpp> // Player::VehicleDeadMessage -- the death->respawn notification (task #52)
|
||||
#endif
|
||||
@@ -2932,6 +2933,34 @@ void
|
||||
sGotoDrive = (gv && *gv) ? 1 : 0;
|
||||
}
|
||||
gBTDrive.fireForced = sAutoFire;
|
||||
// [aud-tail] (Gitea #5 verdict harness, additive): BT_FIRE_PULSE="on,off"
|
||||
// (frames) pulses the forced trigger instead of holding it, so a SINGLE
|
||||
// energy shot is followed by trigger silence -- the reported scenario
|
||||
// (beam ends, buzz tail runs out alone). Overrides BT_AUTOFIRE's hold.
|
||||
{
|
||||
static int s_fpOn = -1, s_fpOff = 0, s_fpCount = 0;
|
||||
if (s_fpOn < 0)
|
||||
{
|
||||
const char *fp = getenv("BT_FIRE_PULSE");
|
||||
if (fp && *fp)
|
||||
{
|
||||
s_fpOn = atoi(fp);
|
||||
if (s_fpOn <= 0) s_fpOn = 6;
|
||||
const char *comma = strchr(fp, ',');
|
||||
s_fpOff = comma ? atoi(comma + 1) : 600;
|
||||
if (s_fpOff <= 0) s_fpOff = 600;
|
||||
}
|
||||
else
|
||||
{
|
||||
s_fpOn = 0;
|
||||
}
|
||||
}
|
||||
if (s_fpOn > 0)
|
||||
{
|
||||
gBTDrive.fireForced =
|
||||
((s_fpCount++ % (s_fpOn + s_fpOff)) < s_fpOn) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
if (sAutoDrive > 0.0f)
|
||||
{
|
||||
gBTDrive.forced = 1;
|
||||
@@ -3324,7 +3353,18 @@ void
|
||||
}
|
||||
// STOP radius: "enemy" holds at weapon range so it can shoot
|
||||
// instead of ramming; a fixed point drives right up to the spot.
|
||||
const float stopDist = (s_goto == 2) ? 300.0f : 5.0f;
|
||||
// BT_GOTO_STOP overrides (issue #16 parallax test: approach the
|
||||
// dummy from range all the way in to 3/4-screen).
|
||||
float stopDist = (s_goto == 2) ? 300.0f : 5.0f;
|
||||
{
|
||||
static float s_stopOv = -1.0f;
|
||||
if (s_stopOv < 0.0f)
|
||||
{
|
||||
const char *so = getenv("BT_GOTO_STOP");
|
||||
s_stopOv = (so && *so) ? (float)atof(so) : 0.0f;
|
||||
}
|
||||
if (s_stopOv > 0.0f) stopDist = s_stopOv;
|
||||
}
|
||||
const float stopDist2 = stopDist * stopDist;
|
||||
const int arrived = (dist2 < stopDist2);
|
||||
// publish for the mapper bridge (the real-controls path reads
|
||||
@@ -3353,7 +3393,20 @@ void
|
||||
// only from BT_AUTODRIVE). Drive forward toward the target and cut
|
||||
// the throttle inside the stop radius so it holds at firing range.
|
||||
gBTDrive.forced = 1;
|
||||
gBTDrive.forcedThrottle = arrived ? 0.0f : 0.8f;
|
||||
// BT_GOTO_THR: approach-speed override (issue #16 parallax test --
|
||||
// a walking approach spans the render-live window so the curve can
|
||||
// be sampled from far to near).
|
||||
float gthr = 0.8f;
|
||||
{
|
||||
static float s_thrOv = -1.0f;
|
||||
if (s_thrOv < 0.0f)
|
||||
{
|
||||
const char *to = getenv("BT_GOTO_THR");
|
||||
s_thrOv = (to && *to) ? (float)atof(to) : 0.0f;
|
||||
}
|
||||
if (s_thrOv > 0.0f) gthr = s_thrOv;
|
||||
}
|
||||
gBTDrive.forcedThrottle = arrived ? 0.0f : gthr;
|
||||
turn = gBTGotoTurn; // non-real-controls fallback path
|
||||
}
|
||||
}
|
||||
@@ -4581,6 +4634,16 @@ void
|
||||
Point3D hotPoint; // picked world point on its hull
|
||||
Entity *pickTarget = 0; // what the boresight ray hit (mech/terrain)
|
||||
Point3D pickPoint; // where it hit
|
||||
// BT_RANGE_LOG (Gitea #4 VERDICT instrumentation -- uncommitted diag):
|
||||
// capture the boresight ray + which pick tier won this frame, so the
|
||||
// HUD-feed block below can trace the range chain per frame.
|
||||
// rlPickClass: 0=no ray/no pick 1=mech 2=structure (static solid
|
||||
// tree) 3=ground march (visual-terrain step march) 4=max-range
|
||||
// default designation (nothing pickable within 1200)
|
||||
int rlPickClass = 0;
|
||||
int rlHaveRay = 0;
|
||||
float rlRayS[3] = {0,0,0}, rlRayD[3] = {0,0,0};
|
||||
float rlStructD = -1.0f; // sqrt range of the structure tier hit
|
||||
static int gAimNoRay = 0, gAimGround = 0, gAimHits = 0; // 1Hz diagnostics
|
||||
{
|
||||
extern int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3]);
|
||||
@@ -4731,6 +4794,9 @@ void
|
||||
{
|
||||
Point3D rayStart(rs[0], rs[1], rs[2]);
|
||||
Vector3D rayDir(rd[0], rd[1], rd[2]);
|
||||
rlHaveRay = 1; // BT_RANGE_LOG capture (issue #4)
|
||||
rlRayS[0] = rs[0]; rlRayS[1] = rs[1]; rlRayS[2] = rs[2];
|
||||
rlRayD[0] = rd[0]; rlRayD[1] = rd[1]; rlRayD[2] = rd[2];
|
||||
// MP TARGETING (task #46): walk EVERY other living mech (the solo
|
||||
// dummy AND every peer replicant, from the live-mech registry) and
|
||||
// pick the CLOSEST one the boresight ray strikes -- generalising
|
||||
@@ -4776,6 +4842,7 @@ void
|
||||
structDist = dx*dx + dy*dy + dz*dz;
|
||||
structPoint = sp;
|
||||
haveStruct = true;
|
||||
rlStructD = (float)Sqrt(structDist); // BT_RANGE_LOG (issue #4)
|
||||
static float s_wp = 0.0f; s_wp += dt;
|
||||
if (getenv("BT_GROUND_LOG") && s_wp >= 1.0f)
|
||||
{
|
||||
@@ -4795,6 +4862,7 @@ void
|
||||
{
|
||||
pickTarget = hotTarget; // mech is the closest hit
|
||||
pickPoint = hotPoint;
|
||||
rlPickClass = 1; // BT_RANGE_LOG (issue #4)
|
||||
++gAimHits;
|
||||
}
|
||||
else if (haveStruct)
|
||||
@@ -4815,6 +4883,7 @@ void
|
||||
// HudSim part_013.c:5620).
|
||||
pickTarget = (structOwner != 0) ? structOwner : gBTTerrainEntity;
|
||||
pickPoint = structPoint;
|
||||
rlPickClass = 2; // BT_RANGE_LOG (issue #4)
|
||||
++gAimGround;
|
||||
}
|
||||
else
|
||||
@@ -4831,6 +4900,7 @@ void
|
||||
{
|
||||
pickTarget = gBTTerrainEntity;
|
||||
pickPoint.x = hx; pickPoint.y = hy; pickPoint.z = hz;
|
||||
rlPickClass = 3; // BT_RANGE_LOG (issue #4)
|
||||
++gAimGround;
|
||||
}
|
||||
}
|
||||
@@ -4871,12 +4941,44 @@ void
|
||||
MECH_TARGET_POS(this) = farPt;
|
||||
targetReticle.targetEntity = gBTTerrainEntity;
|
||||
targetReticle.rayIntersection = farPt;
|
||||
rlPickClass = 4; // BT_RANGE_LOG: max-range default (issue #4)
|
||||
}
|
||||
else
|
||||
{
|
||||
MECH_TARGET_ENTITY(this) = 0; // no world sentinel -> no target
|
||||
MECH_TARGET_SUBIDX(this) = -1;
|
||||
}
|
||||
|
||||
// [parallax] diag (issue #16): project the pick point back into the
|
||||
// RENDER view -- the frame the crosshair is drawn in. Reticle +y is
|
||||
// down, so ry > 0 = the pick lands BELOW the crosshair. A healthy
|
||||
// boresight holds (rx,ry) ~ (0,0) from max range all the way in; a
|
||||
// pick ray anchored below the sight line shows ry sliding DOWN
|
||||
// (positive) as range closes.
|
||||
if (getenv("BT_PARALLAX_LOG") && pickTarget != 0)
|
||||
{
|
||||
static float s_pl = 0.0f; s_pl += dt;
|
||||
if (s_pl >= 0.5f)
|
||||
{
|
||||
s_pl = 0.0f;
|
||||
extern int BTProjectToRenderView(const float world[3],
|
||||
float *rx, float *ry, float eyeOut[3]);
|
||||
float w[3] = { (float)pickPoint.x, (float)pickPoint.y,
|
||||
(float)pickPoint.z };
|
||||
float prx = 0.0f, pry = 0.0f, eo[3] = {0,0,0};
|
||||
if (BTProjectToRenderView(w, &prx, &pry, eo))
|
||||
{
|
||||
float pdx = w[0]-eo[0], pdy = w[1]-eo[1], pdz = w[2]-eo[2];
|
||||
float rng = (float)Sqrt(pdx*pdx + pdy*pdy + pdz*pdz);
|
||||
DEBUG_STREAM << "[parallax] range=" << rng
|
||||
<< " rx=" << prx << " ry=" << pry
|
||||
<< " mech=" << ((hotTarget != 0 && pickTarget == hotTarget) ? 1 : 0)
|
||||
<< " pick=(" << w[0] << "," << w[1] << "," << w[2] << ")"
|
||||
<< " eye=(" << eo[0] << "," << eo[1] << "," << eo[2] << ")"
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DETERMINISTIC FIRE-AT-STRUCTURE TEST (BT_FIRE_AT_STRUCT, task #50 verify):
|
||||
@@ -5052,6 +5154,53 @@ void
|
||||
BTSetHudTargetRange((Scalar)sShownRange);
|
||||
}
|
||||
|
||||
// BT_RANGE_LOG (Gitea #4 VERDICT instrumentation -- uncommitted diag):
|
||||
// per-frame trace of the whole range chain. Alongside the pick the
|
||||
// game actually used, re-cast the SAME ray through (a) the 4m-step
|
||||
// visual-terrain march (BTGroundRayHit) and (b) an ANALYTIC
|
||||
// Moller-Trumbore intersect over the same visual triangle set incl.
|
||||
// walls/ceilings (BTGroundRayHitExact) -- march-vs-exact divergence is
|
||||
// direct fall-through evidence; exact-vs-structure divergence flags
|
||||
// visible geometry missing from the static collision tree.
|
||||
if (getenv("BT_RANGE_LOG"))
|
||||
{
|
||||
static float s_rlT = 0.0f; s_rlT += (float)dt;
|
||||
extern bool BTGroundRayHit(float,float,float, float,float,float,
|
||||
float, float*,float*,float*);
|
||||
extern bool BTGroundRayHitExact(float,float,float, float,float,float,
|
||||
float, float*,float*,float*);
|
||||
float mhx=0, mhy=0, mhz=0, ehx=0, ehy=0, ehz=0;
|
||||
float marchD = -1.0f, exactD = -1.0f;
|
||||
if (rlHaveRay)
|
||||
{
|
||||
if (BTGroundRayHit(rlRayS[0], rlRayS[1], rlRayS[2],
|
||||
rlRayD[0], rlRayD[1], rlRayD[2], 1200.0f, &mhx, &mhy, &mhz))
|
||||
{
|
||||
float ddx = mhx-rlRayS[0], ddy = mhy-rlRayS[1], ddz = mhz-rlRayS[2];
|
||||
marchD = sqrtf(ddx*ddx + ddy*ddy + ddz*ddz);
|
||||
}
|
||||
if (BTGroundRayHitExact(rlRayS[0], rlRayS[1], rlRayS[2],
|
||||
rlRayD[0], rlRayD[1], rlRayD[2], 1200.0f, &ehx, &ehy, &ehz))
|
||||
{
|
||||
float ddx = ehx-rlRayS[0], ddy = ehy-rlRayS[1], ddz = ehz-rlRayS[2];
|
||||
exactD = sqrtf(ddx*ddx + ddy*ddy + ddz*ddz);
|
||||
}
|
||||
}
|
||||
Point3D rp = MECH_TARGET_POS(this);
|
||||
DEBUG_STREAM << "[range] t=" << s_rlT
|
||||
<< " true=" << trueRange << " shown=" << sShownRange
|
||||
<< " cls=" << rlPickClass << " ray=" << rlHaveRay
|
||||
<< " structD=" << rlStructD
|
||||
<< " marchD=" << marchD << " exactD=" << exactD
|
||||
<< " pick=(" << rp.x << "," << rp.y << "," << rp.z << ")"
|
||||
<< " eye=(" << rlRayS[0] << "," << rlRayS[1] << "," << rlRayS[2] << ")"
|
||||
<< " dir=(" << rlRayD[0] << "," << rlRayD[1] << "," << rlRayD[2] << ")"
|
||||
<< " exact=(" << ehx << "," << ehy << "," << ehz << ")"
|
||||
<< " pos=(" << localOrigin.linearPosition.x << ","
|
||||
<< localOrigin.linearPosition.y << ","
|
||||
<< localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
}
|
||||
|
||||
gBTHudHeading = gDriveHeading; // CompassHeading (attr 0xD)
|
||||
gBTHudTwist = (float)TorsoHeading(); // RotationOfTorsoHorizontal (attr 4)
|
||||
gBTHudTwistLimit = (float)GetHorizontalFiringReach();// HorizontalTorsoLimit (attrs 5/6)
|
||||
|
||||
Reference in New Issue
Block a user