Physics¶
Verified against: SLUS_212.15 (NTSC-U, SHA1 e9cb2cc49aa046b9e494313dce2f5038ed17b2f4). Static reading in Ghidra
(2026-10-06, the whole of Physics/ on 2026-10-07); the game under PCSX2 2.9.94 (PINE, hooks and pcsx2 record, in
level99, 2026-10-07). The exact specifications of
Reimplementing Physics, RayCast and World were read in the disassembly on 2026-10-12.
Purpose¶
The physics world is what moving things collide through: the bodies and shapes of humans and world objects, the
sweep that moves a body against the level's collision mesh and the other bodies, the queries other
code asks (a shape, a box or a ray against the world), and a small per-tick step. It is not a rigid-body
simulator: nothing in it integrates velocities or applies impulses, and a contact changes only the moving body's
velocity, as its owner's contact handler decides (Contacts). Each mover integrates itself in its own
update (humans in Humans_Update, world objects on the wheel) and asks
the physics world to sweep it.
The 60 Hz step (0x00340918, called on every tick of the play tick) does two small jobs: it
plays the "settle" turn of a thrown or dropped object that has landed, and every 2 s it refreshes a broad-phase
bound. Neither moves a human or decides a hit.
Original structure¶
One object of class IPhysics (name string 0x005502b0), 0x4f460 bytes, created at boot into 0x00597198
(Boot). Its code lies before and in c:/Warriors/Source/Physics/physics.cpp
(path 0x005771d0; Source map): the constructor 0x0033c288 starts right after the heaps'
block allocator, so 0x0033c288-0x003418f8 is Physics/ too (inferred; no path string names the file).
In-house, no middleware. Confirmed (code) for the names: the class is IPhysics and the path is the game's own
c:/Warriors/Source/Physics/. Inferred for the absence: no string of the executable names Havok, Karma, Meqon, ODE
or a ragdoll, and the linked libraries (Source map) include no physics library.
Every function of Physics/ (0x0033c288-0x0034f740), by area; the sections below say what each does. Names are
ours (some set by the Combat research) and match the local Ghidra project; all confirmed (code) at the
address unless a section says otherwise.
| Area | Functions | Evidence |
|---|---|---|
| The world | IPhysics_Construct 0x0033c288, IPhysics_Step 0x00340918, Physics_StaticInit 0x0034f6c0 and its stub 0x0034f718 (copy a 48-byte constant from 0x00511720 to 0x006eb9e0) |
confirmed (code) |
| Bodies | create and free: IPhysics_CreateHumanBody 0x0033cc48 / _FreeHumanBody 0x0033ce00, _CreatePunchBagBody 0x0033cb08 / _FreePunchBagBody 0x0033cbf0, _CreateObjectBody 0x0033ce58 / _FreeObjectBody 0x0033cf00, _CreateCarBody 0x0033cf68 / _FreeCarBody 0x0033cff8, Physics_CreateBoxBody 0x0033d070 / IPhysics_FreeBoxBody 0x0033d158; lists: IPhysics_AddBody 0x00340668, _RemoveBody 0x00340700, _AddHumanBody 0x003407a0, _RemoveHumanBody 0x003407f8, BodyList_Insert / _Remove / _Reinsert 0x003404a8 / 0x003405d8 / 0x00340628; the body: PhysicsNode_Init 0x00341158, PhysicsBody_Construct 0x003418f8, _ConstructForOwner 0x00341970, _Destroy 0x00341a00, _SetOwner 0x00341a68, _AddShape 0x00341aa8, _GetShapes 0x00341ae0, _Update 0x00341c10, _Moved 0x00341e40, _GetContactScale 0x00341e18, _ComputeBounds 0x00341638, _AddShapeBounds 0x003417c0 |
confirmed (code) |
| Shapes | pools: IPhysics_AllocObjectSphere 0x0033d1a0, _AllocObjectBox 0x0033d1e8, _AllocCarBox 0x0033d230, _AllocBigSphere 0x0033d278, _FreeShape 0x0033d2c8, _FindFreeStrikeSphere 0x003408d8; base: PhysicsShape_Construct / _Destroy 0x00342a30 / 0x00342a70, PhysicsShape_GetBounds 0x00341190; sphere 0x00342aa0 / 0x00342ae0 / pose 0x00342b08; box 0x00342d18 / 0x00342d98, PhysicsBox_SetSize 0x00342dc0, pose 0x00342df8, _SetMatrix 0x00343040, _GetCorners 0x003430e8; capsule 0x003434a8 / 0x003434e0, size 0x00343508, pose 0x00343518; segment 0x003437b0 / 0x003437e8, pose 0x00343810; AABB_FromSphere 0x00341660, AABB_FromUprightCylinder 0x00341708, AABB_ExtendByMove 0x00341840, AABB_ScaleAboutCentre 0x003414c0 |
confirmed (code) |
| The human body | PhysicsHumanBody_Construct 0x00341e98, _Destroy 0x00341f00, PhysicsBody_ApplyHumanScale 0x00341f28, PhysicsHumanBody_SetPushSphere 0x003420d0, _GetShapes 0x003420c8, _OnStruck 0x00342130, _AddStrikeSphere 0x00342158, _ClearStrikeSpheres 0x00342168, _GetContactScale 0x00342288, _Update 0x003422d0, _PushAwayHumans 0x003425c0, _Moved 0x00342828, PhysBody_SetShapeEnabled 0x003428d0, _AddBoneShape 0x003429e8, Human_TestStrikes 0x0033f110 (Combat) |
confirmed (code) |
| Ignore lists | IPhysics_FindFreeIgnoreSlot 0x00340890, IPhysics_ForgetIgnoredOwner 0x0033cd70, PhysicsBody_IsIgnoring / _Ignore / _StopIgnoring 0x00341ae8 / 0x00341b20 / 0x00341b80, the human body's 0x003421d0 / 0x00342230 / 0x00342260 |
confirmed (code) |
| Queries | IPhysics_QueryBox 0x0033feb0, _QueryBoxFiltered 0x00340120, _CollideShape 0x0033e7f0, _OverlapShape 0x0033ebb8, _RayCastBodies 0x0033eee0 |
confirmed (code) |
| Sweeping a body | PhysicsBody_Sweep 0x0033e278, IPhysics_SweepShapeVsMesh 0x0033d2d8, _SweepBodyVsMesh 0x0033d340, PhysicsBody_SweepHumanMask 0x0033d498 (against the bodies), PhysicsBody_PushOutOfWalls 0x003477c0 (Characters) |
confirmed (code) |
| Contacts | Contact_InsertSorted 0x0033d718, PhysicsBody_ResolveContacts 0x0033d9d8, PhysicsVec_BackOffPlane 0x0033d7b8, Vec_RemoveNormalPart 0x0033d870, PhysicsVec_Bounce 0x0033d8f0 |
confirmed (code) |
| Shape pairs | 14 overlap functions 0x00345d58-0x00347148, 16 sweep functions 0x00343cd8-0x00345d38, 2 ray functions 0x003459f0, 0x00345b90, the mesh functions 0x003473b8, 0x003473c0, 0x003473c8, 0x00347c08, 0x00348530, 0x00348a50 |
confirmed (code) |
| Mesh tests | CollisionMesh_BeginShapeQuery 0x00347170, MeshContacts_SortByFraction 0x00347300, the working triangle 0x00343a90, 0x00343b20, 0x00343c30, 0x00343c90 |
confirmed (code) |
| Geometry kernels | 0x00349180-0x0034ee60 (37 functions) |
confirmed (code) |
| Settling | IPhysics_StartSettle 0x00340fe0, _CancelSettle 0x003410f0, Settle_ComputeTarget 0x00340d08, Settle_NearestAxis 0x00340b38 |
confirmed (code) |
Outside Physics/:
| Address | Name | Role | Evidence |
|---|---|---|---|
0x00336a00 |
Quat_Slerp(t, out, from, to) |
spherical interpolation, shortest arc | confirmed (code) |
0x00335b08 |
Quat_IntegrateAngular(dt, q, ω) |
q += ½ ω q dt, normalised |
confirmed (code) |
0x00394050 |
WorldObject_OnContact |
the world object's contact handler (vtable 0x005453a0 +0xfc) |
confirmed (code); role inferred |
0x00395a10 |
WorldObject_Integrate |
the world object's integrator (vtable +0x144) |
confirmed (code) |
0x00395b70 |
WorldObject_Update |
the world object's update (vtable +0x13c) |
confirmed (code) |
0x00391c10 |
WorldObject_Remove |
cancels a settle, leaves the ObjectTaskManager |
confirmed (code); role inferred |
The world object here is the class of vtable 0x005453a0, allocated from the ObjectTaskManager's pool
(0x004f3e98) and one of the four classes seen on the wheel (Tasks); its kind is an
ObjectAttribs entry (0x00512c04, indexed by the object's +0x112).
Data¶
IPhysics¶
Confirmed (code) at 0x0033c288 and the step. Pool sizes are counts × bytes.
| Offset | Type | Meaning |
|---|---|---|
+0x04 |
ptr | head of the human body list (below) |
+0x08 |
ptr | head of the body list, sorted by AABB min x |
+0x10 |
u16 | zeroed at construction; reader not traced |
+0x12 |
u16 | ticks since the last bound refresh (0-119) |
+0x14 |
float | the widest body in x, max(max.x − min.x) |
+0x18 / +0x7c |
5 × 5 fns | the shape-pair overlap and sweep tables |
+0xe0 / +0xf4 / +0x108 |
5 fns each | per shape type: sweep against the level mesh, overlap with it, a ray against the shape |
+0x120 |
300 × 0x60 | spheres: the humans' five bone spheres each |
+0x71a0 |
300 × 0x70 | segments: the humans' five bone segments each |
+0xf4e0 |
60 × 12 | the ignore slots |
+0xf7b0 |
60 × 0x60 | spheres for weapon strike spheres (0x003408d8), switched off at construction and flagged 0x2 (flags 0x2) |
+0x10e30 |
60 × 0x60 | the humans' capsules, made with (2.0, 0.35) by 0x00343508 |
+0x124b0 |
60 × 0xe0 | the humans' bodies: each holds its capsule (+0x30) and ten bone shapes |
+0x15930 / +0x22930 / +0x2c530 |
416 × 0x80 / 0x60 / 0x90 | world objects: bodies, spheres, boxes |
+0x3af30 / +0x42eb0 |
255 × 0x80 / 0x90 | glass panes: bodies, boxes |
+0x4be20 / +0x4c720 |
18 × 0x80 / 36 × 0x90 | cars: bodies, boxes |
+0x4db60 |
30 × 0x60 | 2.0 m spheres: the humans' push spheres |
+0x4e6a0 |
64 × 0x30 | settle slots (below) |
+0x4f2a0-+0x4f3f0 |
the punch bag's body set: a human body, one segment (bone 3, radius 0.35, length 1.5, no offset, along the bone's x) and a capsule (radius 0.35, height 1.787 m); Bodies | |
+0x4f450-+0x4f45c |
vector | every live body, by index (body +0x20): +0x4f454 the array, +0x4f458 / +0x4f45c count and capacity (inferred), all zeroed at construction; +0x4f450 is not written (Unit 15) |
The 60 human body sets match the 60 human slots (AI humans), inferred. Who allocates from which pool is
confirmed (code) by the callers: Obj_CreatePhysicsBody (world objects), GlassPane_CreateBody, Car_MakeBodies. The
ten bone shapes are the strike shapes; their bones, sizes and events are on Combat.
Bodies¶
Body (0x80, PhysicsBody_Construct 0x003418f8, vtable 0x00544710). Confirmed (code):
| Offset | Meaning |
|---|---|
+0x00 / +0x10 |
AABB min / max (w = 1) |
+0x20 |
index in the body vector, −1 when none (PhysicsNode_Init 0x00341158; IPhysics_RemoveBody does not reset it, Unit 7) |
+0x24 / +0x28 |
previous / next in the x-sorted list |
+0x30 |
first shape (a chain through shape +0x00) |
+0x34 |
owner (a task object); free when 0 |
+0x38 |
ignore list (below) |
+0x3c |
group (s16): two bodies of the same non-zero group never touch |
+0x40 |
flags: 0x80000000 dirty (re-pose and re-sort on the next update), 0x800 update every time, 0x801 sweep the moved shape against the bodies in the update, 0x8000 no slide on contact; the starting values per kind are below |
+0x44 |
the owner's type mask, from owner vtable +0x20 (0x40 a human); queries filter by it |
+0x50 |
pending push-out, added to the next sweep's move |
+0x60 |
scale per axis (1.0; the player's capsule 1.4286, Characters) |
+0x70 |
the sweep's "could not move" counter |
+0x74 |
vtable |
Body vtable: +0x08 destructor (0x00341a00), +0x10 Update (0x00341c10), +0x18 Moved (0x00341e40),
+0x20 AddShape (0x00341aa8), +0x28 GetShapes (0x00341ae0), +0x30 / +0x38 / +0x40 the ignore
list, +0x48 GetContactScale (0x00341e18: 1.0 for a contact with a body, 0.98 with the mesh).
World-object bodies (Obj_CreatePhysicsBody, 0x00391d48), from the type
(CfgObj),
confirmed (code): shape 1 (OBB) is a box with half-extents size / 2 (+0x78, PhysicsBox_SetSize
0x00342dc0); shape 2 (SPHERE) a sphere of radius size.x / 2; both offset by centre (type +0x1c,
Object_GetAttachOffset 0x003917d8). Other shapes get no body, except one class (the RTTI check against
0x00580ec8) that gets a 0.75 m sphere with flag 4. The flags are then 0x80000500 plus a bit per PHYFLAG bit of
CfgObj argument 12 (+0x5e), the layers below; object kinds 0x1d, 0x21, 0x22, 0xf, 0x1a and
0x1f add 0x800, 1, 2, 0x100 or the object flag 0x200 (per kind, as listed at 0x00392160).
Collision layers¶
Body flag bits that a mover's mask selects (Sweeping a body), named by the scripts' PHYFLAG table
(enums), which Obj_CreatePhysicsBody maps one to one (confirmed (code)):
PHYFLAG |
Value | Body flag | Who meets a body with it |
|---|---|---|---|
BLOCKOBJECTS |
1 | 0x2 |
a flying world object's sweep |
BLOCKHUMANS |
2 | 0x4 |
a walking human's sweep |
MELEETARGET |
4 | 0x10 |
strikes |
WEAPONTARGET |
8 | 0x8 |
weapon strikes |
JUMPTARGET |
16 | 0x40 |
an airborne (or holding) human's sweep |
CHARGETARGET |
32 | 0x20 |
a human whose capsule has flag 2 (charging) |
RAYCASTTARGET |
64 | 0x2000 |
rays |
COMBATPASSTHROUGH |
128 | 0x10000 |
|
THROWNWEAPONTARGET |
256 | 0x20000 |
thrown weapons |
RUNTARGET |
512 | 0x40000 |
a human above gait 3 (running) strikes it on contact (Objects) |
The "who" column for strikes, rays and thrown weapons is inferred from the names; the street props' values and what
Human_OnContact does with JUMPTARGET, CHARGETARGET and RUNTARGET are on Objects.
Confirmed (runtime), the body flags
in level99's street: humans 0x2203f (0x2223f, 0x2343f), cars 0x2211a, glass panes 0x7a, world objects
0x500 (no layer) up to 0x5057e. So a walking human meets other humans (answered 4, Contacts)
and the objects with BLOCKHUMANS, but not a car's or a glass pane's body.
What stops a walker at a parked car is the collision mesh. Confirmed (runtime), quick-save slot 6: the player put
1.5 m from a parked car's side and walked into it at an angle (stick 80 % up; hook human-contact on
Human_OnContact) slid along the car's side 0.42 m from its body's box, and each of the 29 contacts had no object
(+0x40 = 0) and a mesh triangle with flags 0xf00d (enabled, type bits 2 and 3, top value 15) and material 0. The
car code has no collision-mesh call, so these triangles are inferred to be part of the level's static mesh at the
car's place; bit 2 is the player-climbable bit (Collision), which matches the climb onto a
car (Characters). The shells themselves are on Characters.
The create functions take the first free body of a pool (owner 0), set the owner (0x00341a68) and flags, and add it to
the body vector and the sorted list (IPhysics_AddBody 0x00340668); the free functions undo that
(IPhysics_RemoveBody 0x00340700, which first clears the owner from every ignore list) and, for objects, cars and
panes, clear the shape's in-use bit 0x8 (the human free clears none, and the pane free skips StopIgnoring, Unit
9). Flags by kind: human 0x8002243f, punch bag 0x8000243f, world object 0x80000500,
car 0x80000000
(then 0x8002211a once Car_MakeBodies ORs in 0x2211a, 0x00387ea8), glass pane 0x8000007a
(Objects). A human body also joins the human list (0x003407a0; next +0xc8, previous +0xcc) and gets
its scale (0x00341f28). The punch bag is the human class 0x1cc (HeavyBag,
Characters): 0x0021ce08 gives it its own body set instead of one of the 60.
The punch bag's shapes (IPhysics_CreatePunchBagBody, 0x0033cb08: the capsule on, the bone shapes off), read at
runtime (PCSX2 2.9.94, level95, the bag at rest with its origin at height −197.567; confirmed (runtime), the posing
confirmed (code) at 0x00343810 and 0x00343518):
| Shape | Bone | Offset +0x10 |
Size | Posed |
|---|---|---|---|---|
segment (type 4, inline at body set +0xe0, also listed at a pool copy) |
3 | (0, 0, 0) | radius 0.35, length 1.5 | start = bone 3's position, 2.15 m above the origin; end = start + bone 3's local x axis × 1.5, which at rest points straight down (end 0.65 m above the origin, 0.05 m off the vertical); so it swings with the bag |
| capsule (type 3) | none | (0, 0, 0) | radius 0.35, height 1.787 m | base on the bag's origin; the height is recomputed each update as for a human (bone 6's height + 0.2 × scale) |
The segment is what strikes meet (Combat); the capsule is what sweeps and pushes meet (Workouts).
PhysicsBody_Update (0x00341c10), when the body is dirty or flag 0x800 is set: pose every shape (shape vtable
+0x28), recompute the AABB from the shapes (0x00341638, 0x003417c0), re-sort (0x00340628), clear the dirty
bit; with 0x801 set, it also sweeps the first shape from where it was to where it is against the other bodies and
resolves the contacts, so a body moved by its owner still meets what is in the way. The candidates come from the
new AABB grown forward by the move, not from a box around the whole move, so a body passed through can be
missed (Unit 7). The camera builds bodies on its
stack for its sphere tests (0x00341970).
Shapes¶
A shape is a 16-byte-aligned record with a common head (PhysicsShape_Construct 0x00342a30, vtable 0x00544870):
+0x00 next shape of the body, +0x10 local offset, +0x20 the previous world position (for sweeps), +0x30
type, +0x31 bone (−1: the body's own frame), +0x32 flags (0x1 enabled, 0x2, 0x4 a target for strikes,
0x8 allocated, 0x10 reset: the next pose makes the previous position equal the new one, for the sphere, box and
segment but not the capsule, Unit 6), +0x34 vtable of 8-byte {adjust, function} slots
(the enable
sets 0x11, a getter returns a pointer to the world position, a setter +0x0c writes it, the pose, and "set from a
matrix", which moves a sphere's centre or a capsule's base to the matrix's position and sets a box's axes; slot by
slot in Unit 6). The world AABB of any shape is 0x00341190: for a capsule the upright
cylinder's box,
and a segment pointing down on an axis gets a wrong bound there (Unit 6). Confirmed
(code):
| Type | Shape | Size, vtable | Fields | Pose (vtable +0x28) |
|---|---|---|---|---|
| 1 | oriented box | 0x90, 0x005447f0 |
+0x40 half extents (PhysicsBox_SetSize 0x00342dc0 halves the sizes it gets), +0x50-+0x7c axes, +0x80 position |
the owner's matrix and the local offset (0x00342df8); cars set the matrix directly (0x00343040) |
| 2 | sphere | 0x60, 0x00544830 |
+0x40 radius, +0x50 centre |
the local offset on its bone (owner vtable +0xb8) or in the body's frame (owner vtable +0xa0) (0x00342b08) |
| 3 | capsule | 0x60, 0x005447b0 |
+0x40 radius, +0x44 height, +0x50 base |
for a human at its position, the offset unused, and the height follows the state; for another owner the position plus the unrotated offset (0x00343518, below, Unit 6) |
| 4 | segment | 0x70, 0x00544770 |
+0x40 start, +0x50 end, +0x60 radius, +0x64 length |
start = its bone plus the bone-rotated offset (bone −1: the body's position, offset ignored), end = start + the bone's x axis × length (0x00343810) |
The "capsule" is upright: the capsule-capsule tests treat it as the vertical range [base z, base z + height] with
a horizontal radius (0x00346388, 0x003498c0 + 0x0034a8d0), so a human never tilts. The sweep swaps the two
capsules' heights (0x00343e1c-0x00343e28, Unit 11); the overlap test does
not. The
capsule-box sweep reads no height or z range at all: only the candidate gather separates them vertically (Unit
12). Two overlap tests round its ends instead (capsule against sphere and against segment,
a true capsule
reaching r past both ends), and the capsule-box overlap reads no height at all (Unit
4); confirmed (code). A segment is a sphere of its radius
swept along a
bone: the limbs' strike volumes.
The human body¶
0xe0 bytes (0x00341e98, vtable 0x005446b0): a body plus +0x80 the ten bone shapes (0x003429e8), +0x84
the push sphere, +0x90 a broad bound, +0xc0 one bit per switched-on bone (PhysBody_SetShapeEnabled
0x003428d0), +0xc4 the weapon strike spheres (0x00342158, 0x00342168), +0xc8 / +0xcc the human list,
+0xd0 a weapon-strike flag, +0xd4 "ignore the level". The bone shapes, their sizes and when they switch on are on
Combat; PhysicsBody_ApplyHumanScale (0x00341f28) multiplies their offsets, radii and
lengths and the capsule (height 0x00219890, radius 0x00219860 of the human) by the human's scale. Confirmed
(code) at the cited addresses:
- Update (vtable
+0x10,0x003422d0), unless flag0x4000: re-pose the capsule when dirty; with weapon strike spheres, pose them on the held object (0x00226ff0); while anything strikes (and on a dirty update without strikes), rebuild the broad bound with the strike reach max(0.8 × the scale, the current attack's reach,AttackTable_GetReach), or with weapon spheres the horizontal distance to the first one plus its radius; pose the bone shapes with flag0x1or0x4and the push sphere (Unit 10). - Moved (vtable
+0x18,0x00342828): the broad bound+0x90becomes an upright box 0.8 m out horizontally and 2.0 m tall from the position (Unit 10), and each bone shape with flag0x1or0x4is "set from" the new matrix (0x00342890): a sphere's centre jumps to the body's new origin (no offset) with its previous position left alone, and a segment is not touched. So it does not reset the previous positions: the next pose sweeps a sphere from the new origin to its bone, a segment from its start before the teleport (Unit 6). - The capsule's height (
0x00343518): base height + 0.2 × scale; the bottom raised by 0.61 × scale in one mode (+0x3be) and by 0.7 × scale in the states0x00227f68selects (at most 0.7 above+0x560), except in a long fall; 0.35 × scale when cuffed; never under 0.2 × scale. - The push sphere (
0x003420d0): a 2.0 m sphere from the 30 pool, switched on only byGrab_StartandTackle_Start; every other caller switches it off (Unit 10). While it is on,0x003425c0(from the human's update0x0023d8c8) overlaps it with the other humans (options0x2012, mask0x40) and adds to the pending push-out (body+0x50) of each one that is not its target, has no player number and has no body flag0x200a push of 0.25 m × w(this) / (w(other) × k(other)) along the 3-D line from this human to the other (Unit 10): bystanders are nudged out of the animation's way. w is attribute 1, 82.0 for every human, so it cancels; k is attribute 7, the other's push weight (1.0, or 1e9 while it must not move). Confirmed (code) at0x003425c0. GetContactScale(+0x48,0x00342288): 0.25 while airborne, else 1.0.+0x50(0x00342130) tells the human (0x00219b08).
Attributes¶
An owner answers numbered float attributes through its vtable +0xd0 (and sets them through +0xe0). Confirmed
(code) for the human (0x00221260, setter 0x002212b0) and the world object (WorldObject_GetFloatProperty,
0x00395e40); the names are inferred from use:
| # | Human | World object | Read by |
|---|---|---|---|
| 1 | 82.0 | its type's +0x62 (CfgObj argument 6; 10 or 50 for most types) |
the push sphere (The human body); a weight |
| 7 | +0x27c: the push weight, 1.0, set to 1e9 to make the human immovable (grabs, arrests, Combat) |
1.0 | the push sphere |
| 8 | 0 | its type's +0x88 (CfgObj argument 11, named mass in the scripts): 0.1 for 1,359 of the 1,371 types, 0.2 for 7 (the cue ball among them), 0.15 and 0.4 for one each, 0 for 3 |
contact code 2's bounce: the restitution |
| others | 0 | 0 |
A third slot, vtable +0xd8, asks whether the owner has attribute n at all; the human's answer
(Human_HasAttribute2, 0x002212a0) is yes for attribute 2 only. Confirmed (code); its callers are not traced.
The values per type are confirmed (runtime), read from level99's object database. The bounce
(PhysicsVec_Bounce, 0x0033d8f0) is v −= n (v·n)(1 + e) when v·n < 0, and v −= n (v·n) e (an outgoing
normal speed cut to 1 − e of itself) otherwise (Unit 3); a brick
that hit the floor at 5.4 m/s came back up at 0.54 m/s (Settling).
Ignore lists¶
A body can ignore particular owners: sixty 12-byte slots at IPhysics +0xf4e0 (+0x00 the body, +0x04 the
ignored owner, +0x08 next), chained from body +0x38. Ignore (vtable +0x40, 0x00341b20) takes a free slot
(0x00340890), IsIgnoring (+0x30, 0x00341ae8) walks the chain, StopIgnoring (+0x38, 0x00341b80) frees
one slot or, given null, all. When a body is removed, every slot naming its owner is freed (0x0033cd70). The human
body adds two rules (0x003421d0, 0x00342230, 0x00342260): its owner's grab partner (Human_GetGrabOther
0x002269d8, the other human of a grab) is always ignored, and a null owner
means the level itself (+0xd4). With all sixty slots in use, Ignore does nothing. Confirmed (code).
Contacts¶
A contact is 0xc0 bytes, two 0x40-byte halves for the moving shape and the other one (0x0033d718: +0x00 own
owner, +0x04 own shape, +0x40 the other owner or 0 for the level, +0x44 the other shape), then +0x50 the
normal, +0x80 the fraction of the move at the contact (0 to 1), +0x90 the point, +0xa0 a slide vector,
+0xb0 flags (0x80 a human's landing), +0xb4 the mesh triangle. A mirrored pair function swaps the halves.
Lists hold at most 50 contacts, kept sorted by fraction. Confirmed (code).
Resolution (PhysicsBody_ResolveContacts, 0x0033d9d8) walks the sorted contacts and asks the moving body's
owner what to do (owner vtable +0xf8, for example Human_OnContact 0x00219d50); a contact is skipped when its
triangle has been disabled since it was found, or when its other owner has no physics body (owner vtable +0xf0
returns the body). The low 16 bits of the answer (the whole walk, exactly: Unit 3):
| Code | What the branch does | Evidence |
|---|---|---|
| 0 | nothing | confirmed (code); summary inferred |
| 1 | advances the body along the normal only, to 0.01 m short of the touch, when that is more than 0.01 m (owner position, vtable +0xa8 / +0xb0; the part of the move along the surface is not applied), then removes from both velocities (the move's and the real one) the part that goes into the surface (0x0033d870) |
confirmed (code) |
| 2 | the same advance; then bounces both velocities off the normal with the owner's restitution (owner vtable +0xd0 with 8; 0x0033d8f0) |
confirmed (code) |
| 3 | the same advance, then zeroes both velocities | confirmed (code) |
| 4 | as 1, after pushing the other body: unless its flag 0x200 is set, 0.5 × (d · n) n × w(mover) / (w(other) × k(other)) is added to its pending push-out (+0x50), d being this pass's move and n the contact normal (pointing at the mover), so the other is moved away by half of the mover's move into it on its own next sweep; then the other body's vtable +0x54 is told (Human contacts) |
confirmed (code) |
High bits: 0x10000 ends the walk and the move goes on (return 0), 0x40000 aborts the move (return −1),
otherwise the walk stops after this contact and the sweep makes another pass (return 1). Code 0 without either bit
goes on to the next contact. 0x20000 with a non-zero code keeps the largest contact scale met (body vtable
+0x48), applied to the velocity at the end of the move. Contact flag 0x1 (the corner slide of
Unit 2) adds the slide vector +0xa0 to the move's velocity,
flag 0x2
stores it once per move in body +0x50; both first drop its part against the move's direction. A code above 4
takes none of the branches. Confirmed (code) at 0x0033d9d8 (Unit 3).
Who answers what. Owner vtable +0xf8 is set in seven classes of the task family; the vtables were found from
the code that stores them, and each handler was decompiled. Confirmed (code):
| Owner | Handler | Answers |
|---|---|---|
human (vtable 0x0053f088) |
Human_OnContact 0x00219d50 |
0, 0x20000 or 0x20001 (slide) against the level and objects (Characters); 1 against a body with type flag 0x200; against another human, 1 after a strike (Strike_Contact 0x0021b290, its vtable +0x104), otherwise 4, with 0x20000 added while airborne |
world object (0x005453a0) |
WorldObject_OnContact 0x00394050 |
2 against a body or a steep surface (normal z ≤ 0.7), 0x20002 on a floor, 0x10003 on a floor once settled (Settling); 0x10000 (after 0x00393e20) while its byte +0x10d or +0x10e is clear; 0x40000 when, thrown, it hits a human; 0 or 5 (no branch) when 0x003951d8 gives it a human (its holder, inferred) |
car (0x00544c08) |
Car_OnLanded 0x00389e08 |
always 2; with the level it also clears airborne and zeroes the velocity (Cars) |
glass pane (0x00544ed0), light task (0x00545138), particle system (0x00545660), scene (0x005458c8) (Task classes) |
0x004dac20 |
0 |
So 3 is never answered: nothing in the game zeroes both velocities on a contact.
Settle slot¶
0x30 bytes, free when +0x00 is 0:
| Offset | Type | Meaning |
|---|---|---|
+0x00 |
ptr | the object |
+0x04 |
float | progress t, 0 to 1 |
+0x08 |
float | acceleration of t, per tick²: 0.02 (0x3ca3d70a) |
+0x0c |
float | speed of t, per tick |
+0x10 |
quat | end rotation |
+0x20 |
quat | start rotation |
Object flags (+0x54, task head): 0x40000 settling, 0x8000000 settled.
Behaviour¶
The step¶
IPhysics_Step(phys) (0x00340918), once per 60 Hz tick after the wheel. Confirmed (code):
- Bound refresh. Count the tick in
+0x12; at 120 (every 2 s), reset it and recompute+0x14as the widest body in x over the list. Inserting a body (0x003404a8) only ever grows+0x14, so the refresh is what lets it shrink. The queries read it to know how far back along the sorted list to look (Queries). - Settle turns. For each of the 64 slots in use:
t += speed,speed += 0.02, clamptto 1; set the object's rotation toslerp(start, end, t), keeping the position of its stored pose (owner slot+0xa8/+0xac), and write the pose through owner slot+0x68/+0x6c(the world object's0x00395220, which changes nothing while the object is held, flag0x10); if the object has a body, mark it (+0x40 |= 0x80000000) and update the body at once (body slot+0x10/+0x14,PhysicsBody_Update0x00341c10: its shapes re-posed and re-sorted). Att= 1 the object's flags lose0x40000and gain0x8000000, and the slot is freed.
Starting from rest, t after n ticks is 0.01 × n(n − 1), so a settle takes 11 ticks (183 ms), slow at
first and fastest at the end, like something tipping over.
Confirmed (runtime) in a level: a settle slot filled by hand (an object, start = its rotation, end a quarter turn,
acceleration 0.02, speed 0) read t = 0.02, 0.12, 0.2, 0.3, 0.42, 0.56, 0.72, 0.9 on successive samples, then 1.0
with the slot freed and the object's flags 0x40000 → 0x8000000, 0.21 s after the slot was written. A real
landing is in Settling a landed object.
Settling a landed object¶
WorldObject_OnContact (0x00394050) gets the contact (hit object at +0x40, normal at +0x50, point at
+0x90). Confirmed (code): when no human holds the object (flag 0x10), it did not break on this contact (both
wear bytes +0x10d and +0x10e non-zero, Contacts), the contact is the ground (no hit object and
normal z > 0.7), its type's axis byte (+0x85, CfgObj
argument 10) is not 0, and it is neither settling nor settled (0x8040000), it starts a settle
(IPhysics_StartSettle, 0x00340fe0): the first free slot (or the object's own),
speed 0, start = its rotation extrapolated to now (Obj_GetPoseNow, owner slot +0xa0 / +0xa4: the stored
rotation turned by the angular velocity over the time since its last update, Unit 15), its
angular
velocity zeroed (owner slot +0x78 / +0x7c, 0x003a19a0, with the zero vector 0x005116c0), flag 0x40000.
With all 64 slots busy nothing happens.
The end rotation (Settle_ComputeTarget, 0x00340d08) turns the object by the smallest angle that lines up one of
its local axes with the contact normal, the turn wrapped to ±90°: it comes to rest on its nearest face. The axis
(Settle_NearestAxis, 0x00340b38) is the one whose positive direction is nearest the normal (the smallest
acos(axis · n), not |axis · n|; the ±90° wrap then lets the negative end land on the floor too, so an allowed
axis pointing straight down can lose to one lying flat, which is turned through 90°) among the axes the
type's axis byte allows: the byte is passed through unchanged (lbu a3,0x85(v0) at 0x003946ec), bit 0 is the
local x axis, bit 1 y, bit 2 z (AXIS: X 1, Y 2, XY 3, Z 4, XZ 5, YZ 6,
XYZ 7). Bit 3, in XZ_ROUND 13 and YZ_ROUND 14, is tested but the axis it picks is never read, so a _ROUND
type settles like XZ or YZ. Confirmed (code). Of the 1,371 object types loaded in level99, 855 have axis 0
and never settle, 197 are Z, 119 XYZ, 76 X, 54 XZ (the brick, the beer bottle), 30 XY, 28 Y, 4 YZ
and 8 XZ_ROUND (the bats and the pool cue); confirmed (runtime), read from the object database.
While it settles (0x40000 only), a floor contact starts nothing new and answers 0x20002: a bounce with the
type's restitution, then the move's velocity scaled by the mesh contact scale 0.98 (its z only when upward);
a wall or a body answers 2, the same bounce with no scale: no friction, the tangential velocity is kept.
Confirmed (code) at 0x00394050 and 0x0033e278, and the 0.98 at runtime (−5.4886 m/s became +0.5379, not
+0.5489). On a later ground contact with 0x8000000 set, the object stops: velocity and angular velocity zeroed, flags
0x4000000 (airborne) and 0x8000000 cleared, 0x2000000 (grounded) set. Removing an object (0x00391c10)
cancels its settle.
Confirmed (runtime), a brick dropped from the player's hand (scenario physics_brick_settle: Human_DropHeld called
on the player standing in the street of quick-save slot 6, no input; hooks on the object's update, its velocity
setter, the contact handler and the bounce, so every value below is the game's own, per update):
| Ticks after the drop | What happens |
|---|---|
| 0 | the drop: velocity set to 0 (Human_DropHeld clears +0x10c, so the holder start is not used); flags 0x4000000 airborne |
| 1 | first update: vz = −0.2613 (dt 1/60: the drop's message 28 stamps the object's last-update time a tick back, Task_SendMessage28 0x002296c0, Characters) |
| 3-19, every 2 | vz −= 0.5226 (dt 1/30, interval 2 ticks), then the sweep moves it |
| 20 | floor contact at −5.49 m/s: WorldObject_OnContact starts the settle (axis mask 5) and answers 0x20002; the bounce (e = 0.1, the type's +0x88, Attributes) gives +0.5379 m/s |
| 22-28, every 2 | while settling, vz drops by only 0.2613 per update (+0.2766, +0.0153, −0.2459, −0.5072, −0.7685): the update's dt is 1/60, not 1/30 (the step's settle turn re-poses the object every tick, which inferred stamps its time) |
| 31 | the settle ends (11 ticks after it started): flags 0x40000 → 0x8000000 |
| 32 | vz −= 0.5225 (dt 1/30 again), then the second floor contact finds it settled and answers 0x10003: back to 0.01 above the floor, velocity 0 (set three times: the handler, the sweep, the object), airborne and settled cleared, grounded set |
| 36 | interval back to 20 ticks |
The brick (OBB 0.07 × 0.21 × 0.10 m, centre (0, 0, 0)) came to rest flat on its 0.21 × 0.07 face with its origin
at z 0.2831: the floor (0.2231, where the player stands) + its half-height 0.05 + the 0.01 back-off. Four runs gave
this sequence; one other run met the floor one update earlier at a different contact point, and its velocity was 0
after each bounce (not explained).
Queries: the sort-and-sweep¶
Every query first gathers candidate bodies from the x-sorted list with a box. Confirmed (code) at 0x0033feb0
(IPhysics_QueryBox(phys, out, box, startBody, ignoredOwner, typeMask, flagMask)) and 0x00340120 (the same with
more filters):
- With a start body (the querying body itself, which is a candidate too), walk backwards from it, then
forwards from it. Without one, walk forwards from the head. The bound (IPhysics
+0x14, the widest body) stops the backward walk only at a node that would reach the box, so with a current bound the walk runs to the head; going backwards there is nomin.x ≤ box.max.xtest (Unit 7). - Forwards, stop at the first node with
min.x > box.max.x. - Keep a body when its
max.x ≥ box.min.xand it overlaps the box in y and z, its owner is not the ignored one, its type mask (+0x44) meetstypeMask(sotypeMask0 finds nothing) and its flags (+0x40) meetflagMask(0: any). The filtered form also drops bodies with excluded flags, owners whose state flags (owner vtable+0x50) meet an excluded mask, and pairs a callback refuses.
The answer is the backward hits in reverse list order, then the forward hits. The query has no cap: 512 is the size of every caller's list, and nothing checks it (Unit 7). On top of it:
| Query | Address | What it does | Evidence |
|---|---|---|---|
IPhysics_CollideShape(phys, out, query, move) |
0x0033e7f0 |
sweeps a shape (the body's own, or one given with a transform: option 0x400) along a move against the candidate bodies (option 2; the sweep table) and the level mesh (option 1; the type's mesh sweep with the query's two thresholds); keeps up to 50 contacts sorted by fraction and copies the first max out. Bodies of the same non-zero group and owners that refuse (owner vtable +0x110) are skipped. Used by the follow camera's sphere tests, the throw aim and the cars; the airborne push-out and WorldObject_UpdateBodyContacts sweep the mesh only (options 0x2004d); every option bit and caller in Unit 11 |
confirmed (code) |
IPhysics_OverlapShape(phys, out, query) |
0x0033ebb8 |
the same without a move, with the overlap table and the mesh overlap (+0xf4); option 0x80 skips the same group, 0x100 asks the owner, 0x200 only enabled shapes; it reads no other bits than 0x1, 0x2, 0x10, 0x80, 0x100, 0x200 and 0x400 (Unit 7). Used by the push sphere and the strikes |
confirmed (code) |
IPhysics_RayCastBodies(length, phys, out, ray, typeMask, ignoredOwner) |
0x0033eee0 |
the box around the ray's two ends, candidates with flag 0x2000 (RAYCASTTARGET), then each candidate's enabled shapes with the type's ray function (+0x108, boxes and capsules); keeps the nearest hit's owner, shape and fraction and writes no point or normal (Unit 14). Used by the AI's line tests (0x002e7608, 0x00308c60 and others) |
confirmed (code) |
So +0x14 is the classic sort-and-sweep bound: the step's refresh keeps it from growing for ever
(The step), though it never cuts the backward walk in practice.
Sweeping a body¶
PhysicsBody_Sweep (0x0033e278, Characters for the human's use) moves a body by
v × dt plus its pending push-out (divided by the owner's push weight, attribute 7, unless body flag 0x1000;
cleared on every call), in up to three passes. A pass, confirmed (code):
- The body's box at the start of the move, grown to cover the move (
0x00341840). A copy scaled about its centre by body+0x60(0x003414c0) is made and never used: both queries get the unscaled box (0x0033e518,0x0033e550). - Against the mesh (
0x0033d340): the first shape's mesh sweep (+0xe0by type, through0x0033d2d8with the wall threshold −0.65). For a human (type mask0x40) in the air, also the landing segment (0x0023e408), whose hit becomes a contact flagged0x80. Faces are one-sided unless flagged. In the capsule's mesh sweep (PhysicsMesh_SweepCapsule0x00347c08) a triangle is read only for its enabled bit (0x00347de0, no type-bit mask, so a door's0x400or0x40changes nothing), then: a wall (|n.z|≤ 0.65,0x00347e60); the side testn·c − n·v0 < 1e-6(0x00347ec0), wherecis the sphere's centre at the start of the move (the shape's+0x50, read at0x00347c80, raised byr + 0.05), skips a one-sided triangle (flag0x2clear,0x00347edc) and turns a two-sided one to face the sphere (0x00343c90,0x00347ee4); then the move must go into the face,n·move < −0.001(0x00347f2c). So a one-sided wall stops a body only from its front, and a body that starts behind it passes. Confirmed (code). What makes the level's doors block from both sides is the door's init setting flag0x2(Objects). - Against the bodies (
PhysicsBody_SweepHumanMask,0x0033d498): the candidates of the swept box whose body flags (+0x40; the type mask is −1) share a bit with a mask that depends on the mover (collision layers): 2BLOCKOBJECTSfor a non-human; for a human 4BLOCKHUMANS, or0x44(andJUMPTARGET) while airborne or holding (0x800), plus0x20CHARGETARGETwhen the capsule has flag0x2,0x40000RUNTARGETabove gait 3, and0x80000 << player; same non-zero group skipped; owner asked (+0x110); each enabled shape of the candidate (body vtable+0x28) through the sweep table, stopping at the candidate's first shape that hits (one contact per candidate body,0x0033d6c0). Confirmed (code). Between two humans this is one capsule against one capsule: Human contacts. - Contacts are inserted sorted by fraction (
0x0033d718) and resolved (Contacts).
The move is never shortened by a contact's fraction: each pass sweeps the whole slid V × dt again from where the last
advance left the body (Unit 3). After the passes: when three passes each stopped (or a
slide left no move),
the body stays where the last advance put it, its horizontal velocity is zeroed and written to the owner (vtable
+0x74) whatever the body's flags, and its counter +0x70 goes up; otherwise it moves by the rest and the counter is
reset. The velocity the slides leave is written back to the owner (vtable +0x74) only when body flag 0x400 is
set (0x0033e738) and a stopping contact asked for it (not a level triangle with flag 0x100, not two airborne
humans). Among humans only a player's body has 0x400 (world objects' bodies have it too, 0x80000500):
Human_CreatePhysicsBody (0x0021ce08) sets it with +0x60 = +0x64 = 1.4286 for a brain of type 0, as does
Human_MakePlayer (0x00229c40); Human_ReleasePlayer (0x0022a2a8) clears it and puts +0x60 / +0x64 back to
1.0. So a slide brakes the player (Characters) but never an AI, whose control asks for its speed
again each update. Confirmed (code); the AI's unchanged speed is confirmed (runtime) on
Characters.
Human contacts¶
What a walking human meets of another human, confirmed (code) at the cited addresses:
- The shapes. The mover sweeps its first shape, and the candidate's chain is walked from its first shape too
(body vtable
+0x28;PhysicsHumanBody_GetShapes0x003420c8returns+0x30): for a human that chain is the capsule alone, so the bone shapes and the push sphere never block. Capsule against capsule (PhysicsSweep_CapsuleCapsule0x00343cd8) uses each capsule's own radius (shape+0x40, 0.35 × the class scale, already scaled byPhysicsBody_ApplyHumanScale), without body+0x60or a second scale: centres meet at the sum of the radii (about 0.69 m for the player and a man of scale 1), and the two vertical ranges must overlap during the move. The level's sphere radius (Characters) plays no part. - Who is skipped. A candidate without
BLOCKHUMANS(0x4) in its body flags, one sharing the mover's non-zero group, and a disabled capsule. Human owners never refuse (owner vtable+0x110is0x004dac30, which returns 1), and the brain is not read, so a scripted orBrDeadhuman is met like any other. - Groups. Every link of two (or three) humans takes a fresh number from IPhysics
+0x10(counted 1 to0xfffe) and writes it to both bodies'+0x3c, so linked humans pass through each other:Grab_Link(0x0022c760),Pair_LinkMount(0x0022c000), the tackle (HitReact_WhileTackling),Grab_Escape,Revive_Start,Uncuff_BeginMash,Pair_AlignForMoveandTandem_AlignThree; each also sets both push weights (attribute 7) to 1e9. The matching ends write 0 (Grab_End,Tackle_End,Tandem_Unlink,Throw_ClearLink,Revive_Finish, the uncuff end hooks), as doesHuman_StartFall(0x0023dc58). A scene sets one group on the humans it plays (SceneTask_Start0x0039d870, cleared bySceneTask_End). The ignore lists (below) serve strikes, not the walk. - Who loses
BLOCKHUMANS. Body flag0x4is cleared (with the re-pose flag0x80000000set) byHuman_KnockOut(0x00230328),Human_StartWounded(0x0022fa78),Human_OnHealthOut(0x002674c0, again after either) andHuman_Arrest(0x0022ec18, the cuffs), so walkers pass over a human knocked out, wounded or cuffed; a dead one (state0x100000000) has his body freed unless airborne. Confirmed (code) at the cited addresses; no other& ~4in the executable reaches a human's body flags (searched by the mask'sli -5). - A knockdown keeps it.
Human_KnockDown(0x0022f100, state0x80000, the heavy reactions) writes no body flag: it sets the push weight to 1e9 (attribute 7), so a grounded human still blocks walkers, who slide along him without moving him, from the fall until he stands. Getting up (Human_UpdateDown0x00256a60→Human_PlayRise0x0025e2d8, clip 199 withHuman_EndDownState0x0025e250as its end callback →Human_GetUp0x0022f3c8) puts the weight back to 1 and touches no body flag either. Confirmed (code). - What gives it back. Two things only, confirmed (code):
- A new body.
Human_CreatePhysicsBody(0x0021ce08) frees the body and takes a fresh one fromIPhysics_CreateHumanBody(0x0033cc48), whose flags are0x8002243f(BLOCKHUMANSincluded; group 0, so no link survives).Human_Heal(0x0022eb40) calls it after waking (Human_WakeUp) and ending a wound (Human_EndWounded), so the human blocks again from the moment he is healed: a revive at its end (Revive_Finish0x00274ee0, notRevive_Start), a flash (Flash_Use0x002842e4), the uncuffing (Uncuff_MashSuccess0x00260910,Uncuff_WithKey0x00261264),HuRevive(Human_Revive0x002377f8), and the level's own heals (Gm_Level_Update0x00159004,ANGameOver_Toggle).Human_AttachInstancealso makes one when the human is set up. Human_Unarrest(0x0022ef58) sets body flags0x80000004: the uncuff paths,HuSetArrested(false)(0x0023774c) andHuman_EndDownStatewhen a cuffed human's rise clip ends.
- A new body.
- What does not.
Human_WakeUp(0x00230598) andHuman_EndWounded(0x0022fc00) only re-enable a shape and put the push weight back. So a knocked-out human woken without a heal (HuSetConscious(true),Human_SetConscious0x00237778, orHuman_EndDownState's knocked-out branch) and a wound ended byHuSetWounded(false)orHuman_ResetForModestand up withoutBLOCKHUMANS, and walkers keep passing through them until something heals or unarrests them. Confirmed (code). -
Seen at runtime (confirmed (runtime), PCSX2 2.9.94, a copy of slot 6, 2026-10-11, scenario
repo:research/traces/scenarios/blockhumans_wake.toml). PoizoCiv, standing, his body flags0x2223f, was put through six direct calls on the game's thread 40 updates apart, his body pointer and body+0x40read every update:Call Body flags after it BLOCKHUMANSHuman_KnockOut(him, 0)0x2203boff Human_WakeUp(him, 0)(whereHuSetConscious(true)and a knocked-out rise end)0x2203b, unchangedstill off Human_Heal(him, 0)0x2203fon Human_StartWounded(him)0x2203boff Human_EndWounded(him)(whereHuSetWounded(false)ends)0x2203b, unchangedstill off Human_Heal(him, 0)0x2203fon
The heal's new body took the same pool slot (the pointer did not change). So waking and ending a wound leave him
passable, and only the heal gives the flag back, as the code says. The knock-out also took bit 0x200 (not
pushable) away, which Human_KnockOut's own writes do not: what cleared it in that update is not traced, and the
heal's new body did not have it either (Human_CreatePhysicsBody sets it only when +0x3bf is 0; that byte was
not read).
- The answer (Human_OnContact 0x00219d50): a charging capsule (shape flag 0x2) or an airborne mover not yet
ignoring the other strikes him (Strike_Contact, then ignores him) and slides (1); otherwise 4, 0x20004 in
the air. So walking into a human slides along him and pushes him by half of the move into him (code 4 above),
times 1 / his push weight k (1, or 1e9 while held: nothing). Body flag 0x200 (not pushable) is set by
Human_SetPushable (0x0021d848, HuSetPushable(false); also when a body is made for a human whose +0x3bf is
0, Human_CreatePhysicsBody), and by the punch bag; such a human is slid along but never moved.
- The pushed human is told (body vtable +0x54, PhysicsHumanBody_OnStruck 0x00342130 →
Human_OnPushContact 0x00219b08): only when the pusher is a player and walks into him nearly head-on (the move's
direction · the normal < −0.8). A cop, a civilian, or a human of the pusher's side (friendly, and the pusher at a jog
or faster) starts a timer at +0x56c / +0x570 (10 s, or 2.1 s for a friend); a second push after 2 s deals
Human_DealDamage(him, 0, pusher, 0, hostile 1, 0) (unfriendly: no damage, the hostile flag set; inferred: he
takes it as an attack by the player, not checked at runtime) or asks Human_CanPush (friendly) and clears it.
Human_StateUpdate clears the timer once the game time passes +0x56c (0x002405e0), so the second push must
come before the deadline: 2-10 s after the first for a stranger, 2.0-2.1 s for a friend. A bum under BumLogic
ignores it.
Human_CanPush (0x002320a0) refuses when either is in a fight stance, holds any of the held flags 0x2fefefff or
any state of 0x7bf9e9f7ff0; otherwise it swaps the two places: each human's transform is set to the other's
position with its own rotation (vtable +0x6c), so a player who keeps walking into a free teammate at a jog or
faster for 2 s ends up on the other side of him (Combat: teammates). Confirmed (code) at
0x00219b08; the swap was seen at runtime with the timer aged by hand (Combat: teammates).
The mover's own velocity is cut by the slide only when it is a player (Sweeping a body).
The shape-pair tables¶
Two 5 × 5 tables of functions at IPhysics +0x18 (overlap) and +0x7c (sweep), indexed [moving type][other type]
(row × 0x14 + column × 4), and three per-type tables against the level mesh and rays. An empty entry means the pair
never touches. Filled by IPhysics_Construct, confirmed (code):
| Moving \ other | 1 box | 2 sphere | 3 capsule | 4 segment | Evidence |
|---|---|---|---|---|---|
| 1 box | overlap 0x00345dd8; sweep 0x003451e8 |
0x00346680; 0x003454c0 |
0x00346d18; 0x00345108 |
none; 0x00345590 (never) |
confirmed (code) |
| 2 sphere | 0x00346db8; 0x003453f8 |
0x00345d58; 0x003440d0 |
0x00346f90; 0x00345670 |
0x00346eb0; 0x00344320 |
confirmed (code) |
| 3 capsule | 0x00346808; 0x00344618 |
0x00346ed8; 0x00345910 |
0x00346388; 0x00343cd8 |
0x00347148; 0x00345d18 (overlap) |
confirmed (code) |
| 4 segment | none; 0x00345588 (returns 0) |
0x00346de0; 0x00344538 |
0x00346fb8; 0x00345d38 (overlap) |
0x00346500; 0x00344300 (overlap) |
confirmed (code) |
| Per type | 1 box | 2 sphere | 3 capsule | 4 segment | Evidence |
|---|---|---|---|---|---|
mesh sweep +0xe0 |
0x00348530 |
0x00348a50 |
PhysicsMesh_SweepCapsule 0x00347c08 (one sphere, Characters) |
none | confirmed (code) |
mesh overlap +0xf4 |
0x003473b8 (returns 0) |
0x003473c8 |
0x003473c0 (returns 0) |
none | confirmed (code) |
ray +0x108 |
0x00345b90 |
none | 0x003459f0 |
none | confirmed (code) |
Mirrored entries call the other order and swap the contact's halves. A segment's "sweep" against a capsule or a
segment is the static overlap, which writes no fraction, so such contacts are sorted by stack leftovers
(Unit 11): segments move with their bone and are tested where they are, which is how
strikes
work (Combat). A segment never touches a box, and no sphere or segment is ray-tested.
Strikes go through the sweep table (Human_TestStrikes 0x0033f110 sweeps each strike shape from its last pose
to its current one), so of a human's strike shapes only the spheres (hands, head, feet, a weapon's spheres) can
reach a car's or an object's box; the forearm, shin and spine segments never do. Confirmed (code)
(Who reaches a car, Unit
5).
Mesh tests¶
The mesh functions read the level's collision mesh through the world manager (+0x40 level object,
+4 mesh). Confirmed (code):
0x00347170starts a query: it clears the mesh's per-triangle "visited" bits (+0x94, one bit per triangle,+0x84triangles) so a triangle shared by several grid cells is tested once, and turns the world box into the mesh's frame to pick the cells.- For each enabled triangle of those cells the test fills a working triangle (corners
+0x00/+0x10/+0x20, edges0x00343a90, edge normals0x00343b20, unit normal+0x70by0x00343c30) and turns a two-sided one to face the shape (0x00343c90; for a box, the box's centre decides the side, and the box sweep stores no plane constant at+0x80, Unit 13). - Sphere sweep (
0x00348a50):Sweep_SphereTriangle(0x0034ee60); box sweep (0x00348530):Sweep_BoxTriangle(0x0034d5d0); sphere overlap (0x003473c8):SphereTriangle_Penetration(0x0034edd0). Hits become 0xb0-byte mesh contacts, sorted by fraction (0x00347300) and copied out.
Geometry kernels¶
The pair and mesh functions above call these. Their roles are confirmed (code) by their callers and arguments;
the exact algebra is in the units below. A correct implementation of the same test gives the same contacts except
where a unit names a quirk: Sweep_BoxTriangle's zero-speed reset gives fractions a correct test does not
(Unit 13).
| Address | Name | Test | Evidence |
|---|---|---|---|
0x00349268 |
Sweep_SphereSphere |
moving sphere against a sphere (also ObjectRender_Draw) |
confirmed (code) |
0x00349488 / 0x00349560 |
Ray_ClipSlab / Ray_OrientedBox |
a ray against an oriented box, slab by slab | confirmed (code) |
0x003498c0, 0x0034a8d0 |
Sweep_CircleCircle2D, Sweep_IntervalOverlap |
the capsule-capsule sweep: horizontal circles, then the vertical ranges over time | confirmed (code) |
0x00349a60, 0x00349180 |
Sweep_SphereCapsule, Vec_MakePerpendicularBasis |
a segment against an upright capsule with hemispherical ends; the kernel has no moving radius (a sphere's is folded into the capsule's, a ray passes the capsule's own, Unit 14) | confirmed (code) |
0x0034a318, 0x0034a6d0 |
CapsuleBox_SweepCorner, CapsuleBox_SweepSide |
the capsule-box sweep, four corners and four sides: a circle against a rectangle in the box's plane, with no vertical test (Unit 12) | confirmed (code) |
0x0034a2d0, 0x0034a6a0 |
CapsuleBox_CornerCircle, CapsuleBox_SideInterval |
the capsule-box overlap | confirmed (code) |
0x0034ac08, 0x0034acc8, 0x0034ad50 |
Segment_ClosestParam, Segment_ClosestPoint, Segment_ClosestPoints |
point-segment and segment-segment closest points | confirmed (code) |
0x0034af50, 0x0034afd8 |
Ray_SphereRoot, Ray_CircleRoot |
the first root of a ray against a sphere (3D) or a circle (2D) | confirmed (code) |
0x0034bbe8 and 0x0034b040, 0x0034b3b8, 0x0034b600, 0x0034b7b0, 0x0034baf0 |
Sweep_SphereOrientedBox and its corner, edge, edges, face-edge and frame steps |
moving sphere against an oriented box (also the camera's tilt over obstacles) | confirmed (code) |
0x0034c430 |
Sweep_BoxBox |
moving oriented box against an oriented box: only whether they meet, no fraction (PhysicsSweep_BoxBox writes fraction 0, Unit 13) |
confirmed (code) |
0x0034d5d0, 0x0034aa60, 0x0034ab28 |
Sweep_BoxTriangle, BoxTri_ProjectBox, BoxTri_ProjectTriangle |
moving box against a triangle, axis by axis | confirmed (code) |
0x0034ee60 (0x0034ee40 calls it) |
Sweep_SphereTriangle |
moving sphere against a triangle: the face, then an edge (0x0034e3b8) or a corner's region (0x0034e5d0); what it does with a sphere that already overlaps is on Characters |
confirmed (code) |
0x0034e3b8, 0x0034e5d0, 0x0034dff8, 0x0034d938, 0x0034e120 |
SphereSweep_Edge, SphereSweep_VertexRegion, SphereSweep_EdgeCylinder, Ray_InfiniteCylinder, Segment_SphereIntersect |
its edge steps: an edge already within the radius (fraction 0), else the edge as a cylinder; past a corner, one of the corner's two edges | confirmed (code) |
0x0034e840, 0x0034edd0 |
PointTriangle_DistanceSq, SphereTriangle_Penetration |
the distance of a point to a triangle; a sphere's depth into it | confirmed (code) |
How things actually move¶
None of this is in the step; it is listed so an implementer knows where each motion lives. Confirmed (code):
- Humans sweep their body in
Human_Moveat 30 Hz (Characters). The physics world gives the shape and the sweep; no body pushes another here. - World objects integrate in their update: by default
Task_Integrate(Tasks), explicit Euler with dt = phase time − time of the last update (0x003a1688), so dt is the wheel interval. Airborne and with a body (0x00395a10), velocity first:vz −= 15.68 × dt, thenPhysicsBody_Sweepwith that velocity, then the rotation by the angular velocity unless settling. On the update after a human lets go (+0x10cset, holder at+0x11c), the object starts at the holder's+0x5f0with velocity from the holder's+0x600and that first sweep uses dt = 1/60 s.+0x10cis set only for a throw released from the aiming state (Objects: throws). The two are the thrower's aim, written every frame of the aiming state byHuman_TraceThrowAim(0x0018fee0, Graphics draws its arc):+0x5f0is the release point, a fixed offset turned by the human's rotation (+0x620) and added to his position (+0x610), and+0x600the throw velocity (Human_ComputeThrowVelocity) turned the same way. Confirmed (code). - While airborne or settling (
0x4040000, or body flag0x800), an object's interval is 2 ticks (0x00395b70): it flies at 30 Hz, like the humans. - A thrown object hitting a human is decided in its contact handler, from its own sweep: the hit object is
tested as a human (
0x00229868) and handled byThrownObject_HitHumanTest(0x003928d0) andThrownObject_HitHuman(0x00392b88): damage, reaction and wear are in Objects: throws.
Cars¶
A car's collision is one body (IPhysics_CreateCarBody) with two boxes from the car pool, built by Car_MakeBodies
(0x00387d50) from the type record (0x0057e4c0 + 0x5f0 × type, Cars): box 1 has its
half-sizes at record +0x00 and its offset at +0x10, box 2 at +0x20 and +0x30. It sets body flags 0x2211a
and clears the car's object flag 0x200. Confirmed (code). The car never sweeps its body: it is moved with the car
(pose on the next update) and is what objects and the camera meet. A walking human does not meet it: his layers
leave the car's body out, and he meets and climbs the car's shell in the level's mesh (layers,
Characters). When a car explodes, box 1 loses 0.25 m of height (Cars).
Car_OnLanded (0x00389e08) answers 2 to every contact; on the level it also
clears the airborne flag, zeroes the velocity and sends message 0x3f (kind 5) with the point (0, 1.84, 0.02) in the
car's
frame to the task whose handle is at [0x005971b4] + 0x14: the shared sub_car_damage, which makes a sub_hood_smoke
there (Cars: hit effects).
Loose parts (Car_UpdateLoosePart, 0x00387f18, from the car's update for each part flying off, bit in +0x11f4)
have no body. Confirmed (code):
- The part's lifetime (
+0x94, 360 at the start) counts down once per update; below 91 it sends message0x15once to the object held in+0x8c; at 0 the part is removed (+0x11f0, and its linked part) and stops flying. - Otherwise, while flying (
+0x96bit 0):vz −= 15.68 × dt(the same gravity as objects), then a box of the part's size (part record+0x10-+0x18, records at type record+0xf0, 0x30 bytes each) at the part's matrix is swept byv × dt(IPhysics_CollideShape, option0x400). - With no contact the part moves by
v × dt. With one it backs off the plane (PhysicsVec_BackOffPlane) and bounces with the restitution at part record+0x20(PhysicsVec_Bounce). On a floor (normal z > 0.7) its spin stops; on the first floor contact (+0x96bit 1 not yet set, then set) it starts turning to lie flat by the smallest angle, eased like a settle but with an acceleration of 0.05 (+0x88) from rest (+0x84). Each floor contact is counted (+0x97); the first three, when faster than 2 m/s, play impact sounds at volume 1 / (count + 1). After any contact the velocity is scaled by 0.95.
The car's update runs every tick while a part flies (Task_SetUpdateInterval), so a piece lasts 360 ticks, 6 s
(inferred from the interval).
30 Hz or 60 Hz, and online play¶
What follows is inferred from the code above.
- Gameplay does not depend on the step. Humans step at 30 Hz, flying objects update at 30 Hz, and hits come from sweeps in those updates. The step only turns landed objects and keeps a broad-phase bound.
- At a 30 Hz step, the settle must be rescaled to keep its 183 ms: with acceleration 0.08 per step², it ends in 6 steps (200 ms). The bound refresh becomes every 60 steps. Neither changes play.
- Online, nothing the step holds needs syncing beyond an object's settled state: given the landing normal, a settle is deterministic and cosmetic, so a client can play it locally or take the object's rotation from the server. The state that matters is a flying object's position, velocity and holder, and the humans' state, all outside this step. The physics is the game's own short, fixed sequence of float operations, with no third-party solver to make deterministic.
Reimplementing Physics, RayCast and World¶
Every function of Physics/ (0x0033c288-0x0034f740) and every function of RayCast/ and World/ that Coney
does not cite yet, split into units an implementer can take one at a time, bottom-up. Each unit lists its functions
(names and sizes from the shared Ghidra project; "tagged" means docs/progress/functions.toml already lists it), what
specifies it, where it goes in Coney and what its tests check. All 19 units are specified exactly, on three
pages: geometry kernels (Units 1, 4, 5, 12 and 13), shapes, bodies and
moves (2, 3 and 6 to 11) and rays and the world (14 to 19), with their
arguments, record offsets, constants, branches, the original's quirks and worked test numbers, all confirmed (code)
unless a line says otherwise (read in the disassembly, 2026-10-12). Unit 1's corner contacts and the strike shapes'
previous positions are also confirmed (runtime) (Seen at runtime). Where a unit
corrected a section
of this page, the section was fixed in place; each unit also says what Coney changes.
When this plan was made (2026-10-12, main at f325e018), the tracker had 17 of Physics/'s 183 functions, 12 of
RayCast/'s 15 and 33 of World/'s 61. RayCast/'s mesh code (src/raycast/collision_mesh.*) is complete apart
from Unit 16, which also changes its ray: it skips excluded triangles before intersecting and returns only a kept
hit, so it cannot give the see-through material a clear ray crossed
(WorldManager_RayCast);
World/'s streaming and drawing are done, and its open functions are lifecycle (Units 17 to 19).
What Coney has instead (the stand-ins these units replace):
- Units 1 to 3 are done (
src/physics/, issue #346): the walking body is the original's swept test and pass loop (Coney's implementation). The airborne body's landing still uses Coney's own segment, notHuman_LandingTest's contact (#369). - Humans meet humans before walls in a pass (a marked Coney choice); Unit 11 sorts both together (#371).
- Strikes test only the square's aimed object attack against a car (issue #72); Unit 5 and the strike loop (How Human_TestStrikes builds the sweep) let any punch or kick whose sphere reaches a box hit it.
- A falling object's move is a ray from its centre (
world_objects::LooseObjects); Unit 13 is its box sweep.
Order and dependencies. Units 1, 4, 5, 6, 16 and 17 depend on nothing else here. Then:
| Unit | Needs | Unblocks in Coney |
|---|---|---|
| 1 Triangle sphere-sweep kernels | exact edge and corner contacts for every sphere against the level | |
| 2 A shape against the collision mesh | 1 | the walking sweep's contacts, the corner slide, the airborne push-out |
| 3 Moving a body | 2 | the pass loop, contact codes, slides, bounces, push-outs, the "could not move" counter |
| 4 Static shape-pair overlaps | pick-ups, the push sphere, objects touched by humans | |
| 5 A sphere swept against a box | strikes on cars (issue #72, step 1) and objects' boxes (issue #300) | |
| 6 Shapes | posed shapes and bounds for 7 to 14 | |
| 7 Bodies and the sort-and-sweep world | 6 | the candidate queries every test starts from |
| 8 Ignore lists and shape pools | 7 | |
| 9 Creating and freeing bodies | 7, 8 | one body per human, object, car and pane, with the original's flags |
| 10 The human body set | 6 to 9 | strike shapes on bones, the push sphere |
| 11 Moving shapes against bodies | 1, 6, 7 | humans and walls in one sorted pass |
| 12 A capsule swept against a box | 6 | walking into an object's box |
| 13 Box against box and triangle | 2, 6 | falling and thrown objects' boxes, cars' loose parts |
| 14 Rays against bodies | 6, 7 | the AI's line tests against bodies |
| 15 The physics world | 3, 7, 9 | one IPhysics per level, the step and the 64 settle slots |
| 16 RayCast leftovers | material names, the world manager's ray | |
| 17 Level file and level object | the level object's destructor order, the WAD entry size | |
| 18 World manager lifecycle | 17 | one WorldManager owning the level's pools (the budget comes back on unload) |
| 19 Streamed world object lifecycle | 18 | the two-world visibility marks, a pending atomic release |
A unit is done when its functions are tagged in Coney (@orig, functions.toml), its tests pass on synthetic
geometry, and the stand-in it replaces is gone or marked.
Floating point. The PS2's FPU and VU0 have no infinities or NaNs: a division by zero gives ±FLT_MAX, and
rounding is toward zero (inferred: a hardware fact, not read in these functions). An IEEE implementation matches to
about one unit in the last place, so tests compare fractions with a tolerance (1e-5 is ample), and the few edge
cases that divide by zero say what the original then does.
Coney's implementation¶
Coney has Units 1 to 3 (src/physics/) and placed world objects (src/world_objects/), but no IPhysics and no
bodies of its own yet. A dropped object's fall, bounces, settle and stop are
world_objects::LooseObjects (repo:src/world_objects/loose_objects.h), stepped by the play mode: each 1/30 s step
runs the object's update, then two settle ticks, so the settle keeps its 11 ticks. Its move is a ray from the body's
centre against the level mesh, as long as the move plus the box's or sphere's reach that way; humans, cars and other
objects are not met yet. A thrown object starts the same way with its throw's velocity and spin
(World objects).
The world objects in the world have their bodies (world_objects::ObjectBodies,
repo:src/world_objects/object_bodies.h): each type's box or sphere at its object's pose, with its PHYFLAG layers,
remade each step before the humans move. A walking human's sphere slides along the BLOCKHUMANS ones after the
level's walls, as Human_OnContact slides along an object (stand-in: pushed out of the deepest body the move goes
into, in the ground plane; the sum of those pushes is then swept through the level's walls as a push-out; the object
does not move).
The walking body moves through the original's sweep (Unit 3, physics::sweepBody,
repo:src/physics/body_sweep.h) against the level (Unit 2, physics::sweepCapsuleMesh)
with the original's kernels (Unit 1): up to three passes, each sweeping the whole slid move
from where the body is, the capsule's walking sphere against the walls the move goes into, met at the fraction where it
first touches from where the move starts (so a fast move never passes a wall), advanced to 0.01 m short of it along the
normal, the move and the velocity slid, the corner slide added on the ground, and the stuck counter (+0x70, Coney's
m_blockedUpdates) counted. Human::sweep gives it a per-sweep owner (WalkingOwner, repo:src/human/human.cpp) that
answers Human_OnContact's 0x20001 for every level triangle and code 0 for the fences a climb passes; the body has
flag 0x400 for a player only, so only a player's slid velocity is written back. While airborne the same sweep takes
the move across the ground (no corner slide; a player keeps a quarter of the slid speed, the human contact scale), and
the air sphere's push out of the nearest wall (physics::pushOutOfWalls) is added to the pending push for the next
move, as Human_PushOutInAir does. What differs, each marked in the code:
- Coney's choice: the human has no physics body between updates; each sweep builds one from the human's own
fields (the pending push is body
+0x50, kept with flag0x1000since Coney divides it by the push weight when it is added). - Coney's reading: the sweep is given the slope-scaled velocity, and the scale is taken off the velocity it writes back (which of the two the original writes back is not on this page).
- Coney stand-in: a capsule body's query box is its walking sphere's bounds, not the capsule shape's (Unit 6); the visited bits live beside the mesh, not in it.
- Not yet: the airborne body falls and lands by Coney's own landing segment (
Human::moveInAir), not byHuman_LandingTest's contact in the sweep (#369;physics::landingContactis written and tested).
The shape-pair code of Unit 4 and Unit 5 is in
src/physics/, as functions of posed shapes (physics::Box, Sphere, Capsule, Segment in
repo:src/physics/shapes.h) that fill a contact record (physics::Contact, repo:src/physics/contact.h, laid out
as the 0xc0-byte record's fields): the static overlaps and their table (repo:src/physics/overlaps.h), the segment
and capsule-box kernels (repo:src/physics/segment_kernels.h) and the sphere-box sweep
(repo:src/physics/sphere_box_sweep.h), with every quirk the units list kept and pinned by a test. Nothing calls
them yet: the strike loop (issue #72, step 1) calls physics::sweepSphereBox with a car's or an object's box
(physics::boxAt turns a body's pose and half extents into one), and the overlap query (Unit
7) calls physics::overlapShapes. Coney's choices, each marked in the code:
PhysicsOverlap_BoxCapsulewrites the capsule-side normal (the box normal negated) into+0x50, which the original leaves holding whatever the stack held.- The sphere-box sweep's direction to the box, and the inside case's normal, are (0, 0, 0, 1) for a sphere straight above or below the box centre, where the original normalises a zero vector.
- The ray roots are computed as C / (D − B), the same root as the original's form, and exact at a tangent where the original divides by zero.
PhysicsOverlap_BoxBoxkeeps the original's wrong matrix (a maintainers' choice in Open questions; no caller pairs two boxes).
Humans meet each other as Human contacts describes (human::sweepCapsules and
human::contactPush, repo:src/human/human_contacts.h; Human::meetHumans). The characters' step
(human::Humans::update) gives every human the step's list of humans, and a walking human's sweep first meets their
upright capsules (0.35 × scale, 2 m tall): the nearest contact stops the capsule there and the rest of the move
slides along the other human (code 1), and, unless the mover charges, the one met gets half of the move into him over
his push weight (1e9 while in a pair or arrested) as a pending push for his own next move (code 4), unless a script
made him unpushable (HuSetPushable(false)). A player's velocity loses its part into the human, as against a wall.
Skipped: a human without BLOCKHUMANS (Human::blocksHumans), out of health or out of the world, and the human this
one holds or is held by (a grab, tackle, mount or mugging), so a linked pair passes through each other. The flag goes
with a knock-out (HuSetConscious(false) or the health running out), a wound and an arrest, and comes back only with
a heal (HuRevive, a flash, a freed cuffed human's revive) or the release from an arrest; a knockdown keeps it, as
in the original (Human contacts, "Who loses BLOCKHUMANS"). What differs, each marked in the
code:
- Coney's choice: the humans take their part of the move before the walls take theirs, where the original sorts both kinds of contact of a pass by fraction together; a capsule already within reach is met at fraction 0 by a move that goes further in, which matches the original's start case across the ground (Unit 11).
- Not yet: an airborne body meets no human (a jump's strike and code 1 against a human); a scene's shared group;
the pushed human's reaction (
Human_OnPushContact's timers and damage).
Open questions¶
- Not yet seen at runtime from the units: the capsule sweep's cell-list overrun after a fraction above 1 (Unit 2). (The late and missed corner contacts were looked for at a post, a planter, a kerb and a ledge and not seen: Seen at runtime.)
- Bits and flags the units read but cannot name: triangle flag bit 3 (
0x8), which a sphere sweep skips for a body with flag0x100or a query with option0x8000(every world object's body starts with0x100in0x80000500, so a flying object's sphere passes those triangles; inferred); how many triangles carry each corner edge in bits 14-15; which states0x00227f68selects for the landing segment's raised feet. Body flag0x1000is set byPhysicsBody_PushOutOfWalls(0x00347b70); other setters are not traced. - Callers not traced:
IPhysics_CollideShape's callersCar_UpdateLoosePart,Human_TraceThrowAimandCamPower_PlaceShot(the others are in Unit 11; none seen passes0x8000); which owner class has type mask0x200, which makes the capsule sweep skip its vertical test; whether anything butIPhysics_OverlapShapereads the overlap table; whether the camera (the other caller ofSweep_SphereOrientedBox) reads its normals, six of which point the wrong way (The normal tables); how a held weapon's strike sphere reachesCar_OnHitas kind 1 (its hit goes to the held object's contact handler). - Names that do not fit (kept, as other engineers gave them):
CollTri_ComputeEdgeNormals(0x00343b20) normalises the edges;Ray_InfiniteCylinder(0x0034d938) is a capsule;SphereBox_SweepCorner(0x0034b040) is the face-region step,SphereBox_SweepFaceEdges(0x0034b7b0) the corner-region step andSphereBox_ToBoxSpace(0x0034baf0) goes from the box to the world. The plate comments say what each does. - From the implementation of Units 1 to 3 (issue #346): Unit 3's test 13 gives
PhysicsVec_Bounce((0, 0, −2), (0, 0, 1), 0.1)as(0, 0, 2.2), but the formula above it (v −= n d (1 + e)) and test 12 (−5 to 0.5) give(0, 0, 0.2); Coney follows the formula. Which velocityPhysicsBody_Sweepwrites back to a walking human on a slope: the slope-scaled oneHuman_Move(0x0023d8c8) hands it, or the unscaled one (Coney takes the scale off). Two guards Coney had to choose: a zero-length edge inRay_InfiniteCylinder(treated as the sphere at its first end, inferred from1/0on the PS2) and a move parallel to an edge's side plane inSphereSweep_VertexRegion(decided by the signs the PS2's±FLT_MAXcrossing would have, inferred). -
A maintainers' choice:
PhysicsOverlap_BoxBox(0x00345dd8) builds its rotation matrix the wrong way round with the centres mixed in, so it misses rotated or distant boxes; no caller pairs two boxes, so reproducing it or writing the correct test changes nothing a player sees (Unit 4). -
Hangout railing vault (checklist item 46: the vault plays but the player stays put): not explained by the door flags above, which only doors and panes write. The climb's pass-through (materials 30, 31 and 122 ignored while record
+0x08has0x40, Characters) and the railing triangles' material inlevel95.levare where to look next; not traced in this pass. A lead: of the clubhouse's triangles (z between −200 and −185 inlevel95.lev) only 2 are material 122 and 7 material 31, against 137 of 7 (LAMPOST) and 56 of 3 (STEEL), so a railing built of another material keeps its wall during the vault unless the original passes it some other way. -
How an object of a type with
axis0 comes to rest: nothing in its contact handler clears airborne, so it would keep bouncing at interval 2 (inferred); the throwable ones (dyn_lawnchair_a,_b) break on their first contact (runtime).