Files
RP412/RP/VTVMPPR.h
T
CydandClaude Opus 5 e2bcb29a53 The throttle a pod respawns under is nobody's order
Player request from the six-player night: a pod that dies at full throttle
reappears under a hand still holding full throttle, and launches on it.
The pod should respawn with the throttle at zero and stay there until the
player has brought the control back to zero themselves. The arcade never
met the problem in this form - its throttle was a physical lever that kept
its position regardless - but the same idea protects a real lever too,
which matters now that the pods want this code.

The latch lives in VTVControlsMapper, the one place every control source -
RIO lever, pad trigger, keyboard axis, input script - funnels through, per
simulation step, so it is deterministic and local to the owning machine by
construction (the mapper only runs on MasterInstance).

VTV::Reset arms it through DeathReset, with one wrinkle: the controls
mapper is subsystem ZERO, below BasicSubsystemCount, so Reset's subsystem
loop has never reached it. It is called explicitly now. Only RegularReset
latches - that is the death respawn and the first spawn (so a throttle
held through the countdown no longer buys a flying start). Football's
repositioning reset does not latch, and mission review has no controls.

While latched the mapper computes as though the throttle were zero, but
only for the pass: the true lever position is restored at the bottom of
the function, so the cockpit gauge and the watchers keep showing the
player the hand they need to bring down. The restore also keeps the
release test honest - RIO analog events arrive on CHANGE, so a zeroed
attribute would otherwise sit at zero, release the latch by itself, and
hand back a live throttle the moment the hand twitched.

Release is at 5% of travel: wide enough for a resting trigger or a real
lever's potentiometer sitting a few counts off its stop, narrow enough
that easing off does not satisfy it.

Proven with the input-script harness, both directions. Throttle held at
1.0 from the green light: the pod moves 1.3cm in 30 seconds, all of it
hover settle. Throttle at zero for two seconds then 1.0: the latch
releases in the quiet window and the pod is 665m down the track twenty
seconds later. The second run is also the normal-play case - a control
already at zero releases the latch on the first step, invisibly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 21:08:16 -05:00

272 lines
6.2 KiB
C++

#pragma once
#include "vtvsub.h"
#include "..\munga\average.h"
//##########################################################################
//################ VTVControlsMapper::ModelResource ##################
//##########################################################################
class VTVControlsMapper:
public VTVSubsystem
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data support
//
public:
static Derivation *GetClassDerivations();
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Messaging Support
//
public:
enum {
ConfigureControlsMessageID = VTVSubsystem::NextMessageID,
ConfigureSideSlipMappablesMessageID,
ChooseSideSlipMessageID,
ConfigureLiftCutMappablesMessageID,
ChooseLiftCutMessageID,
ConfigureHornMappablesMessageID,
ChooseHornMessageID,
ActivateHornMessageID,
ConfigurePTTMappablesMessageID,
ChoosePTTMessageID,
ActivatePTTMessageID,
ToggleReticleMessageID,
NextMessageID
};
void
ConfigureControlsMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ConfigureSideSlipMappablesMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ChooseSideSlipMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ConfigureLiftCutMappablesMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ChooseLiftCutMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ConfigureHornMappablesMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ChooseHornMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ActivateHornMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ConfigurePTTMappablesMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ChoosePTTMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ActivatePTTMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
void
ToggleReticleMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
);
private:
static const HandlerEntry MessageHandlerEntries[];
protected:
//static MessageHandlerSet MessageHandlers;
static MessageHandlerSet& GetMessageHandlers();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support
//
public:
enum {
StickPositionAttributeID = VTVSubsystem::NextAttributeID,
ThrottlePositionAttributeID,
PedalsPositionAttributeID,
ReverseThrustAttributeID,
LiftCutAttributeID,
SideSlipAttributeID,
PowerDemandAttributeID,
AngularVelocityDemandAttributeID,
BrakeLightsAttributeID,
LookLeftAttributeID,
LookRightAttributeID,
LookBehindAttributeID,
LookUpAttributeID,
ControlModeAttributeID,
HornBlastAttributeID,
MustMatchAttributeID,
ReverseThrustEngagedAttributeID, // ECH 8/3/95 - HACK - deal with thrust
LiftCutEngagedAttributeID,
PTTStatusAttributeID,
NextAttributeID
};
private:
static const IndexEntry AttributePointers[];
protected:
//static AttributeIndexSet AttributeIndex
static AttributeIndexSet& GetAttributeIndex();
//
// public attribute declarations go here
//
public:
ControlsJoystick stickPosition;
Scalar throttlePosition;
Scalar pedalsPosition;
ControlsButton
reverseThrust,
liftCut,
sideSlip,
rollLeft,
rollRight,
pitchUp,
pitchDown;
Vector3D
powerDemand,
angularVelocityDemand;
int brakeLights;
ControlsButton
lookLeft,
lookRight,
lookBehind,
lookUp,
hornBlast,
pttStatus;
// ECH 8/3/95 - HACK - deal with thrust
AverageOf<Scalar>
averageOfForwardThrustDemand;
Scalar
forwardThrustDemand;
Logical
reverseThrustEngaged;
Logical
liftCutEngaged;
enum {
BasicMode = 0,
StandardMode,
VeteranMode,
MasterMode
}
controlMode;
unsigned int
mustMatch,
mayMatch;
protected:
ControlsButton
previousPTTStatus; // used to detect change in PTT status
//
// True from a respawn until the throttle control has been seen at
// (near) zero. While set, InterpretControls behaves as though the
// throttle were zero, whatever the hand is doing; the attribute
// itself keeps the real lever value so the cockpit gauge shows the
// player what they must bring down. See DeathReset.
//
Logical
throttleLatched;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Model Support
//
public:
enum {
ConfigurationState = VTVSubsystem::StateCount,
StateCount
};
typedef void
(VTVControlsMapper::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
InterpretControls(Scalar time_slice);
void
SetConfigurationState(Logical enter_config);
//
// Latch the throttle at zero until the player's control comes back to
// zero. Called by VTV::Reset on a regular reset - a death respawn, and
// the first spawn - because the hand that was holding full throttle
// when the pod died is still holding it when the pod reappears, and
// the pod used to launch on it. The arcade never had the problem the
// same way: its throttle was a physical lever the pilot had to move
// regardless.
//
// Football and mission review resets do not latch: one repositions
// every pod mid-game where the friction is unwanted, the other has no
// controls at all.
//
void
DeathReset(int reset_command);
virtual void
CreateTemporaryEventMappings(
Receiver *receiver,
Receiver::MessageID config_message_id
),
RemoveTemporaryEventMappings(),
AddOrErase(
unsigned int button_ID,
Receiver *target,
Receiver::MessageID message_ID
),
AddOrErase(
unsigned int button_ID,
ControlsButton *destination
);
virtual void
NotifyOfControlModeChange(int new_mode);
virtual void
NotifyOfConfigurationModeChange(Logical new_state);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
VTVControlsMapper(
VTV *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data
);
~VTVControlsMapper();
Logical
TestInstance() const;
};