BT410 5.3.120: the mech ducks -- the master perf's duck driver landed whole, and the collision cylinder breathes with it

The first block reconstructed from the isolated master performance
(@004a9f61..@004aa155, instruction-level read of the ndisasm listing), and
with it the Mech's authentic message roster straight from the binary's own
handler table: RealMaxSpeed 0x15, BalanceCoolant 0x16, SetBurningState
0x17, ClearBurningState 0x18, EjectPilot 0x19, DuckRequest 0x1A -- the
enum now exists in MECH.HPP with those names.

The system end to end: a DuckRequest message (@0049fa00) latches ONE
evaluation -- the latch is consumed every master frame and cleared by
Reset. The phase machine reads the raw simulation state, the mapper's
duck button cell and the analog demand (deadzone 1e-4): from gait 0/4 it
arms the sqd clip on the LEG channel only (slot 2 -- the slot map, not
the enum's walk names), from the ducked hold it arms squ (slot 3), and a
mech that starts limping while ducked is stood up by force. The
collision-volume alarm (level 1 standing / 0 ducked, guarded by the
authentic "Whoa! Bad Collision Volume State!") swaps the collision
template's maxY -- the box TOP; the ground probe's minY is untouched --
between the ctor-captured height and 0.6x it (the double at @004a2d38).
duckState publishes for the cockpit's crouch selector.

Live-verified end to end: BT_FORCE_DUCK (an authentic press every 4s on
its own clock) cycles press -> sqd -> volume 9.91->5.94 -> ducked hold ->
press -> squ -> standing, continuously, no faults. En route the test
machine-checked three neighbors for free: the squat clips resolve and
play at the idle rate, the clip-finish transitions for slots 2/3 fire,
and the machine correctly REFUSES to duck while walking (legState 5).

Deferred, recorded: the per-arm MarkUpdate masks (8, 1) ride the
still-empty ForceUpdate (the MP update-record feed), and the mapper duck
cell's streamed binding id awaits a BT_MAP_LOG audit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-06 17:23:24 -05:00
co-authored by Claude Fable 5
parent 06bc863b33
commit 6b9811473b
7 changed files with 641 additions and 213 deletions
@@ -0,0 +1,85 @@
# pod_render_norio.conf -- pod_render_rec with the RIO serial port OFF.
#
# The emulator log shows a steady stream of serial1 RX OVERRUN errors on
# the RIO pipe, and controls post their events at HighEventPriority
# UNCONDITIONALLY (CONTROLS.HPP:250) -- so a chattering RIO port would
# flood a priority the background pump always serves first, starving the
# priority-0 renderer events the load gate waits on. This conf tests that
# by removing the port entirely. Everything else is identical to the rec
# conf, so a launch here and a hang there isolates the RIO.
#
[sdl]
output=opengl
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
priority=higher,higher
[dosbox]
memsize=32
machine=svga_s3
[cpu]
core=dynamic
cputype=pentium
cycles=max
[sblaster]
sbtype=sb16
sbbase=220
irq=5
dma=1
hdma=5
[mixer]
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
# emulation-thread stalls (RIO retry recovery) don't audibly chop
rate=44100
blocksize=1024
prebuffer=60
[serial]
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
# VWE fork namedpipe backend (com0com/realport retired -- COM1/COM2 gone):
# DOSBox = pipe client (retry), vRIO/vPLASMA apps = servers; an unconnected
# pipe behaves as an unplugged cable so the mission still runs. serialnamedpipe.h
serial1=disabled
serial2=namedpipe pipe:vplasma
# live UNBUFFERED game output: DOS char devices are not buffered, so
# redirecting stdout to COM3 lands every line immediately. A normal
# '> file' redirect stays 0 bytes until the process exits, which hides
# all progress on a run that does NOT crash.
serial3=file file:C:\VWE\TeslaRel410\emulator\render-bridge\podlog.txt
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
c:
cd \REL410\BT
set VIDEOFORMAT=svga
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
rem driver relies on; skipping it left the cards uninitialized (silent).
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
set BLASTER=A220 I5 D1 H5 P330 T6
c:\sb16\diagnose /s
set BLASTER=A240 I7 D3 H6 P300 T6
c:\sb16\diagnose /s
set BLASTER=A220 I5 D1 H5 P330 T6
set TEMP=c:\
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
rem attached; stdout redirected so mission-load progress survives kills.
set BT_JOINTS=1
set L4VIEWEXT=1
set BT_STACK_LOG=1
set BT_MECH_LOG=1
set BT_FORCE_DUCK=1
set BT_GAUGE_LOG=1
set BT_FORCE_THROTTLE=0
set BT_FORCE_TURN=0
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r s n p
32rtm.exe -x
BTL4REC.EXE -egg testarn.egg > COM3
echo GAME-RC=%errorlevel% >> RC.TXT
32rtm.exe -u
echo ALPHA1-RUN-DONE
pause
+253 -1
View File
@@ -86,7 +86,8 @@ Derivation
const Receiver::HandlerEntry
Mech::MessageHandlerEntries[] =
{
MESSAGE_ENTRY(Mech, TakeDamage)
MESSAGE_ENTRY(Mech, TakeDamage),
MESSAGE_ENTRY(Mech, DuckRequest)
};
Mech::MessageHandlerSet
@@ -292,6 +293,25 @@ Mech::Mech(
radarLinearPosition = &localOrigin.linearPosition;
radarAngularPosition = &localOrigin.angularPosition;
duckState = 0;
//
// The duck system's rest state (binary ctor tail @004a1674: the volume
// alarm at +0x4c4 initialises to level 1 = STANDING, and the two
// cylinder heights capture template maxY and 0.6 x it -- the heights
// are seeded after the collision template resolves, below).
//
duckRequestLatch = 0;
duckPhaseRequest = 0;
duckDemand = 0.0f;
collisionVolumeState.Initialize(2);
collisionVolumeState.SetLevel(1);
standingVolumeHeight = 0.0f;
duckedVolumeHeight = 0.0f;
if (collisionTemplate != NULL)
{
standingVolumeHeight = collisionTemplate->maxY;
duckedVolumeHeight = 0.6f * standingVolumeHeight;
}
unstablePercentage = 0.0f; // staged: no instability model yet
collisionSpeed = 0.0f; // staged: audio watcher set (see MECH.HPP)
distanceToMissile = 0.0f;
@@ -810,6 +830,50 @@ Logical
vehicle->GetSimulationState() == 9;
}
//
//#############################################################################
// DuckRequestMessageHandler -- binary @0049fa00 (authentic name "DuckRequest"
// from the handler table @0x10bc7c). A positive request latches one duck
// evaluation for the next master frame; the frame consumes the latch either
// way.
//#############################################################################
//
void
Mech::DuckRequestMessageHandler(Receiver::Message *message)
{
Check(this);
Check_Pointer(message);
if (((int *)(message + 1))[0] > 0)
{
duckRequestLatch = 1;
}
}
//
//#############################################################################
// The collision-volume swap (binaries @004ac04c / @004ac064): write the
// template's maxY -- the box TOP; the ground probe's minY is untouched.
// Level 1 (standing) restores the captured height; level 0 (ducked) the
// 0.6x height.
//#############################################################################
//
void
Mech::SetStandingCollisionVolume()
{
Check(this);
Check(collisionTemplate);
collisionTemplate->maxY = standingVolumeHeight;
}
void
Mech::SetDuckedCollisionVolume()
{
Check(this);
Check(collisionTemplate);
collisionTemplate->maxY = duckedVolumeHeight;
}
//
//#############################################################################
// TakeDamageMessageHandler -- the Mech override of the Entity damage entry
@@ -1177,6 +1241,12 @@ void
void
Mech::Reset(const Origin &origin, Logical full_reset)
{
//
// The binary Reset (@0049fb74) drops any pending duck latch with the
// rest of the per-life state.
//
duckRequestLatch = 0;
Check(this);
localOrigin = origin;
@@ -1619,6 +1689,44 @@ void
//
legResetLatch = 0;
//
// BT_FORCE_DUCK=1: one authentic duck press every ~4s on its own
// clock (independent of the one-shot button harness below) -- the
// DuckRequest message, plus the held cell and analog demand every
// frame. Press one ducks, the next (from the hold) stands: the
// full-machine cycle soak.
//
{
static int s_forceDuck = -1;
static Scalar s_duckClock = 0.0f;
if (s_forceDuck < 0)
{
s_forceDuck = (getenv("BT_FORCE_DUCK") != NULL) ? 1 : 0;
}
if (s_forceDuck)
{
MechControlsMapper
*pulse_mapper = (MechControlsMapper *)subsystemArray[0];
if (pulse_mapper != NULL)
{
pulse_mapper->duckCommand = 1;
}
duckDemand = 1.0f;
s_duckClock += time_slice;
if (s_duckClock >= 4.0f)
{
s_duckClock = 0.0f;
ReceiverDataMessageOf<int>
duck_press(
Mech::DuckRequestMessageID,
sizeof(ReceiverDataMessageOf<int>),
1);
Dispatch(&duck_press);
}
}
}
if (limping)
{
AdvanceLegAnimationGimp(time_slice);
@@ -1641,6 +1749,150 @@ void
((Gyroscope *)gyroSubsystem)->WriteMechJoint();
}
//
// THE DUCK DRIVER (master perf @004a9f61..@004aa155, disasm-decoded).
//
// Phase pick, every frame: not standing (raw state != 0) or duck button
// released -> no request. Standing with the button held: from gait
// state 0/4 (stand / turn-in-place), request duck-DOWN while the analog
// demand clears the 1e-4 deadzone; from gait state 1 (ducked hold),
// request duck-UP; anything else, none.
//
// The arms fire only on a DuckRequest-latched frame of a squat-capable
// mech, bind the LEG channel only (squat is a leg figure), and set the
// volume alarm: down -> level 0 (ducked), up -> level 1 (standing).
// The latch is consumed unconditionally. A mech that STARTS LIMPING
// while ducked is stood up by force (modes 3/4 with gait state 1).
// On a volume-alarm edge the collision template's top swaps between
// the captured heights; any other level is the authentic
// "Whoa! Bad Collision Volume State!" Fail. The dirty marks (binary
// MarkUpdate(8)+MarkUpdate(1) per arm) ride ForceUpdate -- still a
// no-op here until the update-record feed lands.
//
{
int
raw_mode = (int)GetSimulationState();
//
// The mapper lives in roster slot 0 (the binary caches it at
// mech+0x190; our tree resolves the same object through the roster).
//
MechControlsMapper
*duck_mapper = (MechControlsMapper *)subsystemArray[0];
int
duck_held =
(duck_mapper != NULL && duck_mapper->DuckCommand() != 0);
if (raw_mode != 0 || !duck_held)
{
duckPhaseRequest = 0;
}
else
{
int
leg_state = (int)legStateAlarm.GetLevel();
if (leg_state == 0 || leg_state == 4)
{
duckPhaseRequest =
(duckDemand <= 0.0001f && duckDemand >= -0.0001f) ? 0 : 1;
}
else if (leg_state == 1)
{
duckPhaseRequest = 2;
}
else
{
duckPhaseRequest = 0;
}
}
{
static int s_duckEvalLog = -1;
if (s_duckEvalLog < 0)
{
s_duckEvalLog = (getenv("BT_MECH_LOG") != NULL);
}
if (s_duckEvalLog && duckRequestLatch != 0)
{
DEBUG_STREAM << "[duck] eval mode=" << raw_mode
<< " held=" << duck_held
<< " leg=" << legStateAlarm.GetLevel()
<< " phase=" << duckPhaseRequest
<< " capable=" << squatCapable << endl;
}
}
if (duckRequestLatch != 0 && squatCapable != 0)
{
if (duckPhaseRequest == 1)
{
SetLegAnimation(2); // the sqd (squat-down) clip slot -- the
// SLOT map diverges from the enum's walk
// names here (MECH2.NOTES.md)
ForceUpdate(8);
ForceUpdate(1);
collisionVolumeState.SetLevel(0);
}
else if (duckPhaseRequest == 2)
{
SetLegAnimation(3); // the squ (squat-up) clip slot
ForceUpdate(8);
ForceUpdate(1);
collisionVolumeState.SetLevel(1);
}
}
duckRequestLatch = 0;
//
// Limping stands you up (modes 3/4 with the legs still in the
// ducked hold).
//
if (
(raw_mode == 3 || raw_mode == 4) &&
(int)legStateAlarm.GetLevel() == 1
)
{
SetLegAnimation(3); // squ -- forced stand-up
ForceUpdate(8);
ForceUpdate(1);
collisionVolumeState.SetLevel(1);
}
//
// The volume swap on an alarm edge, and the DuckState publish.
//
if (collisionVolumeState.GetState() != collisionVolumeState.GetOldState())
{
static int s_duckLog = -1;
if (s_duckLog < 0)
{
s_duckLog = (getenv("BT_MECH_LOG") != NULL);
}
if (s_duckLog)
{
DEBUG_STREAM << "[duck] volume level "
<< collisionVolumeState.GetOldState() << " -> "
<< collisionVolumeState.GetState()
<< " legState=" << legStateAlarm.GetLevel()
<< " maxY " << standingVolumeHeight
<< "/" << duckedVolumeHeight << endl;
}
switch (collisionVolumeState.GetLevel())
{
case 1:
SetStandingCollisionVolume();
duckState = 0;
break;
case 0:
SetDuckedCollisionVolume();
duckState = 1;
break;
default:
Fail("Whoa! Bad Collision Volume State!\n");
break;
}
}
collisionVolumeState.SetLevel(collisionVolumeState.GetLevel());
}
//
// THE TURN-IN-PLACE DISPATCHER (master perf @0x4aa505-0x4aa588, decoded
// from raw disasm -- Ghidra never decompiled the function). From
+52
View File
@@ -221,6 +221,23 @@
class Mech:
public JointedMover
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Message Support -- the authentic roster, read from the binary's own
// handler table (ids and NAME STRINGS live at 0x10bbf0../0x50c146..):
// 0x12 TakeDamage (Entity overlay), 0x14 PlayerLink (Entity overlay),
// then the mech's own block from Entity::NextMessageID.
//
public:
enum {
RealMaxSpeedMessageID = Entity::NextMessageID, // 0x15 @0049f604
BalanceCoolantMessageID, // 0x16 @0049f728
SetBurningStateMessageID, // 0x17 @0049f674
ClearBurningStateMessageID, // 0x18 @0049f700
EjectPilotMessageID, // 0x19 @0049f854
DuckRequestMessageID, // 0x1A @0049fa00
NextMessageID
};
//
// The per-zone damage code walks the roster / segment table / alarms
// directly (the 1995 arrangement, mirrored by the reconstruction).
@@ -885,6 +902,41 @@
Point3D *radarLinearPosition;
Quaternion *radarAngularPosition;
int duckState;
//
// THE DUCK SYSTEM (master perf @004a9f61..@004aa155, disasm-decoded).
//
// duckRequestLatch (+0x398) ONE-SHOT: set by the DuckRequest
// message handler (@0049fa00, request > 0), consumed -- and
// CLEARED -- every master frame after the arm check; also
// cleared by Reset. Only a latched frame may bind squat clips.
// duckPhaseRequest (+0x3f8) 0 none / 1 duck-down / 2 duck-up,
// recomputed every frame from the raw simulation state, the
// mapper's duck button cell, the leg gait state and the duck
// analog demand (deadzone 1e-4 @0x4ab16c).
// duckDemand (+0x79c) the analog crouch input.
// collisionVolumeState (+0x4c4) the volume alarm: level 1 =
// STANDING, 0 = DUCKED. On an edge the collision template's
// maxY (BoxedSolid +0xc -- the box TOP; the ground probe's
// minY is untouched) swaps between the two ctor-captured
// heights; any other level Fails ("Whoa! Bad Collision
// Volume State!", MECH4.CPP line 417).
// standing/duckedVolumeHeight (+0x518/+0x51c) captured at ctor
// time: template maxY, and 0.6 x it (the double @004a2d38).
//
int duckRequestLatch;
int duckPhaseRequest;
Scalar duckDemand;
AlarmIndicator collisionVolumeState;
Scalar standingVolumeHeight;
Scalar duckedVolumeHeight;
void
DuckRequestMessageHandler(Receiver::Message *message);
void
SetStandingCollisionVolume(); // binary @004ac04c... see .CPP
void
SetDuckedCollisionVolume();
//
// STAGED VALUE (the instability model is not reconstructed).
// The mech streams the authored unstable* effect constants in its
+9
View File
@@ -66,6 +66,11 @@
void
Mech::SetLegAnimation(int state)
{
if ((state == 2 || state == 3) && getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[duck] arm slot " << state
<< " clip=" << animationClips[state] << endl;
}
Check(this);
//
// Bounded by the SLOT count, not the name count: slot 0x20 is the
@@ -192,6 +197,10 @@ Scalar
return 0.0f;
case 2:
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[duck] sqd finished -> ducked hold" << endl;
}
mech->legStateAlarm.SetLevel(1);
return 0.0f;
+18 -2
View File
@@ -86,8 +86,24 @@ per contact by `Mover::ProcessCollisionList`. The decomp
matching our Simulate's structure and locating every staged gap:
1. controls/demand intake (mech.cpp helpers 0x4a4eab/0x4a4ee9/0x4a4f9e)
2. three cerr+SetLevel triplets — illegal-state alarm guards
3. **THE DUCK DRIVER**: three arms of {mech2 SetAnim 0x4a7fc4,
MarkUpdate ×2, AlarmIndicator::SetLevel} — duck down/hold/up
3. **THE DUCK DRIVER** — DECODED + LANDED 5.3.120, live-verified
(BT_FORCE_DUCK cycles down/hold/up continuously on the rig). The
full system: DuckRequest message 0x1A (authentic name from the
binary's handler table; roster = RealMaxSpeed 0x15, BalanceCoolant
0x16, SetBurningState 0x17, ClearBurningState 0x18, EjectPilot 0x19,
DuckRequest 0x1A) latches one evaluation (+0x398, one-shot, Reset
clears); the phase machine reads the RAW simulation state, the
mapper's duck cell (+0x25c) and the analog demand (+0x79c, deadzone
1e-4 @0x4ab16c): gait 0/4 + input -> down (LEG slot 2, sqd), gait 1
-> up (slot 3, squ); limp modes 3/4 force stand-up; the
collisionVolumeState alarm (+0x4c4; 1=standing, 0=ducked; "Whoa! Bad
Collision Volume State!" guard, line 417) swaps the collision
template's maxY (ExtentBox +0xc == the box TOP; the ground probe's
minY untouched) between the ctor-captured height and 0.6x it (the
double @004a2d38); duckState publishes for the cockpit selector.
Still open here: the two MarkUpdate masks (8 then 1) ride our
ForceUpdate no-op, and the mapper duck cell's streamed binding id
awaits a BT_MAP_LOG audit on a duck-bound RES.
4. verify-guarded state read (mech4 accessors + Fail 0x40385c)
5. gyro feed a (vec pair → gyro_b 0x4b2de4, gyro_a 0x4b2d8c)
6. death arm: InDeathTransition 0x49fb54 → the 1.9KB mech2 advancer
+1
View File
@@ -95,6 +95,7 @@ MechControlsMapper::MechControlsMapper(
shared_data
)
{
duckCommand = 0;
Check(owner);
//
// Prime the published control inputs to neutral. Per-frame interpretation
+223 -210
View File
@@ -1,210 +1,223 @@
//===========================================================================//
// File: mechmppr.hpp //
// Project: BattleTech //
// Contents: Implementation details for the mech controls mapper //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#if !defined(MECHMPPR_HPP)
# define MECHMPPR_HPP
# if !defined(SUBSYSTM_HPP)
# include <subsystm.hpp>
# endif
# if !defined(CSTR_HPP)
# include <cstr.hpp>
# endif
# if !defined(CONTROLS_HPP)
# include <controls.hpp>
# endif
# if !defined(VECTOR2D_HPP)
# include <vector2d.hpp>
# endif
//##################### Forward Class Declarations #######################
class Mech;
//###########################################################################
//######################## MechControlsMapper ###########################
//###########################################################################
class MechControlsMapper:
public Subsystem
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation ClassDerivations;
static SharedData DefaultData;
static const IndexEntry AttributePointers[];
static AttributeIndexSet AttributeIndex;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute IDs -- the control inputs the streamed mappings bind to.
// The chain ends at Subsystem::NextAttributeID == 2.
//
public:
enum {
MechControlsMapperPadFirstAttributeID = 2,
StickPositionAttributeID, // 3
ThrottlePositionAttributeID, // 4
PedalsPositionAttributeID, // 5
ReverseThrustAttributeID, // 6
SpeedDemandAttributeID, // 7
TurnDemandAttributeID, // 8
LookForwardAttributeID, // 9
LookLeftAttributeID, // a
LookRightAttributeID, // b
LookBehindAttributeID, // c
LookDownAttributeID, // d
TorsoUpAttributeID, // e
TorsoDownAttributeID, // f
TorsoLeftAttributeID, // 10
TorsoRightAttributeID, // 11
TorsoCenterAttributeID, // 12
ControlModeAttributeID, // 13
DisplayModeAttributeID, // 14
PilotArrayPageAttributeID, // 15
PilotArrayAttributeID, // 16
TargetRangeExponentAttributeID, // authored watcher
NextAttributeID
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Control modes
//
public:
enum ControlMode {
BasicMode = 0,
StandardMode,
VeteranMode
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Look states. A five-state machine off the pod's look buttons; on a state
// change InterpretControls calls Mech::CommitLookState, which re-aims the
// eyepoint from the model's authored look angles (and, once the weapon wave
// lands, re-arms the per-view weapon fire enables).
//
public:
enum LookState {
LookNone = 0,
LookLeftState,
LookRightState,
LookBehindState,
LookDownState
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
MechControlsMapper(
Mech *owner,
int subsystem_ID,
CString subsystem_name,
RegisteredClass::ClassID class_ID,
SharedData &shared_data = DefaultData
);
~MechControlsMapper();
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Per-frame control interpretation (the mapper's Performance): reads the
// engine-pushed raw input attributes (throttle / stick / pedals / buttons)
// and publishes the locomotion + aim demands the mech drive consumes.
//
public:
typedef void
(MechControlsMapper::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
InterpretControls(Scalar time_slice);
Mech*
GetMech() { Check(this); return (Mech *)GetEntity(); }
Scalar
GetSpeedDemand() const { Check(this); return speedDemand; }
Scalar
GetTurnDemand() const { Check(this); return turnDemand; }
//
// The LIMP machines write the demand cell DOWN to the damaged side's
// speed cap (binary GimpLegClipFinished @004a7970 preamble writes
// mapper+0x128 directly) -- a limping mech cannot command more speed
// than its limp figure carries.
//
void
SetSpeedDemand(Scalar demand)
{ Check(this); speedDemand = demand; }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local Data -- the published control inputs (the streamed mappings write
// these; InterpretControls, phase 5.3, reads them to drive the mech).
//
public:
//
// The POD ROSTER (binary mapper attributes PilotArrayPage/PilotArray,
// ids 15/16): the comm scoreboard's row source. Zero = no roster
// authored (a solo mission), which is why the shipped cockpit leaves
// that page blank.
//
int
GetPilotArray() const { Check(this); return pilotArray; }
int
GetPilotArrayPage() const { Check(this); return pilotArrayPage; }
protected:
ControlsJoystick stickPosition;
Scalar throttlePosition;
Scalar pedalsPosition;
ControlsButton reverseThrust;
Scalar speedDemand;
Scalar turnDemand;
ControlsButton lookForward;
ControlsButton lookLeft;
ControlsButton lookRight;
ControlsButton lookBehind;
ControlsButton lookDown;
ControlsButton torsoUp;
ControlsButton torsoDown;
ControlsButton torsoLeft;
ControlsButton torsoRight;
ControlsButton torsoCenter;
int controlMode;
int displayMode;
int pilotArrayPage;
int pilotArray;
int lookState;
int previousLookState;
//
// Takes one reserved word (Scalar and int are both 4 bytes here), so
// the class size and every existing offset stay exactly as they were.
//
Scalar targetRangeExponent; // authored watcher
int reserved[21];
};
#endif
//===========================================================================//
// File: mechmppr.hpp //
// Project: BattleTech //
// Contents: Implementation details for the mech controls mapper //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#if !defined(MECHMPPR_HPP)
# define MECHMPPR_HPP
# if !defined(SUBSYSTM_HPP)
# include <subsystm.hpp>
# endif
# if !defined(CSTR_HPP)
# include <cstr.hpp>
# endif
# if !defined(CONTROLS_HPP)
# include <controls.hpp>
# endif
# if !defined(VECTOR2D_HPP)
# include <vector2d.hpp>
# endif
//##################### Forward Class Declarations #######################
class Mech;
//###########################################################################
//######################## MechControlsMapper ###########################
//###########################################################################
class MechControlsMapper:
public Subsystem
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The duck button cell (binary mapper+0x25c) -- a device-poll write
// destination like the torso command cells; the master perf's duck
// driver reads it as the held-button gate. The streamed attribute
// binding id is still to be audited (BT_MAP_LOG) -- until a RES table
// binds it, BT_FORCE_DUCK drives it.
//
public:
int
DuckCommand()
{ Check(this); return duckCommand; }
int duckCommand;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation ClassDerivations;
static SharedData DefaultData;
static const IndexEntry AttributePointers[];
static AttributeIndexSet AttributeIndex;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute IDs -- the control inputs the streamed mappings bind to.
// The chain ends at Subsystem::NextAttributeID == 2.
//
public:
enum {
MechControlsMapperPadFirstAttributeID = 2,
StickPositionAttributeID, // 3
ThrottlePositionAttributeID, // 4
PedalsPositionAttributeID, // 5
ReverseThrustAttributeID, // 6
SpeedDemandAttributeID, // 7
TurnDemandAttributeID, // 8
LookForwardAttributeID, // 9
LookLeftAttributeID, // a
LookRightAttributeID, // b
LookBehindAttributeID, // c
LookDownAttributeID, // d
TorsoUpAttributeID, // e
TorsoDownAttributeID, // f
TorsoLeftAttributeID, // 10
TorsoRightAttributeID, // 11
TorsoCenterAttributeID, // 12
ControlModeAttributeID, // 13
DisplayModeAttributeID, // 14
PilotArrayPageAttributeID, // 15
PilotArrayAttributeID, // 16
TargetRangeExponentAttributeID, // authored watcher
NextAttributeID
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Control modes
//
public:
enum ControlMode {
BasicMode = 0,
StandardMode,
VeteranMode
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Look states. A five-state machine off the pod's look buttons; on a state
// change InterpretControls calls Mech::CommitLookState, which re-aims the
// eyepoint from the model's authored look angles (and, once the weapon wave
// lands, re-arms the per-view weapon fire enables).
//
public:
enum LookState {
LookNone = 0,
LookLeftState,
LookRightState,
LookBehindState,
LookDownState
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
MechControlsMapper(
Mech *owner,
int subsystem_ID,
CString subsystem_name,
RegisteredClass::ClassID class_ID,
SharedData &shared_data = DefaultData
);
~MechControlsMapper();
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Per-frame control interpretation (the mapper's Performance): reads the
// engine-pushed raw input attributes (throttle / stick / pedals / buttons)
// and publishes the locomotion + aim demands the mech drive consumes.
//
public:
typedef void
(MechControlsMapper::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
InterpretControls(Scalar time_slice);
Mech*
GetMech() { Check(this); return (Mech *)GetEntity(); }
Scalar
GetSpeedDemand() const { Check(this); return speedDemand; }
Scalar
GetTurnDemand() const { Check(this); return turnDemand; }
//
// The LIMP machines write the demand cell DOWN to the damaged side's
// speed cap (binary GimpLegClipFinished @004a7970 preamble writes
// mapper+0x128 directly) -- a limping mech cannot command more speed
// than its limp figure carries.
//
void
SetSpeedDemand(Scalar demand)
{ Check(this); speedDemand = demand; }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local Data -- the published control inputs (the streamed mappings write
// these; InterpretControls, phase 5.3, reads them to drive the mech).
//
public:
//
// The POD ROSTER (binary mapper attributes PilotArrayPage/PilotArray,
// ids 15/16): the comm scoreboard's row source. Zero = no roster
// authored (a solo mission), which is why the shipped cockpit leaves
// that page blank.
//
int
GetPilotArray() const { Check(this); return pilotArray; }
int
GetPilotArrayPage() const { Check(this); return pilotArrayPage; }
protected:
ControlsJoystick stickPosition;
Scalar throttlePosition;
Scalar pedalsPosition;
ControlsButton reverseThrust;
Scalar speedDemand;
Scalar turnDemand;
ControlsButton lookForward;
ControlsButton lookLeft;
ControlsButton lookRight;
ControlsButton lookBehind;
ControlsButton lookDown;
ControlsButton torsoUp;
ControlsButton torsoDown;
ControlsButton torsoLeft;
ControlsButton torsoRight;
ControlsButton torsoCenter;
int controlMode;
int displayMode;
int pilotArrayPage;
int pilotArray;
int lookState;
int previousLookState;
//
// Takes one reserved word (Scalar and int are both 4 bytes here), so
// the class size and every existing offset stay exactly as they were.
//
Scalar targetRangeExponent; // authored watcher
int reserved[21];
};
#endif