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>
This commit is contained in:
Cyd
2026-08-11 21:08:16 -05:00
co-authored by Claude Opus 5
parent 13a2109918
commit e2bcb29a53
8 changed files with 113 additions and 154 deletions
+3
View File
@@ -102,3 +102,6 @@ rpl4.log
rpl4-fail.log
last.spl
SPOOLS/
# Playtest evidence - dumps, logs, symbol snapshots. Kept on disk, not in history.
playtestlogs/
+18
View File
@@ -1702,6 +1702,24 @@ void
subsystem->DeathReset(reset_command);
}
}
//
// The controls mapper too. It is subsystem ZERO, below
// BasicSubsystemCount, so the loop above has never reached it - which
// was fine while it had nothing to reset. It does now: the respawn
// throttle latch, which holds the pod at zero throttle until the
// player's control has come back to zero. See
// VTVControlsMapper::DeathReset for which resets latch and why.
//
{
Subsystem *mapper = GetSubsystem(ControlsMapperSubsystem);
if (mapper)
{
Check(mapper);
mapper->DeathReset(reset_command);
}
}
Check_Fpu();
}
+66
View File
@@ -401,6 +401,28 @@ VTVControlsMapper::AttributeIndexSet& VTVControlsMapper::GetAttributeIndex()
// Model Support
//
//
// How close to zero the throttle control must come to release the latch,
// as a fraction of full travel. Wide enough for a resting pad trigger, a
// wound-down keyboard axis, or a real lever's potentiometer sitting a few
// counts off its stop - the pod bay hardware is where this will matter -
// and narrow enough that it cannot be satisfied by easing off.
//
static const Scalar kThrottleLatchRelease = 0.05f;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
VTVControlsMapper::DeathReset(int reset_command)
{
Check(this);
if (reset_command == VTV::RegularReset)
{
throttleLatched = True;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
@@ -455,6 +477,43 @@ void
Verify(stickPosition.y >= -1.0f && stickPosition.y <= 1.0f);
Verify(pedalsPosition >= -1.0f && pedalsPosition <= 1.0f);
//
//----------------------------------------------------------------
// The respawn throttle latch.
//
// A pod that dies at full throttle reappears under a hand still
// holding full throttle, and used to launch on it. From a respawn
// until the control has been seen back at (near) zero, the mapper
// computes as though the throttle were zero.
//
// The zeroing is for this pass only - the true lever position is
// put back at the bottom of the function - so the attribute the
// cockpit gauge and the watchers read stays the player's actual
// hand, which is the thing they need to see to bring it down. It
// also means the release test reads the real control each step
// rather than last step's overwrite: RIO analog events arrive on
// CHANGE, so an overwritten attribute would otherwise sit at zero,
// release the latch on its own, and hand back a live throttle the
// moment the hand moved.
//
// Placed after the input script so a scripted run latches the same
// way a hand-driven one does, per step, deterministically.
//----------------------------------------------------------------
//
Scalar lever = throttlePosition;
if (throttleLatched)
{
if (lever <= kThrottleLatchRelease)
{
throttleLatched = False;
}
else
{
throttlePosition = 0.0f;
}
}
//
//----------------------------------------------------------------
// Figure out the proper control model to use based upon the speed
@@ -833,6 +892,12 @@ void
intercom->SetPTTStatus((Logical) pttStatus > 0);
}
//
// Put the real lever position back - see the latch above. Identity
// whenever the latch is idle.
//
throttlePosition = lever;
Check_Fpu();
}
@@ -949,6 +1014,7 @@ VTVControlsMapper::VTVControlsMapper(
stickPosition.y = 0.0f;
throttlePosition = 0.0f;
pedalsPosition = 0.0f;
throttleLatched = False; // the first-spawn Reset sets it
reverseThrust = 0;
liftCut = 0;
+26
View File
@@ -182,6 +182,16 @@ 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
//
@@ -206,6 +216,22 @@ public:
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,
-154
View File
@@ -1,154 +0,0 @@
Red Planet 4.12.217 (056ee0b)
Environ: 12 setting(s) from environ.ini
Environ: environ.ini does not mention 24 option(s) this build knows: RP412INPUTFOCUS, L4MAPPOS, L4MAPSCALE, RP412PODIUMHOLD, RP412PHYSICSHZ, RP412INTERP, RP412NETPREDICT, RP412GAUGESLICE, RP412MAPRATE, RP412GAUGEDIAG, RP412LAMPSWEEP, RP412CONNECTWAIT, RP412RECORDSIZE, RP412GAUGEPROFILE, RP412RENDERDIAG, RP412PHYSTRACE, RP412SPAWNZONE, RP412INPUTSCRIPT, RP412JOYLOG, RP412AUDIOVOLUME, RP412AUDIOBASS, RP412VERTEXPROC, RP412VSYNC, RP412CAMLOG
Environ: they are at their built-in defaults - delete environ.ini to get the documented file back
L4CONTROLS=PAD;KEYBOARD
Video: frame target 60 fps, drawing interpolated across the physics step
SteamNetTransport: up as 169.254.8.177 (fake ports 33416 console, 33417 game)
L4Application: -fit chose -res 1920 1080 (100% canvas) for the 2560x1080 monitor
L4Application: -fit placed the window borderless at 2560x1080 before the first mission
FrontEnd: callsign "Pilot" and loadout from pilot.cfg
FrontEnd: saved callsign "PoorImpulseControl" and loadout to pilot.cfg
Lobby: joined 109775241819393971
SteamNetTransport: peer registered: 169.254.88.186 (console 10608, game 10609, id 76561198659597127)
SteamNetTransport: peer registered: 169.254.35.214 (console 62256, game 62257, id 76561197995508393)
SteamNetTransport: peer registered: 169.254.105.170 (console 4648, game 4649, id 76561197970536135)
SteamNetTransport: peer registered: 169.254.240.193 (console 7168, game 7169, id 76561197976435895)
SteamNetTransport: peer registered: 169.254.8.177 (console 33416, game 33417, id 76561197969401853)
SteamNetTransport: peer registered: 169.254.122.50 (console 41360, game 41361, id 76561198022091594)
PadBindings: 59 key buttons, 8 key axes, 10 pad buttons, 5 pad axes
PadRIO: virtual RIO active (XInput pad + keyboard)
PadRIO: controls answer only while the game window has focus
Trying to find the monitor marked as active in Windows...
Monitor 0 was set as active in Windows... setting it as the primary monitor.
Either MFDs are explicitly disabled, we're running in windowed mode, or we don't have enough monitors attached to this machine. MFD monitors will not be detected.
Game is currently running windowed mode- will set all remaining undefined monitors to be the same as the primary monitor.
Video: presentation interval vsync
Video: vertex processing HARDWARE
SVGA16: cockpit canvas at 100% for the 2560x1080 work area
SVGA16: secondary displays at UL 100% UC 100% UR 100% LL 100% LR 100% radar 100%
SVGA16: radar on the bottom centre (L4RADARPOS)
SVGA16: cockpit fitted 1920x1080 at 100% in a 2560x1080 client
GaugeInterpreter: undefined label 'Initialization'
SteamNetTransport: listening on engine port 1501 (fake port 33416)...
PadRIO: controller 0 connected
SteamNetTransport: incoming [#4027028733 P2P steamid:76561198659597127@169.254.248.212:15404 fakeport #0] - accepting
SteamNetTransport: connected [#4027028733 P2P SDR steamid:76561198659597127@169.254.248.212:15404 fakeport #0]
STATUS: socket accepted from 169.254.88.186:1501!
Connected to ConsoleHost at 169.254.88.186:1501
SteamNetTransport: connecting to 169.254.88.186:1502 (fake port 10609)...
SteamNetTransport: connected [#2546421614 P2P SDR steamid:76561198659597127@169.254.88.186:10609]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.35.214:1502 (fake port 62257)...
SteamNetTransport: connected [#3892042932 P2P SDR steamid:76561197995508393@169.254.35.214:62257]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.105.170:1502 (fake port 4649)...
SteamNetTransport: dropped [#1183141278 P2P ?@169.254.105.170:4649] end reason 5003: Timed out attempting to connect
SteamNetTransport: attempt 1 ended in state 5
SteamNetTransport: attempt 2 ended in state 1
SteamNetTransport: connect timed out after 20s (RP412CONNECTWAIT)
Could not open connection to 169.254.105.170:1502.
SteamNetTransport: connecting to 169.254.240.193:1502 (fake port 7169)...
SteamNetTransport: connected [#555564226 P2P SDR steamid:76561197976435895@169.254.240.193:7169]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: listening on engine port 1502 (fake port 33417)...
Connected to GameMachineHost at 169.254.88.186:1502
Connected to GameMachineHost at 169.254.35.214:1502
Connected to GameMachineHost at 169.254.240.193:1502
SteamNetTransport: connecting to 169.254.88.186:1502 (fake port 10609)...
SteamNetTransport: incoming [#1946318435 P2P steamid:76561198022091594@169.254.251.68:21949 fakeport #1] - accepting
SteamNetTransport: connected [#1380037914 P2P SDR steamid:76561198659597127@169.254.88.186:10609]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.35.214:1502 (fake port 62257)...
SteamNetTransport: connected [#1946318435 P2P SDR steamid:76561198022091594@169.254.251.68:21949 fakeport #1]
SteamNetTransport: connected [#371675616 P2P SDR steamid:76561197995508393@169.254.35.214:62257]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.105.170:1502 (fake port 4649)...
SteamNetTransport: dropped [#1416324186 P2P ?@169.254.105.170:4649] end reason 5003: Timed out attempting to connect
SteamNetTransport: attempt 1 ended in state 5
SteamNetTransport: connected [#2299564679 P2P SDR steamid:76561197970536135@169.254.105.170:4649]
SteamNetTransport: connect succeeded (attempt 2)
SteamNetTransport: connecting to 169.254.240.193:1502 (fake port 7169)...
SteamNetTransport: connected [#1693276213 P2P SDR steamid:76561197976435895@169.254.240.193:7169]
SteamNetTransport: connect succeeded (attempt 1)
Listen requested on port 1502 but gameListenerSocket already existed!
STATUS: socket accepted from 169.254.122.50:1502!
Connected to GameMachineHost at 169.254.122.50:1502
Connected to GameMachineHost at 169.254.88.186:1502
Connected to GameMachineHost at 169.254.35.214:1502
Connected to GameMachineHost at 169.254.105.170:1502
Connected to GameMachineHost at 169.254.240.193:1502
All connections completed!
SteamNetTransport: connecting to 169.254.88.186:1502 (fake port 10609)...
SteamNetTransport: incoming [#418395659 P2P steamid:76561198022091594@169.254.251.68:21949 fakeport #1] - accepting
SteamNetTransport: connected [#3771470973 P2P SDR steamid:76561198659597127@169.254.88.186:10609]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.35.214:1502 (fake port 62257)...
SteamNetTransport: connected [#418395659 P2P SDR steamid:76561198022091594@169.254.251.68:21949 fakeport #1]
SteamNetTransport: connected [#107458131 P2P SDR steamid:76561197995508393@169.254.35.214:62257]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.105.170:1502 (fake port 4649)...
SteamNetTransport: connected [#173068106 P2P SDR steamid:76561197970536135@169.254.105.170:4649]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.240.193:1502 (fake port 7169)...
SteamNetTransport: connected [#1962487129 P2P SDR steamid:76561197976435895@169.254.240.193:7169]
SteamNetTransport: connect succeeded (attempt 1)
Listen requested on port 1502 but gameListenerSocket already existed!
STATUS: socket accepted from 169.254.122.50:1502!
Connected to GameMachineHost at 169.254.122.50:1502
Connected to GameMachineHost at 169.254.88.186:1502
Connected to GameMachineHost at 169.254.35.214:1502
Connected to GameMachineHost at 169.254.105.170:1502
Connected to GameMachineHost at 169.254.240.193:1502
All connections completed!
SteamNetTransport: connecting to 169.254.88.186:1502 (fake port 10609)...
SteamNetTransport: connected [#3345733046 P2P SDR steamid:76561198659597127@169.254.88.186:10609]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.35.214:1502 (fake port 62257)...
SteamNetTransport: connected [#488432965 P2P SDR steamid:76561197995508393@169.254.35.214:62257]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.105.170:1502 (fake port 4649)...
SteamNetTransport: incoming [#1418378592 P2P steamid:76561198022091594@169.254.251.68:21949 fakeport #1] - accepting
SteamNetTransport: connected [#1418378592 P2P SDR steamid:76561198022091594@169.254.251.68:21949 fakeport #1]
SteamNetTransport: dropped [#1982903022 P2P steamid:76561197970536135@169.254.105.170:4649] end reason 5003: Timed out attempting to connect
SteamNetTransport: attempt 1 ended in state 5
SteamNetTransport: attempt 2 ended in state 1
SteamNetTransport: connect timed out after 20s (RP412CONNECTWAIT)
Could not open connection to 169.254.105.170:1502.
SteamNetTransport: connecting to 169.254.240.193:1502 (fake port 7169)...
SteamNetTransport: connected [#3943096642 P2P SDR steamid:76561197976435895@169.254.240.193:7169]
SteamNetTransport: connect succeeded (attempt 1)
Listen requested on port 1502 but gameListenerSocket already existed!
STATUS: socket accepted from 169.254.122.50:1502!
Connected to GameMachineHost at 169.254.122.50:1502
Connected to GameMachineHost at 169.254.88.186:1502
Connected to GameMachineHost at 169.254.35.214:1502
Connected to GameMachineHost at 169.254.240.193:1502
SteamNetTransport: connecting to 169.254.88.186:1502 (fake port 10609)...
SteamNetTransport: connected [#1251939970 P2P SDR steamid:76561198659597127@169.254.88.186:10609]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.35.214:1502 (fake port 62257)...
SteamNetTransport: connected [#389222490 P2P SDR steamid:76561197995508393@169.254.35.214:62257]
SteamNetTransport: connect succeeded (attempt 1)
SteamNetTransport: connecting to 169.254.105.170:1502 (fake port 4649)...
SteamNetTransport: incoming [#2399032524 P2P steamid:76561198022091594@169.254.251.68:21949 fakeport #1] - accepting
SteamNetTransport: connected [#2399032524 P2P SDR steamid:76561198022091594@169.254.251.68:21949 fakeport #1]
SteamNetTransport: dropped [#3050802723 P2P steamid:76561197970536135@169.254.105.170:4649] end reason 5003: Timed out attempting to connect
SteamNetTransport: attempt 1 ended in state 5
SteamNetTransport: connected [#4019172527 P2P SDR steamid:76561197970536135@169.254.105.170:4649]
SteamNetTransport: connect succeeded (attempt 2)
SteamNetTransport: connecting to 169.254.240.193:1502 (fake port 7169)...
SteamNetTransport: connected [#2022716960 P2P SDR steamid:76561197976435895@169.254.240.193:7169]
SteamNetTransport: connect succeeded (attempt 1)
Listen requested on port 1502 but gameListenerSocket already existed!
STATUS: socket accepted from 169.254.122.50:1502!
Connected to GameMachineHost at 169.254.122.50:1502
Connected to GameMachineHost at 169.254.88.186:1502
Connected to GameMachineHost at 169.254.35.214:1502
Connected to GameMachineHost at 169.254.105.170:1502
Connected to GameMachineHost at 169.254.240.193:1502
All connections completed!
NetClock: host 2 first sample, offset -51745 ms
NetClock: host 3 first sample, offset -16521 ms
NetClock: host 4 first sample, offset 20923 ms
Physics: fixed step, 50 Hz
Binary file not shown.
Binary file not shown.
Binary file not shown.