c2e2df1dceadfef77f6a33ff5a805712a71dc426
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2a679ab571 |
A race can be replayed exactly, so physics claims can be tested
Three pieces of harness, all env-gated and inert in normal play, that turn "do two frame rates play the same race?" from an argument into a number: - RP412PHYSTRACE=1 samples the player vehicle's position on the SIMULATION's own clock - the vehicle's lastPerformance, which advances in whole fixed steps - so two runs sample at identical step counts and their traces compare exactly. Frame-time sampling compares different instants and calls the difference physics; an earlier version of this trace did exactly that, and its noise was chased as if it were drift. At the green light it stops the vehicle dead, because the pod simulates on its pad while the mission loads and a load is never the same length twice: two runs reached the start 776 and 599 steps in, same position, different velocity. - RP412SPAWNZONE pins which drop zone is tried first. The pick is Random(), and Random() is seeded - but a seed only repeats a run if the same NUMBER of draws precedes the pick, and that count rides on load timing. Same seed, different pad, incomparable traces. Pinned, the zone is tried first and falls back to the random walk if taken, so it cannot wedge and changes nothing unless set. - The trace prints the global step counter, which is what caught the force-accumulator bug: the position columns can look plausible while the step column says the physics ran a different number of times. With these three and RANDOM= (which already existed), a race is repeatable to the bit, and the determinism matrix - rates by frame rates by repeats, run as parallel sandboxed instances - is a regression suite: any mismatch in any cell is a real bug. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6e829f815e |
Doors run on the mission clock instead of being replicated
A door's position was an integrated countdown owned by whichever machine the map-entity round-robin happened to deal it to. That left doors one one-way-latency behind on every other machine, re-acquired at each state change; drifting permanently on any frame hitch over a second, which the old code dropped outright rather than clamping; and frozen mid-cycle, collision volumes included, when their owning peer left, since ownership transfer is not implemented. Doors are clockwork with no inputs, and door/VTV physics is already local pointer access - VTV::ProcessCollision reads door->currentVelocity off the local object and the crush test is local VTV state - so a door does not need an owner at all. Door::SlideDoor is now a phase function of Application::GetMissionElapsed(), anchored so phase zero reproduces the original DefaultState entry: fully open, starting to close. Every host builds its own doorframe out of the map stream as a HermitInstance, the instance kind DynamicEntityCreation does not broadcast, so nothing is sent, nothing is received, and a peer leaving takes no doors with it. Verified against a copy of the old integrator at 25fps with the real 10s travel / 3s dead timings: identical 26s cycle, a constant one-frame offset, and no drift across a 3s stall that leaves the old code permanently 3 seconds out of phase. Also fixes a latent bug found on the way: UpdateManager iterates the dynamic master socket, which holds Independant and Hermit instances as well as masters, and handed all of them to EntityUpdateReplicants, which asserts MasterInstance. Doorframes no longer consume a slot in the map-entity ownership cursor, which shifts who owns every map entity dealt after them, so this cannot share a session with an older build. The lobby publishes a simulation revision and refuses to launch a mixed room. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |