Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e37ded1
Trace where the race draws the car, the road and the next car
lowlevelmetal Oct 9, 2026
0096f5b
Start a network race once every player has loaded it
lowlevelmetal Oct 9, 2026
1523740
Show the other players' and the shared police's damage
lowlevelmetal Oct 9, 2026
5b27203
Draw the world between the fixed simulation steps
lowlevelmetal Oct 9, 2026
23ccba0
Test the race start handshake
lowlevelmetal Oct 9, 2026
2b29781
Test drawing between steps and that the simulation stays the same
lowlevelmetal Oct 9, 2026
09b1200
Test the damage replication and bound a flooded record
lowlevelmetal Oct 9, 2026
c212116
Describe drawing between the simulation steps
lowlevelmetal Oct 9, 2026
0a80eba
Document the race start handshake and mark lobby review O1 fixed
lowlevelmetal Oct 9, 2026
d9a937a
Start a network race's report even without race rules
lowlevelmetal Oct 9, 2026
34f6492
Keep the race start's counting and the screenshot hook plainly typed
lowlevelmetal Oct 9, 2026
2dc975c
Draw the effects' particles between their updates
lowlevelmetal Oct 9, 2026
6271b2a
Give a player who joins between races the host's race number
lowlevelmetal Oct 9, 2026
f36d16e
Test the race start's report order and an earlier race's report in Ne…
lowlevelmetal Oct 9, 2026
3723aa6
Record the 0.2.0 race start measured against the handshake
lowlevelmetal Oct 9, 2026
1f3af08
Record the network cars' judder through a lossy relay
lowlevelmetal Oct 9, 2026
eea3291
Send the headlights in the network car state
lowlevelmetal Oct 9, 2026
7b954a1
Merge integration (render interpolation) into the race start branch
lowlevelmetal Oct 9, 2026
eb9a011
Add development hooks for testing network damage
lowlevelmetal Oct 9, 2026
ea0317f
Merge integration (drawing between the simulation steps)
lowlevelmetal Oct 9, 2026
fbf4d9f
Merge integration (the race start handshake)
lowlevelmetal Oct 9, 2026
cb1bacb
Add hooks for same-moment pictures of damaged network cars
lowlevelmetal Oct 9, 2026
ab88aa5
Record how network cars show their owner's damage
lowlevelmetal Oct 9, 2026
06cb7d6
Finish a starved damage batch through the recorder's common tail
lowlevelmetal Oct 9, 2026
7e64112
Call this version 0.3.0
lowlevelmetal Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25...4.0)

project(OpenMM2
VERSION 0.2.0
VERSION 0.3.0
DESCRIPTION "Open source reimplementation of Midtown Madness 2"
HOMEPAGE_URL "https://github.com/lowlevelmetal/OpenMM2"
LANGUAGES C CXX)
Expand Down
5 changes: 3 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ which is parsed as data.
## Timing

The simulation runs on a fixed time step; rendering interpolates between the
last two simulation states so any refresh rate works. Multiplayer exchanges
simulation state at a fixed rate independent of the frame rate.
last two simulation states so any refresh rate works (rendering.md, "Drawing
between simulation steps"). Multiplayer exchanges simulation state at a fixed
rate independent of the frame rate.

Each frame's time step is the real time since the previous frame, held to
0.0001–0.1 s as MM2's `datTimeManager::Update` does, so a long hitch slows
Expand Down
6 changes: 6 additions & 0 deletions docs/effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ unless the other party is a vehicle. Then:
engine), above 75 mph two random wheel/hub pairs and a fender, above
50 mph one pair, thrown at 1.3 times the car's speed.

In a network race the other players' cars and a client's shared police show
their owner's damage: the smoke from the replicated level, and the texel
damage, broken parts, sparks and shards replayed from the owner's events
(multiplayer.md, "Damage"); they lay no tyre tracks and throw no wheel
particles.

Ejected parts take their physics from `tune/banger/<car>_<part>.dgBangerData`
(`vehBreakableMgr::Create`). Retail data has these for every car's wheels and
for some cars' BREAK parts, sirens and fenders, but none for hubs. MM2 still
Expand Down
16 changes: 11 additions & 5 deletions docs/gamemodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,10 @@ penalties (below).

