Repository navigation
Parity audit round 3: frames, cruise spawn, random streams, order, conventions - #1
Merged
Merged
Conversation
aiTrafficLightInstance::Init places a traffic light at its pole's base plus R * CG (the model's dgBangerData CG) and Draw draws the body, whose mesh is centred on that point, at that frame; DrawGlow takes R * CG off again for the glow and WALK meshes, which are modelled from the base. OpenMM2 drew the body at the base, so every pole sat 3.9 m in the ground under glows at the right height and the lights looked misplaced. The body, visibility, room and draw distance now use the CG frame. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
aiRaceData::aiRaceData stores a [Police] line's heading as heading x -0.017444445, the same not-quite-pi/180 factor aiRouteRacer::Init uses for the racers' first .opp row, and aiPoliceOfficer::Reset copies it to the car's reset rotation. OpenMM2 converted it with pi / 180, turning a post at 245 degrees about 0.12 degrees too far. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RaceMenuBase::SetStateRace (and the modes' next-race setup) store the race table's Cops column, a count from 0 to 8, in the cop density as it is. aiMap::Init clamps it to 0..1 when it places the posts, but the modes' RegisterFinish compare the setting with the count, so once the slider has been moved a race with two or more cops is not recorded. OpenMM2 clamped the count when it applied the table, which made the moved-and-restored slider count as the race's setting. The slider steps from its own clamped value, as UISlider::Action steps mmSlider's (SetValue clamped the bound variable when UISlider::Update took it): Left from a count of 3 gives 1 - step, not 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the post turned by MM2's own degree factor, the point 70 m behind London's roam post at (1110, -731) lies a few centimetres off the road, so the tests found no post and failed. 75 m behind is on the road again; the suspect is still out of the cop's view (behind it, at the edge of DetectPerpetrator's 75 m). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aiVehicleInstance::Draw draws a traffic car that has a body (aiVehicleActive) with WHL0-3 at its vehWheelCheaps' drawing matrices: unturned, at the pivot moved along the car's up axis by the spring's travel (Limit below the pivot off the ground) and back by 0.2 of the sideways and 0.3 of the forward tyre deflection, carried by the body's matrix; WHL4/5 unturned at their pivots raised by WHL2/3's drawn height less the wheel radius. OpenMM2 kept turning the wheels at the rail's pivots, so a car hit into the air kept its wheels tucked in. aiVehicleInstance::DrawShadow lays such a car's shadow on the ground under it (lvlInstance::DrawPhysics), and a rail car's on the ground when it is upside down; OpenMM2 always left it at the body. TrafficBodies now keeps the drawing matrices and AiRenderer gets a ground probe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mmPlayer::Init names the player node after the car and loads it, so mmPlayer::FileIO reads tune/<car>.asnode: the speed bases, the keyboard and game pad filter rates (DiscreteSteering*), and the mouse, joystick and wheel sensitivities, curves and approaches that mmPlayer::Update blends by speed. Every retail car but vpcentury and vpdune ships one, and they differ a lot from the constructor's values OpenMM2 used for every car (vpbug: SpeedBaseHi 44.6 instead of 100, keyboard DeltaOut 2.573 -> 0.8 instead of 3.5 -> 2.5, DeltaIn 5 instead of 2.5 -> 1.5, joystick approach on). Fields a file lacks keep the constructor's values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The xref loop of lvlLevel::LoadInstances requests each xref's banger with RequestBanger(name, 0), a dgUnhitYBangerInstance, which keeps only the X row's x and z of the placed matrix (its CG offset is still turned by the whole matrix in dgUnhitBangerInstance::Init). OpenMM2 kept the full matrix. 68 of the 177 retail xrefs were exported Z up, so San Francisco's shop awnings, the Chinatown awnings and sign, Harrods' awning and the Golden Gate, Bay Bridge and tower lights were drawn and collided tipped 90 degrees. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dgBangerData::Load hands the nested block to asBirthRule::Load, which skips the block's name, its brace and the first field's name and then reads the rule by position. OpenMM2 only looked for a block named BirthRule, so sp_tree1_s_break06, whose block is called asBirthRule, lost its rule (InitialBlast 25, SpewRate 1.24) and threw no leaves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vehTrailerInstance::Draw draws a trailer's body alone below the high LOD; at H it adds TLIGHT while the tow car brakes harder than 0.1 (an ordinary lit draw of the object pass, which its black transparent fxltglowred material leaves invisible) and TWHL0-3 at the trailer's wheel matrices. It has none of vehCarModel's other parts and keeps lvlInstance's empty DrawGlow. OpenMM2 drew trailers as cars: the medium and low TWHL0/1 meshes, which are modelled away from their pivots, at the front wheels' matrices, a refl_dc reflection, texel damage, and TLIGHT as an added glow when braking and at night. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ai::PlayerCar::at (mm2tool aisim --drive and the AI tests' player) built its frame with rotationY(atan2(-x, -z) + pi). rotationY(a) already faces (-sin a, 0, -cos a), so the extra half turn put the car's -Z axis against its velocity, and the traffic saw the tool's player driving backwards. The game's own player car comes from the simulation and was not affected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pedAnimation::DrawSkeleton offsets each quad's corners in the pedestrian's own space (the pedestrian's matrix is the world matrix) by the first row of gfxRenderState's modelview: the pedestrian's X axis in view space, MM2's view matrix being the camera's inverse with z negated (sm_FullComposite). That is the camera's right axis while pedestrian and camera only turn about Y, and tilts under a pitched camera. OpenMM2 used the camera's right axis in world space. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restart Race, F4 in the menu and F4 in the game only set mmReplayManager's reset flag (+0x19); mmReplayManager::Update acts on it at the start of the next frame, before any node of the game updates: mmReplayManager::Reset, then the tree's Reset down to the mode's Reset, so the first rules, AI and physics after a restart see the cars and the session back at the start. F4 also ends a pause at once (asRoot::Reset). OpenMM2 reset the session at once and the cars only from the Restart event after that frame's physics, so the session's first update after a restart saw the car where it had been: restarting at a checkpoint showed it cleared in the new countdown. The race now restarts at the top of the next frame (applyRestart), and the frame of the request takes the paused path. The session's events are handled in one place (handleSessionEvents) for the rules, the restart and later the paused frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aused asRoot's pause only stops what checks it. mmGameManager::Update runs cityLevel::Update (lvlSky::Update turns the sky), cityLevel::PreDraw (asParticles::Update: the rain and snow) and camViewCS::Update (the camera) in every frame; mmGame::Update skips UpdateGame and aiMap::Update but still runs its fall and water checks, whose timer counts datTimeManager::Seconds; and mmHUD::Update, a node of the game, still counts the HUD message down. OpenMM2's paused frame froze all of these: the camera stopped where it was (a camera key pressed in the full-screen map changed nothing until the game ran on), the rain and the sky stood still behind the menu, a message outlived the pause and five seconds in the water could not run out under the menu. The paused frame now updates the camera, the weather and the city renderer, and Session::updatePaused runs the hazard checks and the message timer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MM2 lights instances with Direct3D 7's fixed function and never turns on NORMALIZENORMALS (gfxRenderState::Init zeroes the state block and no code writes that state). Direct3D takes normals through the inverse transpose of the world matrix and does not renormalise them, so a building or prop placed with a scaled .inst matrix is lit in proportion to 1 / its scale along each axis: a petrol station at 0.88 is brighter, a house stretched 1.34 high has darker roofs. OpenMM2 renormalised the world normal and lit all of them at unit strength. About 1,500 of London's 1,997 and 2,400 of San Francisco's 3,763 instances carry a scale. Rigid matrices (cars, gizmos) are unaffected. The Direct3D rule is inferred; it is outside midtown2.exe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mmGame::UpdatePaused (the C and V keys whatever the camera actions are bound to) sits in the game's UpdatePaused slot, which nothing calls in build 3393: asNode::UpdatePaused is only reached from its own recursion and AudManager's, and mmReplayManager::Update runs the tree with asNode::Update whether or not asRoot is paused. A paused game takes its keys through mmGame::Update's UpdateGameInput instead, which OpenMM2 already runs while paused; the extra raw C and V keys are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the menu up and the game not paused (a network game, a race lost, the chat line), mmGame::Update skips UpdateDebugInput and UpdateGameInput but still calls UpdateSteeringBrakes when inputDevice is above 0, so every controller but the mouse keeps driving the car from the inputs mmReplayManager::Update recorded that frame; with the mouse nothing sets the car's inputs and it keeps the last ones. OpenMM2 let go of every pedal and the wheel instead. The recorded inputs are now read whatever the menu, and the mouse controller keeps the car's last ones while the menu is up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vehCarModel::DrawHeadlights keeps its two ltLights' directions in world space: without the siren it sets them to the car's forward axis, with it it turns each about Y by +-42.411503 rad/s of the frame's time from wherever it points (Vector3::RotateY). OpenMM2 turned the car's forward axis by the siren's accumulated angle, so the sweep followed the car's turning and jumped each time that angle wrapped at 2 pi. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MM2 has one global irand/frand seed. aiMap::Init draws for the ambient pool (two constructor draws per car, then the type, SetColor twice and aiGoalRandomDrive per car) and then for the pedestrians (type, then aiPedestrian::Init's variant) from wherever the race's set-up left it; aiMap::Reset sets it to 1 and places the traffic (AdjustAmbients) and then the pedestrians (AdjustPedestrians) before anything updates. OpenMM2 gave the pedestrians a stream of their own (seed x 7919 + 1), so their clothes, placement and choices never matched MM2's. ai::World now draws both from one stream: the caller's (ai::Settings::random, MM2's global stream as aiMap::Init finds it) or its own, which reset() sets back to its seed. The first step after a reset populates the traffic and then the pedestrians before either updates (Traffic::populate, Pedestrians::populate), as aiMap::Reset orders them. The ambient array is built (and draws) at traffic density 0 too: MM2 constructs the state's vehicle count before it looks at the density. AiWorld.DeterministicAndWellBehaved now exempts a car that committed to an intersection (enterInt) before its light turned red: with the new draws one such car waits in a queue and turns 2.07 s into the red, which MM2's committed cars do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MM2 draws a race's set-up from its one global seed in a fixed order, so what it decides is the same every time in a city: cityLevel::Load sets the seed to 1 before each road's street props, leaving it as the last road's walk does (1 in London, whose last road has no props); mmPlayer::Init's vehCar::Init draws vehSiren::Init's lens flare (20 flares, six frand each) and vehSplash::Init's 64 random directions (three each, then overwritten); mmGame::InitGizmos the sailboats' paint and speed, the ferries' speed and the parked cars' choice and paint; aiMap::Init each racer's vehCar::Init, the ambient pool, the pedestrians, the cable cars (one draw each for the array's aiRailSet, then aiCableCar::Init's) and each police car's vehCar::Init. OpenMM2 started the gizmos at seed 1, the traffic at seed 1, the pedestrians at their own seed and the siren flares on a stream of their own, so the traffic's models and paints, the pedestrians' clothes, the parked cars and the boats all differed from MM2's. RaceScreen now keeps one stream (ai::Random) through loadWorldObjects (props, player car, gizmos, created before the AI now), loadAi (the racers' cars, then the AI world) and loadEffects (the cable cars, then the police cars), and hands it to the AI world, whose reset sets it to 1. - placeStreetProps / placeCityProps report the stream's state after the last road; roads without sidewalks are walked too (they draw, but nothing stands there: still 10,184 street props). - CableCars::create takes the array constructors' draws first. - takeVehCarInitDraws takes vehCar::Init's 312 draws; the player's, the racers' and the police cars' siren flares come from their place in the stream (VehicleRenderer::setSirenFlares). - game::fx::Rand is now ai::Random, one class for the generator; the particles and force feedback keep their old default seed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The waypoint heading convention (0 is -Z, 90 is +X, the start turned by heading x -0.017453292), the .opp grid row's angle (x 0.017444445, not negated) and which .opp columns aiRouteRacer reads, the [Police] columns, and the race tables' cop count. The .sky parameters (dome height offset and scale, turn rate), why neither retail .lmap is used (its count is checked against lvlSDL's room count, dummy room 0 included: London 1341 for 1342, SF 1125 for 1172), that .ext and .reset have no reader in midtown2.exe, and that TGA, BMP and JPEG textures are now flipped to MM2's row order before upload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aiMap::ReadBinary links each <city>_sup.bai shortcut road into its two end intersections and calls aiIntersection::CreateRoadMap on them, which first moves the intersection's centre to its room's bound-sphere centre (cityLevel::GetBoundSphere: the perimeter's area centroid at the mean corner height) and sorts the road list round that. OpenMM2 kept the file's centre. The file's centres were within 0.21 m of it, but mmGame::RespawnXYZ starts a cruise at an intersection's centre, so a shortcut end must sit where MM2 has it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GameLoop calls AudManager::Update before asRoot::Update, and BeginPhase adds the same manager to asRoot as its first node, so asRoot::Update calls it again: twice a frame. While the game runs each call updates the announcer's queue (mmSpeechContainer::Update) and mmGame::Update does a third time, so the queue's delays pass at three times the clock (the pre-race line starts 0.5 s after it is queued, not 0.75 s); while it is paused AudManagerBase::UpdatePaused's two stops both come in the first paused frame. OpenMM2 called the manager once a frame. AudioManager::updateFrame makes both calls and the race uses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MM2 picks the cruise start in the mode's InitOtherPlayers, which mmGame::Init runs after aiMap::Reset. aiMap::Reset sets the one global random seed to 1 and populates the roads of the player car's room, the car still at mmSingleRoam::InitGameObjects' place (mmGame's (0, 10, 0) plus its CG): with the cruise menu's densities that is 73 link choices for the ambient cars and 4 draws for each of 25 pedestrians in London (room 65), 21 in San Francisco (room 265). RespawnXYZ then draws (counter + 1) numbers irand() % (n - 1) + 1 and takes the last, again until the intersection fits: its room, FindRoomId of its centre, neither water of death nor a terrain instance's (0x24) nor subterranean or covered (0x0A), no freeway or alley road; the start is the centre 2 m up, angle 0. So the start is the same every time: London intersection 19 by Marble Arch, San Francisco intersection 121 on the Embarcadero. OpenMM2 picked a random intersection at race setup with a glibc-style generator seeded from the clock, and judged rooms by the .bai's room field (San Francisco's intersection 187 lies in a terrain instance's room that RespawnXYZ rejects). Now loadRaceSetup leaves the car at the InitGameObjects place, ai::World::globalSeedAfterReset replays aiMap::Reset's draws on one stream, and Session::placeRespawnStart picks the start with MM2's generator, rules and counter. Multiplayer cruise and Cops and Robbers draw twice from the player's own seed (mmMultiRoam / mmMultiCR::Reset, then InitNetworkPlayers) and advance mmGame's run-long counter, which also changes later single-player picks, as in MM2. The Cops and Robbers picker uses the same port. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every value that crosses from MM2's data files and code into OpenMM2, followed from MM2's reader to its uses and compared with OpenMM2's parser and use sites: the race files and spawns, the city files, the tune files, audio units, the HUD, frontend and option values, the renderer's conventions and the frames and angles derived in code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 3's cross-cutting check of the cruise start: how mmGame::Init, aiMap::Reset and mmGame::RespawnXYZ pick it in single player, multiplayer cruise and Cops and Robbers, the draws aiMap::Reset makes first (by city and density), the placement chain, and the London and San Francisco starts with what stands there. The session and game-flow records point their RespawnXYZ rows at it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aiTrafficLightInstance derives from dgUnhitYBangerInstance: Init gives it its model's banger data and bound and stands it at the pole's base + R * CG, and aiTrafficLightSet::SetFourWay moves it into the room of that CG (flags 0x13: a collidable banger). Cars collide with it like any prop and break it loose into its BREAKnn parts (dgUnhitBangerInstance::Impact); then it leaves the rooms, so neither its body nor its glows are drawn, and the parts are ordinary hit bangers. OpenMM2 only drew the poles, so cars drove through them. The poles now join the BangerSet (in aiMap:: Init's place, before the cable cars, so pedestrians step round them as aiPath::AddBangersToObsMap lists them); AiRenderer keeps drawing them with their signals from the props' frames while they stand (PlacedProp::ownerDrawn). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session record keeps the conventions audit's police row and the cruise-spawn audit's player start row; the counts combine both, and the manifest's results table follows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs/parity/round3/random-streams.md lists every consumer of MM2's global irand/frand stream (and of its secondary seed) with the order of draws from cityLevel::Load's street props to aiMap::Reset at a race start and a restart, OpenMM2's counterpart and a verdict; the records the fixes touch (ai-ambient-city, camera-props, vehicle, rendering-fx, the MM2-side infrastructure and world-objects) now point to it. San Francisco's parked cars from MM2's stream are pinned too (102). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mmGame::UpdateHorn opens mmGame::UpdateGameInput, so with the menu up (neither runs) the horn keeps the state it had: a horn held when the menu opened over a running game honks on, and one pressed under the menu does not sound. OpenMM2 read the horn key every frame. The race now keeps the held state (mmGame +0x274) from its own UpdateGameInput, which still runs while the game is paused without the menu, as MM2's does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/parity/round3/frames.md lists every model, part, glow, shadow, decal, particle emitter and light OpenMM2 draws or positions against the frame MM2 uses for it (GetMatrix / GetPosition, CG frame or model origin, part pivots, xrefs, multiplication order and signs), with the fixes of this round and the screenshots checked. The audits' own rows for the functions the fixes touched point at it, and the trailer's tail light is marked as inferred invisible (its material under the object pass's alpha test). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/parity/round3/order.md: MM2's tree and the call order of the load (mmGame::Init, the modes' Init, mmGameManager's Reset), the restart (mmReplayManager's reset flag and the modes' Reset), the frame (GameLoop, asRoot, mmReplayManager, mmGameManager, mmGame::Update), the paused frame, the menu over a running game, the draw order (asCullManager) and the end of a race, side by side with RaceScreen, with each difference's verdict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The cruise-spawn work picked the start from a replay of aiMap::Reset's population on a stream of its own (World::globalSeedAfterReset, Traffic/Pedestrians::replayResetPopulation). With the traffic and the pedestrians now drawing from MM2's global stream itself, that replay is redundant: RaceScreen::placeRespawnStart calls World::resetAndPopulate (aiMap::Reset of mmGame::Init: seed 1, then AdjustAmbients and AdjustPedestrians round the car's InitGameObjects place at once), takes the start from the shared stream as it stands (mmGame::RespawnXYZ), and resets the world again (mmGame::Reset's aiMap::Reset from mmGameManager's constructor), so the first step places the traffic and pedestrians round the start from seed 1, as MM2's second reset does. The draws before the first reset do not reach it (it starts from seed 1): the starts stay London 19 and San Francisco 121. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… audit AudManager::Update runs twice a frame, so the announcer's queue counts three times a frame and the pre-race line starts 0.5 s after it is queued. mmGame::UpdatePaused is never called in build 3393 and moves from ported to not needed; the XCam row no longer mentions its C and V keys. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The traffic light props (frames audit) join aiMap::Init's steps without moving MM2's stream: nothing in aiTrafficLightSet::SetFourWay or aiTrafficLightInstance::Init reaches irand or frand (only their DrawGlow, as the glows are drawn). The restart at the start of the next frame (order audit) reaches ai::World::reset once, so mmGame::Reset's aiMap::Reset sets the stream to 1 once per restart before the next step places the traffic and pedestrians round the start. Both round 3 records now say why the shared stream leaves the cruise starts where the cruise-spawn work put them (London 19, San Francisco 121): aiMap::Reset starts with ResetRandomSeed, so the set-up's earlier draws cannot reach RespawnXYZ, and the population's draw count does not depend on the types and clothes they chose; a new retail test checks it on the whole set-up. The cruise-spawn record's open row (the start's traffic) is closed: mmGameManager's constructor ends with mmGameManager::Reset, whose mmGame::Reset resets aiMap again from seed 1 round the start, as OpenMM2 now does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The five cross-cutting themes (frames, cruise spawn, random streams, order, conventions) in one table with what they fixed; ai-ambient-city's row moves two random-stream rows from deviation to fixed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MSVC 14.51 cannot default-construct a function-local class with default member initializers inside std::vector (C2672 in std::construct_at for std::vector<VertexSweep>(n)), which has failed the Windows build on main since 32903d6. No change in behaviour. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
London and San Francisco, cruise, the races, the crash courses, Cops and Robbers and online multiplayer all run; point at docs/parity for how closely each part follows the original and what is still open. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MSVC 14.51's optimiser crashed (C1001, access violation) on the phys::FlatGround made inside planeProbe's lambda. The probe now does FlatGround::probe's arithmetic itself; the tests are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parity audit round 3: cross-cutting checks of OpenMM2 against MM2Recomp (midtown2.exe build 3393), organised by theme instead of function by function. Records are in
docs/parity/round3/, and the results table is indocs/parity/README.md.Fixes
aiTrafficLightInstanceplaces them. The poles had been drawn 3.9 m in the ground under correctly placed glows. They are now props that cars knock over, as in MM2.mmGame::RespawnXYZputs it. The pick comes from MM2's one global random stream right afteraiMap::Reset: London intersection 19 at Marble Arch, San Francisco intersection 121 on the Embarcadero (with the cruise menu's default densities).mmGame::Init's order (props, the player car, gizmos,aiMap::Init, the resets). Traffic and pedestrians share it.AudManager::Updateruns twice a frame, and the horn and steering behave as MM2 does with the menu up.tune/<car>.asnode; also the police heading factor, cop density and slider stepping.time:for automated screenshots.Testing
🤖 Generated with Claude Code