Commit Graph
7 Commits
Author SHA1 Message Date
CydandClaude Opus 5 82e733c1a6 Replicants reckon from when an update was sent
Simulation::ReadUpdateRecord threw away the sender's timestamp and
stamped lastUpdate with its own arrival time. The line carried the
original authors' own note: "HACK - should be based upon
message->timeStamp".

The dead reckoner extrapolates a replicant over
(lastPerformance - lastUpdate), so starting that clock at ARRIVAL rather
than at SEND leaves every remote vehicle exactly one network latency
behind where it should be. On the 1 ms LAN inside an arcade that is
nothing. Over Steam Datagram Relay it is 50-150 ms of positional lag on
every other player - a constant bias, not jitter, and the information
needed to remove it was already in the packet.

The timestamp cannot be used as it stands: both machines run
QueryPerformanceCounter since their own boot, so the two clocks share no
epoch. The offset is estimated per peer instead. Each record gives

    sample = ourNow - theirStamp = trueOffset + oneWayLatency

and latency is never negative, so the smallest sample seen is the
closest to the truth. A rolling minimum over 128 samples follows crystal
drift and re-adapts when a route gets slower, rather than being pinned
forever by one lucky packet; a shorter path is believed immediately.

Applied with two clamps: never ahead of our own clock, and never further
back than 500 ms. Past that the packet is stale or the estimate is
wrong, and throwing a vehicle half a second forward does more damage
than the lag being corrected.

Entity::UpdateMessageHandler is the only point on the receive path that
knows whose update this is - records carry a timestamp but not an owner -
so it publishes the sender around the loop, and only for entities
somebody else owns. Offsets are forgotten in CreateMission: the hosts in
the next race are not the hosts in the last one and a HostID gets reused.

RP412NETCLOCK=0 restores the arrival-time behaviour, documented in
environ.ini, so a test machine can compare the two without a rebuild.
The estimate is logged per host when it first settles and whenever it
moves more than 50 ms, which is what a three-machine session should be
read against.

WHAT IS AND IS NOT VERIFIED. A full single-player race runs unchanged -
the path is never entered without replicants, which is the regression
risk that reaches everybody. The behaviour this exists for needs real
latency between real machines and is therefore untested: a two-instance
loopback race would only have exercised the zero-latency case, where the
correction is a no-op by construction. Expect remote vehicles to sit
further forward than before, and watch for overshoot when somebody
changes direction sharply - that is the tradeoff this makes, and the
clamp above is what bounds it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:50:28 -05:00
CydandClaude Opus 5 68f5780efa The cockpit clock counts the console's clock
A race ends when the console says so, but the countdown on the map
display was computed from the engine clock and its own idea of when the
race started - QueryPerformanceCounter from Application::gameStarted,
against the console's GetTickCount from gRunStartTick. Two clocks, two
epochs, two threads. They agreed to within a frame in the ordinary case,
which is why nobody noticed.

They do not agree at all when RP412MISSIONSECONDS is set: the override
shortens the CONSOLE's length and leaves the egg's alone, so a 25-second
test race displayed a clock counting down from 5:00 and was stopped with
4:35 still showing.

gMissionClockHook (APPMGR.h, alongside the gPerFrameHook it mirrors) lets
the console answer for the countdown when it is marshalling. NULL, or a
console that has no answer yet, falls back to exactly the old
computation - which is what the arcade -net pods, lobby members and
mission review all take, none of them running a console locally. A
member's clock is anchored by the console's RunMission arriving over the
wire anyway, so it starts within one latency of correct and only drifts
at the rate the two crystals differ.

Two things come out of it beyond the clock itself. The camera directors
switch behaviour at "30 seconds left" (DIRECTOR.cpp, RPDIRECT.cpp) and
were reading the same free-running number, so the dramatic end-of-race
camera and the actual buzzer were on different clocks too; they now
share one. And the countdown holds at 00:00 instead of going negative -
the console polls at 250 ms, so zero always arrives slightly before the
stop is dispatched.

The hook is guarded on gWatchedApp == application. Nothing ever
uninstalls it, so a player who hosts a race and then joins somebody
else's lobby still has it wired up, and in that race the console is a
bystander holding the previous mission's gLengthMs and gRunStartTick.

Verified by running a 25-second race with the menu still set to 5:00 and
photographing the map display: 00:17, 00:01, then 00:00 held while
"time expired - stopping mission" went to the log. Captures use
PrintWindow rather than CopyFromScreen - the first attempt grabbed the
desktop sitting in front of the Map window, which is somebody's screen
contents written to disk, and those files were deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:39:19 -05:00
CydandClaude Fable 5 3f691cacb3 Departed-pod resilience: collision guard + console loss ends the race
Round six raced all three machines (staging fix confirmed) and then
exposed what happens when a pod leaves mid-mission - which arcade pods
never did.

The B crash dump named it exactly: VTV::TakeDamageMessageHandler
resolved message->inflictingEntity to NULL (the entity belonged to the
departed owner) and dereferenced it - Verify is compiled out in
release. Collision damage from an entity that no longer exists is now
ignored.