Circuits and checkpoint races start the countdown only once the pre-race
camera has finished (`UpdateGame` state 0 waits for mmPlayer +0xE5A; Blitz
does not), and the multiplayer races only after the host's start message
(OpenMM2: 2.5 s before the shared start time). Each countdown line plays
does not), and the multiplayer races only after the host's start message,
which comes once every player has reported its race loaded (OpenMM2: a
shared start time, the countdown's lines following the time left to it; see
docs/multiplayer.md, "Race start"). Each countdown line plays
"Startracelow", "Go!" plays "Startracehigh".

### Checkpoints
Expand Down Expand Up @@ -366,14 +368,18 @@ by the game. Falling into the water fails the event 0.5 s later.
HUD, car):

* **Start**: no countdown; "Go!" (113) for 2 s at the top with
"Startracehigh". Players start at random intersections drawn from a
stream seeded with their id (`mmGame::RespawnXYZ`).
"Startracehigh". MM2's host starts at once and each joiner when the host's
game state reaches it; OpenMM2's machines wait for everyone and go together
at the shared start (docs/multiplayer.md, "Race start"). Players start at
random intersections drawn from a stream seeded with their id
(`mmGame::RespawnXYZ`).
* **Machines**: each machine runs the rules for its own car and tells the
others (`CopsAndRobbers::updateNetwork` / `receive`): a client that
reaches free gold asks the host (0x25e) and the gold waits; the host
grants it (0x25a); the carrier's machine reports a drop (0x259) and a
delivery (600); the host draws the next set and sends its places (0x261).
OpenMM2 seeds the first set with the shared start time.
OpenMM2 seeds the first set with the time the host ordered the race,
which every machine knows when it loads.

* **Places**: `race/<city>/multicopwaypoints.csv` is a pool of places (at
least 3; the last row is never picked). Each set, at the start and after
Expand Down
379 changes: 339 additions & 40 deletions docs/multiplayer.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/parity/mm2/game-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ movers, `aiMap::Update`), `Reset` (a restart).
| --- | --- | --- | --- |
| `mmGameMulti::mmGameMulti`, `~mmGameMulti`, `` `scalar_deleting_destructor' `` | not needed | — | |
| `mmGameMulti::Init` | ported (new) | `RaceScreen::loadAi`, `loadRaceSetup` | traffic, cop and opponent densities 0 and no rail cars; the race modes clear mmGame's AI flag (no AI map at all) |
| `mmGameMulti::InitGameStrings` | ported (new) | `RaceScreen::updateNetPlayers` | "has left the game" (38); the "Waiting for N players" lines belong to the ready gate (replaced) |
| `mmGameMulti::InitGameStrings` | ported (new) | `RaceScreen::updateNetPlayers`, `NetRaceStart::waitingText` | "has left the game" (38); "Waiting for 1 player" ... "Waiting for 7 players" (31-37) while others load |
| `mmGameMulti::InitOtherPlayers` | replaced | `RaceScreen::updateRemoteCars` | |
| `mmGameMulti::InitRoster` | ported (new) | `Popup::Roster` | |
| `mmGameMulti::StartXYZ` | ported | `multiplayerGridOffset`, `RaceScreen::startSlot` | slot = the place in the host's player list (new; was the player id); slots 8-15 have no place (OpenMM2 allows 16) |
Expand All @@ -339,7 +339,7 @@ movers, `aiMap::Update`), `Reset` (a restart).
| `mmGameMulti::BootPlayerCB` | ported (new) | `Popup::Roster` -> `NetGame::kick` | |
| `mmGameMulti::RegisterMapNetObjects`, `ActivateMapNetObject`, `DeactivateMapNetObject` | ported (new) | `RaceScreen` blips (HUD audit), places 0 / 10 | the other players on the map and their cards, by start slot |
| `mmGameMulti::GetInactiveNetObjectIndex`, `GetNetObject`, `GetNetObjectIndex`, `ClearNetObjects` | replaced | `m_remotes` | |
| `mmGameMulti::PlayerFinishedLoading`, `PlayerClearLoaded`, `SendRaceReady`, `SendGameSet`, `SendTimeMsg` | replaced | the shared start time, ClockSync | no ready gate (deviation, session record) |
| `mmGameMulti::PlayerFinishedLoading`, `PlayerClearLoaded`, `SendRaceReady`, `SendGameSet`, `SendTimeMsg` | ported (new) | `NetGame::reportLoaded` (`RaceLoaded`), `net::Session` (the loaded flags, cleared at each race and return), `NetRaceStart`, ClockSync | the ready gate (docs/multiplayer.md, "Race start"); the start is a shared session time and the host stops waiting after 60 s (deviations); no car CRC; `SendTimeMsg` replaced by ClockSync |
| `mmGameMulti::SendPosition`, `SendMsg` | replaced | snapshots, `sendEvent` | |
| `mmGameMulti::GetCarCRC`, `SendCheater` | open | — | the tuning CRC check (low) |
| `mmGameMulti::BroadCastCarTuning`, `SendCarTuning` | not needed | — | `SendCarTuning` is empty |
Expand All @@ -361,8 +361,8 @@ movers, `aiMap::Update`), `Reset` (a restart).
| `mmMultiRoam::InitHUD`, `mmMultiRace::InitHUD`, `mmMultiCircuit::InitHUD`, `mmMultiBlitz::InitHUD` | ported | `Hud` | the place readout now counts the network players (new); Blitz's place readout and no checkpoint cards (HUD audit) |
| `mmMultiRoam::Reset` | ported | `cruiseStart` | at the start (from mmGameMulti::InitOtherPlayers) its RespawnXYZ is the first of the two seeded picks; no in-race restart in a network game (round 3: [cruise-spawn](../round3/cruise-spawn.md)) |
| `mmMultiRace::Reset`, `mmMultiCircuit::Reset`, `mmMultiBlitz::Reset` | replaced | the lobby | no in-race restart |
| `mmMultiRoam::UpdateGame`, `SwitchState` | ported | `Session::start` / `updateRace` | "Go!" and the car released at once (new: no shared start in cruise) |
| `mmMultiRace::UpdateGame`, `SwitchState`, `mmMultiCircuit::UpdateGame`, `SwitchState`, `mmMultiBlitz::UpdateGame`, `SwitchState` | ported | `Session::updateRace`, `updateNetRace` | countdown, wreck penalty (no warning, finish or clock check during it, new), finish line, the wait for all finishers and the timeout (new); the ready gate and the end taper are replaced (session record) |
| `mmMultiRoam::UpdateGame`, `SwitchState` | ported | `Session::start` / `updateRace`, `NetRaceStart` | "Go!" and the car released at once (new: no shared start in cruise); "<name>" / "has joined" as each other machine reports (`GameMessageCB` 0x1fa) |
| `mmMultiRace::UpdateGame`, `SwitchState`, `mmMultiCircuit::UpdateGame`, `SwitchState`, `mmMultiBlitz::UpdateGame`, `SwitchState` | ported | `Session::updateRace`, `updateNetRace` | countdown, wreck penalty (no warning, finish or clock check during it, new), finish line, the wait for all finishers and the timeout (new); the ready gate (state 0: 5 s, RaceReady, "Waiting for N players", the start; `NetRaceStart`, on a shared start time); the end taper is replaced (session record) |
| `mmMultiRace::SetTimeoutOn`, `SetTimeoutOff` | ported (new) | `Session::addNetResult`, `updateNetRace` | 60 s from the first finish ("Race over", DNF) |
| `mmMultiBlitz::PlayTimerWarning` | ported | `Session::timerWarning` | |
| `mmMultiRoam::GameMessage`, `mmMultiRace::GameMessage`, `mmMultiCircuit::GameMessage`, `mmMultiBlitz::GameMessage` | ported (new) | `updateNetRace`, `Session::remoteFinished` | the finish lines (host 152 / 110 / 99, client 150 / 107 / 96) with Messagenote; the time-up / restart / host-quit messages are replaced |
Expand Down
3 changes: 3 additions & 0 deletions docs/parity/openmm2-only.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ counted in the summary above.
| `phys::Instance::kinematicMotion`, `Body::kinematicMoves` | | openmm2 | A kinematic instance (network car) that reports its motion strikes as a car moving at it (MM2 has no kinematic instances); off by default, on for the network cars of a shared-traffic cruise and the received traffic and police. |
| The host settings' SHARED TRAFFIC panel (`HostSettingsPage`) | `HostRaceMenu` (no traffic or cop settings) | deviation | On / off and the single-player cruise's traffic and cop density sliders (defaults 0.5 and 1, `RaceMenuBase::SetStateRace`), drawn by OpenMM2 in the upper right panel cruise leaves empty. |
| A client's pedestrians and the received cars | `aiPedestrian::UpcomingAccident` / `Accident` (`aiObstacle::InAccident` in the road's section lists) | inferred | The host's section lists do not travel: a received car off its rail counts for its whole road or intersection (`MapView::mapComponent` of its place). |
| A client's police cars' damage (`game::DamageReplica`, `VehicleEffects`, `AmbientEntity::damage`) | `vehCarDamage::Update`, `ApplyImpact`, `fxTexelDamage::ApplyDamage`, `vehBreakableMgr::Eject`, `vehCarModel::EjectOneshot` on the host's cars | openmm2 | Added 2026-10-09 (protocol 4): the host's police damage level (10 bits) drives the received car's smoke as `vehCarDamage::Update` does, and its dents, broken parts, sparks, shards and impact sounds are replayed from the host's records (docs/multiplayer.md, "Damage"); the wreck (out of action) as before, with its black smoke and the explosion sound. Before, a client saw the wreck flag and the explosion only. |
| A client's knocked traffic cars' wheels (`AmbientEntity::wheels`, `game::trafficWheelMatrices`) | `aiVehicleInstance::Draw` with an `aiVehicleActive` (`vehWheelCheap`'s drawing matrices) | openmm2 | Added 2026-10-09 (protocol 4): a car with a body on the host sends its four wheels' drawing offsets; the client draws it on them and lays its shadow as a physical car's. Before, it was drawn on its rail wheels. |

## Files

Expand Down Expand Up @@ -160,6 +162,7 @@ counted in the summary above.
| `src/core/StringUtil.h` | reclassified M | |
| `src/net/AmbientState.cpp` | O | The shared cruise traffic's message (added 2026-10-09). |
| `src/net/AmbientState.h` | O | |
| `src/net/VehicleDamage.h` | O | A car's damage on the wire (added 2026-10-09); the rules it replays are MM2's (docs/multiplayer.md, "Damage"). |
| `src/net/BitStream.cpp` | confirmed O | Wire encoding. |
| `src/net/BitStream.h` | confirmed O | |
| `src/net/ClockSync.cpp` | confirmed O | Host clock estimate. |
Expand Down
2 changes: 1 addition & 1 deletion docs/parity/round3/cruise-spawn.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ also changes every later single-player start of the run.
| `Traffic::populate`, `Pedestrians::populate`, `Random::state` | `AdjustAmbients` / `AdjustPedestrians` from room 0 | fixed | the populations themselves were audited by the ai areas (draw for draw); with no ambient cars or pedestrians MM2 skips them and OpenMM2's draw nothing |
| `city::addShortcuts`: centre | `aiIntersection::CreateRoadMap` | fixed | a shortcut road's end intersections move to their room's bound-sphere centre (`sdlRoomBoundSphere`; up to 0.21 m in San Francisco) before their road lists are sorted; RespawnXYZ starts there (ai-vehicles' open row, commit ef670f6) |
| C&R picker (`RaceScreen::setupCopsAndRobbers` `randomIntersection`) | `GetRandomPoints`' helper: `RespawnXYZ(false, false, false)` less 2 m | fixed | the room by `FindRoomId`, only 0x24 rejected, no road rules, MSVC draws |
| C&R picker: stream and draws | the host's global stream, (counter + 1) draws | deviation | every OpenMM2 machine draws the first set itself from the shared start time, one draw a pick (the counter is each machine's own, so using it could split the machines) |
| C&R picker: stream and draws | the host's global stream, (counter + 1) draws | deviation | every OpenMM2 machine draws the first set itself from the race's order time, one draw a pick (the counter is each machine's own, so using it could split the machines) |
| Cruise restart | `mmSingleRoam::Reset` -> `mmGameSingle::Reset` -> `mmGame::Reset` | verified | no new pick: `asNode::Reset` puts the car back at its reset place (`Session::restart`, RaceScreen's Restart); network games have no restart |
| `CarSim::setResetPos` | `vehCarSim::SetResetPos` | verified | the CG added unrotated (+0x210) |
| reset angle | +0x250 written from RespawnXYZ's angle | verified | 0: the body faces -Z (`SimVehicle::setResetPos(pos, 0)`) |
Expand Down
2 changes: 1 addition & 1 deletion docs/parity/round3/random-streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ this reason).
| `vehWheel::GetBumpDisplacement` (via `vehDrivetrain::Update`) | the wheels' own stream (`phys/vehicle/Wheel`) | deviation | Physics; per sample. |
| `vehCarModel::EjectOneshot`, `vehBreakableMgr::Eject` (also via `aiVehicleActive::Impact`) | RaceScreen and BangerSet eject streams | deviation | On impacts. |
| `mmPlayer::UpdateFF`, `FFImpactCallback` | `ForceFeedback` stream | deviation | Already recorded as inferred own. |
| `mmMultiCR::GetRandomIndex` (global), the Cops and Robbers point picker (secondary, clock-seeded) | `CopsAndRobbers` (its own LCG of the same form, seeded from the race start time) | deviation | Network; MM2's picker is seeded from the clock, so nothing to reproduce. The LCG constants differ from MSVC's (session record). |
| `mmMultiCR::GetRandomIndex` (global), the Cops and Robbers point picker (secondary, clock-seeded) | `CopsAndRobbers` (its own LCG of the same form, seeded from the race's order time) | deviation | Network; MM2's picker is seeded from the clock, so nothing to reproduce. The LCG constants differ from MSVC's (session record). |
| `ptxGlass::AddShards`, `ptxGlass::Update` | — | not needed | No retail banger is glass (BillFlags 0x100). |
| `pedActive::FirstImpactCallback` | — | not needed | No ragdolls exist in MM2 (pedRagdollMgr is never built). |

Expand Down
4 changes: 2 additions & 2 deletions docs/parity/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ Behaviour is documented in `docs/gamemodes.md`. Tests:
| `setRaceConfig`, `setPassword`, `kick`, `returnToLobby` | `NetArena` host | openmm2 | |
| `setMaxPlayers`, `maxPlayers` | DirectPlay session (8 players) | deviation | OpenMM2 allows 2-16 |
| `setGoldMass`, `goldMass` | `HostRaceMenu::GetGoldMassVal` (cnr_goldMass) | openmm2 | the setting's transport; the default is the host menu's (frontend-ui) |
| `startRace`, `raceStartTime`, `secondsToStart`, `raceStarted` | `mmGameMulti` start message | deviation | a shared start time instead of the host's start message; the session starts the 2.5 s countdown 2.5 s before it |
| `startRace`, `reportLoaded`, `playerLoaded`, `raceStartTime`, `secondsToStart`, `raceStarted` | `mmGameMulti::SendRaceReady`, `mmMultiRace` / `mmMultiCircuit` / `mmMultiBlitz::UpdateGame` state 0, `GameMessage` 0x1f6 / 0x213 / 0x20f, `SystemMessage` 0x2d | fixed | the ready gate (docs/multiplayer.md, "Race start"); deviations: the start is a shared session time, which the countdown follows, instead of the host's start message, and the host stops waiting after 60 s |
| `startPortMapping`, `stopPortMapping`, `portMappingStatus` | — | openmm2 | UPnP / NAT-PMP |
| `submitLocalState`, `remoteCars` | `mmNetObject` | openmm2 | snapshots with interpolation |
| `sendEvent`, `sendCheckpoint`, `sendLap`, `sendFinish`, `sendGold`, `sendCollision`, `sendDamage`, `addSystemLine` | `mmGameMulti::SendMsg`, `mmMultiCR::Send*` | openmm2 | own messages |
Expand Down Expand Up @@ -281,7 +281,7 @@ Behaviour is documented in `docs/gamemodes.md`. Tests:
| `updateRemoteCars`, `drawRemoteCars` | `mmNetObject::Init`, `Update` | fixed | network cars as kinematic bodies at their snapshots (deviation: MM2 simulates them with the remote inputs), the polygonal bound, a vpcop on the Mustang's tune, trailers except in multiplayer cruise and Cops and Robbers, type-3 movers |
| `multiplayerGridOffset`, spawn | `mmGameMulti::StartXYZ`, `mmGame::RespawnXYZ` (seed), `mmGame::FindGroundPos` | fixed | the eight-slot grids (slot = player id, inferred), the cruise start seeded by the player id, spawns dropped with the wheels' probe |
| `CopsAndRobbers::updateNetwork`, `receive` | `mmMultiCR::UpdateGame`, `UpdateGold`, `UpdateBank`, `UpdateHideout`, `ImpactCallback`, `GameMessage` (0x259, 0x25a, 0x25e, 600, 0x261) | fixed | new: each machine runs its own car, the host grants pickups and draws sets |
| `setupCopsAndRobbers`, `updateCopsAndRobbers`, `fondleMass`, `crTeam` | `mmMultiCR::Init`, `InitMyPlayer`, `FondleCarMass`, `mmPlayer::UpdateRegen`, `mmGame::UpdateSteeringBrakes` | fixed | new: the mode is played (places, teams, messages, mass, throttle cap in every network game, regeneration, repair at delivery, HUD lines, limits, end after 3 s); the shared first set is seeded by the start time (OpenMM2) |
| `setupCopsAndRobbers`, `updateCopsAndRobbers`, `fondleMass`, `crTeam` | `mmMultiCR::Init`, `InitMyPlayer`, `FondleCarMass`, `mmPlayer::UpdateRegen`, `mmGame::UpdateSteeringBrakes` | fixed | new: the mode is played (places, teams, messages, mass, throttle cap in every network game, regeneration, repair at delivery, HUD lines, limits, end after 3 s); the shared first set is seeded by the race's order time (OpenMM2) |
| `Hud::drawCrObjects`, `drawCrReadouts`, arrow interest | `mmPowerupInstance::Draw`, `mmBillInstance::Draw`, `mmArrow::SetInterest`, `mmCRHUD::Init` | fixed | new: the spinning gold, the bases' billboards, the team totals ("COPS" / "ROBBERS" or "BLUE" / "RED"); the roster of names and the gold icon are not drawn, and the totals' corner is inferred |
| Cops and Robbers speech | `mmSpeechContainer::InitCNR` | verified | loaded; nothing in build 3393 calls `mmCNRSpeech::Play` |

Expand Down
12 changes: 12 additions & 0 deletions docs/physics.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ scheme. (Several terms below scale with the sample length, e.g. the
drivetrain's AngInertia * dt, so the original drove slightly differently at
other frame rates.)

The fixed step stays, for accuracy (the original at 60 fps) and determinism
(network play, tests, the opponent sweep). A frame then runs no sample or
several, and what is shown between two samples is OpenMM2's presentation:
`World::step` calls a step observer before anything moves, the race screen
keeps each body's state from before the sample (`game::StepHistory`), and
the drawing blends it towards the current state by `interpolationAlpha()`
(the unstepped remainder / the step; rendering.md, "Drawing between
simulation steps"). The observer only reads; `CarSim::resets` (how many
times `vehCar::Reset` put the car somewhere) tells the drawing not to blend
across a reset, and nothing in the simulation reads it. The AI's fixed 1/30 s
steps (`ai::World`) are drawn the same way.

## Rigid body (phInertialCS) — MM2

* Every physics entity's update first adds its weight, Mass * -19.6 on y
Expand Down
Loading
Loading