And the race B and C were left in was a zombie: the owner (console)
had aborted, so the mission clock would count up forever and the
death/respawn flow hung with nobody to arbitrate. Lobby-member races
now set gConsoleLossEndsMission: losing the console mid-mission posts
StopMission locally, the pod tears down, and lands back in the lobby
room. Arcade -net pods keep the re-listen-and-wait behavior.

Loopback hosted race still green.

For the drivers: the ampersand key is the arcade mission-abort - that
was every crash-on-keypress so far; and a sleeping Bluetooth pad wakes
on the Xbox button and hot-connects within 3 seconds (PadRIO
re-probes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 00:02:54 -05:00
CydandClaude Fable 5 2d5057c528 Hosted races stage properly: the owner no longer launches itself
Round five reached the race - full mesh on all three machines, eggs,
ACKs, mission running - but the owner raced ALONE. Cause: the engine
self-runs a pod at WaitingForLaunch when its console host is not
online (the arcade no-console fallback), and the owner''s in-process
console never connects to its own pod. On fast-loading owners the
self-run beat the console''s staging gate, so RunMission was never
sent and the members sat staged at black screens until someone hit
the & emergency-abort key.

gConsoleMarshalsLaunch (APPMGR) now tells the engine an in-process
console owns the launch: the network-race install sets it and the
owner holds at WaitingForLaunch with everyone else; plain single
player leaves it False and auto-runs as always. Verified on loopback:
all pods staged - RUN is back in the hosted-race log and both the
hosted race and the single-player cycle pass.

Also: unhandled-exception minidumps (rpl4crash.dmp beside the exe,
dbghelp loaded lazily) so test-machine crashes hand back stacks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 23:40:04 -05:00
CydandClaude Fable 5 9f79508257 LocalConsole: the in-process marshal that ends missions
Domain correction from playtest: hand-fed eggs are a developer shortcut
- a mission only ends on a console command, so the clock hits 00:00 and
counts up forever. Even single-player games need a console marshal.

RPL4CONSOLE is that console. Like the real one it lives on its own
thread: it owns the mission clock and raises the stop request at the
selected length; the app-manager per-frame hook (new gPerFrameHook seam
in APPMGR, called while the application global is live - the loop
condition NULLs it on exit, which ate the first attempt) executes the
engine-safe part, dispatching the same StopMissionMessage TeslaConsole
sent. Final scores flow in through a new RP-layer sink
(gConsoleScoreSink in RPCNSL): RPPlayer feeds it the same score it
sends a real console at mission end.

It also inherits the launcher role: the application tears down after a
stop (arcade pods were relaunched per mission by TeslaLauncher), so
WinMain respawns the process when the console ended the mission,
landing back on the race-setup screen. L4NetworkManager grows
FeedLocalEgg (the single-user egg-inject path, callable mid-session)
for the future in-process loop.

Verified end to end: menu -> 3:00 race -> stop dispatched exactly on
time -> final score collected (host 1 = 4113) -> process respawned with
the front end up. -egg runs stay unmarshaled (the dev shortcut).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:10:02 -05:00
CydandClaude Fable 5 12b31187f9 Move the build to VS2022 (v143) with runtime parity against VC9
Hand-converted the four .vcproj projects to .vcxproj (Win32, v143,
Windows 11 SDK + DXSDK June 2010 for d3dx9/dxerr only). WinTesla.sln now
builds the v143 projects; the legacy solution is kept as WinTesla_vc9.sln.

Kept: /Zp1 in Munga_L4+RP_L4, Unicode, x86, /DYNAMICBASE:NO,
/FORCE:MULTIPLE (header-defined globals still duplicated across TUs).
Changed: CRT unified to /MD(d); import libs linked by the exes instead of
merged into Munga_L4.lib; WINDOWS_IGNORE_PACKING_MISMATCH and
_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS defined;
legacy_stdio_definitions.lib for the June-2010 dxerr.lib.

Source fixes, all behavior-preserving: Time gains standard (non-volatile)
copy-ctor/assignment overloads (rvalues cannot bind to volatile& in
standard C++); operator==(SOCKADDR_IN&,...) made inline; L4DINPUT's
Enum*Callback pair renamed DIEnum* (collided with L4CTRL's under LTCG);
std::ios.in -> std::ios::in in CAMMGR.cpp.

Verified: VC9 baseline rebuilt from this tree first, then the v143 build
compared against it in a sandboxed game working copy - identical logs and
behavior through RIO init (against vRIO) and mission load, including the
same pre-existing AV in d3d_OBJECT::LoadTexture (L4D3D.cpp:262) that both
toolchains hit; documented in BUILD.md 4 as the next debugging target.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 12:22:31 -05:00
CydandClaude Opus 4.8 4abbf8879f Initial import of Red Planet v4.10 Win32 source
Imports the current Win32 source for the pod-racing game 'Red Planet',
built on the MUNGA engine and its L4 (Win32/DirectX) platform layer:

- MUNGA / MUNGA_L4: cross-platform engine core and Win32 backend
- RP / RP_L4: Red Planet game logic and Win32 application
- DivLoader, Setup1: asset loader and installer project
- lib, MUNGA_L4/openal, MUNGA_L4/sos: third-party audio dependencies

Removed stale Subversion metadata and added .gitignore/.gitattributes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:59:51 -05:00