Skip to content

World objects: spawning, models, tint, glass and doors

Verified against: SLUS_212.15 (NTSC-U, SHA1 e9cb2cc49aa046b9e494313dce2f5038ed17b2f4), static analysis only (Ghidra), a disc check (2026-10-06) of what the scripts pass and of the Object List and the WonderWheel_100 scene, reported as counts and values, and, for the objective markers, held objects and trash props only, PCSX2 2.9.94 over PINE in level99 (2026-10-06, -07).

Purpose

What level scripts set on the world they build and what the player breaks or opens in it: how an object a script spawns finds its model, where it stands and what moves it (Dynamic objects, with the front end's Wonder Wheel as the worked case), an object's tint, the objective markers (the "W" a mission asks the player to walk into), the breakable glass panes, the doors and the breakable barriers. The lists are Object tints, Glass types and Doors; the classes and pools of world objects and glass panes are on Tasks, the crimes a pane can raise on AI: crimes, and what the player's attacks do to them on Combat: breakables.

Original structure

The code sits in TaskEngine/, in the stretches placed by position (Source map). Names are ours. The script types (the table at 0x00512f28, Particles) involved: glass_script (61), sub_glass (210), sub_stained_glass (247), glasstest (62, the shards), dyn_door_swinging (32), sub_swinging_door (249, a leaf), dyn_door_sliding (30), sub_triglint (254), and the barriers dyn_door_fence (27), dyn_door_bar_bani (23), dyn_door_bnstr (25), dyn_door_chain_s (26), dyn_door_fence_o (28), dyn_door_parapet (29). Confirmed (code): each index read from the table's name pointer.

Address Name Role Evidence
0x00390f18 Cfg_AddObjectType CfgObj: one 0x90-byte object type (Object types) confirmed (code)
0x003913d8 ObjectDb_FindByName a type's index by name (a hash table over the names) confirmed (code)
0x00396858 Obj_Spawn ObjSpawn: the unlockable checks, then a spawn record confirmed (code)
0x00398940 ObjRecord_Add ObjSpawn's spawn record (Spawn records); returns its handle confirmed (code)
0x00398fe0 ObjRecord_GetHandle a record's object, spawning it first when not live confirmed (code)
0x00399080 ObjRecord_Spawn makes the object from a record; copies the tint confirmed (code)
0x00399428 / 0x003996a0 ObjRecord_Store / ObjRecord_Remove an object back into its record / gone for good confirmed (code)
0x00398df8 ObjRecord_SetPinned record bit 0x10000 and object +0x10f: never stored confirmed (code)
0x00399d88 ObjectTaskManager_UpdateSpawns streams records in and out around the cameras (Streaming) confirmed (code)
0x003918b8 WorldObject_Init the world object's initialiser: type, script type, defaults, body confirmed (code)
0x003a44c8 Obj_SetModel(object, hash) the model by Object List hash (0: the type's) confirmed (code)
0x001811b0 ObjectList_FindByHash an Object List record by name hash confirmed (code)
0x001808b8 / 0x001809c0 ObjectModel_IsLoaded / ObjectModel_Request a record's three resources resident / requested confirmed (code)
0x00180f58 ObjectModel_MakeInstance a model instance over the record's clump and dictionaries confirmed (code)
0x001897a8 ResourceManager_LoadNearestModel per frame: loads one object's model that is missing confirmed (code)
0x003eeea0 / 0x003ef840 / 0x003ef188 simple_object init / update / message the plain prop (simple_object) confirmed (code)
0x0017fd78 ObjectRender_Draw draws one object instance: lights, size cull, fade confirmed (code)
0x0017fa80 ObjectRender_ApplyFadeDistance the alpha of an object near its ObjShow distance confirmed (code)
0x00395b70 WorldObject_Update integrates, copies +0xcc to +0xc8, runs the class's update confirmed (code)
0x003e98e8 / 0x003e9b08 / 0x003e9be0 dyn_objective init / update / message an objective marker's disc (Objective markers) confirmed (code)
0x003e9828 ObjectiveMarker_SetShown a marker's shown state, passed on to its column confirmed (code)
0x003e9d60 / 0x003e9fd8 / 0x003e9ea0 sub_objective_column init / update / message the marker's column confirmed (code)
0x003a1810 / 0x003a1858 Obj_Attach / Obj_Detach parent, bone and local pose; detaching keeps the world pose (Held objects) confirmed (code)
0x003a19b0 / 0x003a1ad8 Obj_GetWorldPose / Obj_GetPoseNow vtable function words +0x9c / +0xa4 (slots +0x98 / +0xa0 of 0x005453a0 and the car's 0x00544c08): an attached object's pose through its parent's bone confirmed (code), runtime
0x003fe490 / 0x003fe6b8 MeleeWeapon_Take / MeleeWeapon_Detach messages 0x1b / 0x1c of melee_weapon confirmed (code)
0x00257f38 / 0x002586d8 Human_DropHeld / Human_ReleaseThrow a held object let go: dropped / thrown confirmed (code)
0x00221ed0 Human_SetCarryMoveStyle human message 3: pushes the held object's anim set as a move style (Two-handed objects) confirmed (code), runtime
0x00253688 Human_ApplyMoveStyle a move style's anim slot overrides; style 4 is the two-handed carry confirmed (code), runtime
0x003ff918 / 0x003c0108 OverheadWeapon_OnPickedUp / DynCashreg_OnPickUp message 0x1b: attached at the event's bone with the type's hold pose (CfgObj holdPos / holdRot) confirmed (code), runtime
0x003a3978 ScriptObj_GetVectorProperty property 11 / 12: the type record's hold position (+0x00) / rotation (+0x0c) confirmed (code)
0x003a5530 ScriptObj_SpawnThrownDebris a broken prop's piece: created by name at the prop's pose, knocked with message 0x30 (Trash props) confirmed (code), runtime
0x003a53f0 Obj_SpawnChildByName creates an object by name at an object's pose, attached to it confirmed (code)
0x00396bd0 Obj_SetColour ObjColor: packs {r, g, b, a} into the tint word confirmed (code)
0x0038fab8 GlassTypes_Set CfgSetGlassProperties: one entry of the glass type table confirmed (code)
0x0039c0e0 Glass_Spawn pushes the pane's arguments, creates it by type name confirmed (code)
0x0038f8a8 GlassManager_Create allocates the pane, runs its initialiser, the window link confirmed (code)
0x003e29e8 / 0x003e3058 / 0x003e2d90 glass_script init / update / message the pane confirmed (code)
0x0038f378 Glass_Break(pane, breaker, object) the alarm, the window link, the window flags confirmed (code)
0x003e4be0 / 0x003e4cb8 sub_glass init / update the shatter: sound and shards confirmed (code)
0x003e44e0 / 0x003e4838 GlassTest_Init / GlassTest_Update a shard (glasstest) confirmed (code)
0x0038f1c8 GlassPane_UpdateBodyByDistance per frame (vtable +0x13c): the pane's body within 50 m confirmed (code)
0x0038ed68 / 0x0038eec0 GlassPane_CreateBody / GlassPane_RemoveBody the pane's collision body (+0xe8) confirmed (code)
0x0033d070 Physics_CreateBoxBody a static box body in the 255-body pool, flags 0x8000007a (the pane's) confirmed (code)
0x0033f110 Human_TestStrikes per update, after the move: the human's switched-on strike shapes against nearby bodies confirmed (code), runtime
0x00219d50 Human_OnContact the human body's contact handler; an airborne body touching a pane breaks it (Moving into a pane) confirmed (code), runtime
0x0038fa50 → 0x0038ef60 GlassManager_QueueDraws → GlassPane_QueueDraw per frame: each pane into its sprite batch or the near list confirmed (code)
0x0038f090 → 0x001831c0 GlassPane_DrawNear → Instance_DrawOneSpriteIm3D a pane near a camera, drawn on its own confirmed (code)
0x00185b38 ResourceMgr_Render3DSprites the 3D sprite pass (Drawing a pane) confirmed (code)
0x003966c8 → 0x003963b8 World_BreakGlassInRadius BreakGlassInRadius confirmed (code)
0x00396390 → 0x003961d0 World_BreakObjectsInRadius BreakObjectsInRadius confirmed (code)
0x00397230 Door_Spawn pushes the door's arguments, creates it by type name confirmed (code)
0x003fb5f8 / 0x003fbba0 / 0x003fb8d0 dyn_door_swinging init / update / message the swinging door confirmed (code)
0x003f80f0 DoorSwing_SetUpType per type name: leaf models, sounds, flags confirmed (code)
0x003f8fd0 DoorSwing_SwingTo turns the leaves to the angle at data +0x00 confirmed (code)
0x003f9368 DoorSwing_ResetLeaves puts the leaves back in the closed pose confirmed (code)
0x003f9770 DoorSwing_StateCommand message 0x22: open, close, collision, pickable confirmed (code)
0x003f9910 DoorSwing_OpenBy message 0x0b: a human opens it, away from them confirmed (code)
0x003f9588 DoorSwing_SetPickable the lock-pick glint confirmed (code)
0x003f9ad0 / 0x003fa438 DoorSwing_Hit / DoorSwing_HitBreakable message 1 confirmed (code)
0x003fb330 / 0x003fb5a0 / 0x003fb430 sub_swinging_door init / update / message a leaf confirmed (code)
0x003a2310 / 0x00336a00 Task_Integrate / Quat_Slerp a leaf's rotation step and its in-between pose (Leaves) confirmed (code)
0x003b2f40 / 0x003b3220 / 0x003b3158 / 0x003b2180 dyn_door_fence init / update / message / hit a breakable barrier confirmed (code), runtime
0x00391c10 WorldObject_Remove vtable +0x4c: message 2 to the object's handlers, out of its record, deleted (Barriers) confirmed (code), runtime
0x00250c00 NavLinks_SetKindByNumber(number, kind) retags every navigation link carrying a number confirmed (code)
0x00250c50 / 0x00250d00 NavLinks_OpenByNumber / NavLinks_CloseByNumber bit 31 of every link with a number, path flag 8 of its door hole confirmed (code)
0x002508b8 NavLink_DoorPolygon(link) the hole at the middle of a door link confirmed (code)
0x00250100 PathPolygon_FindAtPoint(pos) the flag-4 hole whose box holds a point confirmed (code)
0x00250960 NavLink_FindNearest(pos, kinds, &link, &back) the nearest link of a kind within 5 m, and its reverse confirmed (code)
0x00417480 Flags_DisableNear(radius, pos, activity, 0) disables flags of one activity near a point confirmed (code)
0x003a5340 / 0x003a52c8 Obj_RemoveBody / Obj_AddBody an object's body in the collision world (0x00597198) confirmed (code); names inferred
0x00117280 Sound_PlayMaterialPair(a, b, pos) the sound matrix entry of two materials (Sound) confirmed (code)
0x003a6ee8 Sound_PlayHashAt(hash, pos) a 3D sound by name hash confirmed (code)

Data

Object types

CfgObj fills one 0x90-byte record of the object database (0x00512c04, records from its start, a count at +0x34bc0; the binding's fields are on CfgObj). Confirmed (code) at 0x00390f18, 0x00391778, 0x003917a0, 0x00391340:

Offset Meaning
+0x28 name, up to 26 characters (dyn_s_wwheel_a): the name scripts spawn and the task is created by
+0x43 class name, up to 20 characters (simple_object)
+0x5c the class's index in the script type table (Particles), or −1
+0x60 the record's own index
+0x8c CRC-32 of the name (the standard table at 0x005d91e0, as typed: the names are lower case): the Object List key of its model

The other fields (+0x58-+0x88) are the binding's arguments. A name is found by ObjectDb_FindByName (0x003913d8): a hash table at +0x34dfc keyed by h = 5h + c over the characters. Confirmed (code).

Spawn records

ObjSpawn does not make an object: it adds a spawn record (0x28 bytes) to the ObjectTaskManager's array (+0x14, count +0x18; CfgSetDatabaseSizes objects + 500 of them) and returns a handle naming the record: its index << 16 with serial 0 (Tasks: handles). Confirmed (code) at 0x00398940, 0x00399080, 0x00399428:

Offset Meaning
+0x00 rotation: four s16, the quaternion × 4,096 (0x004f3c70 packs, vitof12 unpacks)
+0x08 position x, y, z
+0x14 tint word (The tint)
+0x18 u16 the flag (marker) named by flagName, 0xffff for none
+0x1c parent handle (none: NilHandle)
+0x20 the type's index in its low 14 bits, the object zone in its top 10 (<< 22)
+0x24 low 16 bits: the live object's handle index (0xffff none); bit 0x10000 pinned, 0x20000 live, 0x40000 removed (never spawned again), 0x80000 ObjSpawn flag 2, 0x100000 stored hidden, 0x1000000 ObjSpawn flag 64, 0x400000 / 0x800000 a door's left / right leaf; bits 26-29 a state handed to the object on spawn (6: none, the value a new record gets)

When a record is stored (Streaming) the object's current pose and tint are written back, so it comes back where it was left.

The tint

A world object's tint is a colour word at +0xc8, copied at +0xcc: 0xRRGGBBAA read as a little-endian word (bytes a, b, g, r in memory). Confirmed (code):

  • ObjSpawn's seventh argument is stored in the spawn record (+0x14, 0x00398940) and copied to the object when it spawns (0x003992a4). The default, 0xFFFFFFFF, is white: no tint. The scripts pass 33 others, all with alpha 0xFF.
  • ObjColor(object, {r, g, b, a}) multiplies each component by 255, and the packer (0x0017aca8) multiplies by 255 again; the soft-float conversion (0x0042c718) keeps the low 8 bits. Since 65,025 is 1 modulo 256, a whole component 0-255 comes out as itself and a 0-1 component does not (1.0 gives 1). The one script that calls it passes 0-255 values.
  • HuSetSpinningIconColor(human, steps, colour) sends message 0x34 to the human's spinning icon (0x00238b18), which fades its tint word to the colour over the steps (Characters: the spinning icon); no script calls it.
  • The breakable doors write the word too: a broken dyn_door_liz, dyn_door_dclub or dyn_door_stall clears its alpha byte (+0xcc = +0xc8 & 0xffffff00, 0x003fa438).

Drawing (ObjectRender_Draw, 0x0017fd78, confirmed (code); the copy also confirmed (runtime)): each update WorldObject_Update (0x00395b70) copies +0xcc into +0xc8, so a class that writes +0xcc sees it take effect one update later. The model instance holds the word as bytes r, g, b, a (instance +0x24-+0x27, equal to +0xc8 in PCSX2), and the draw sets them as the colour of the instance's geometry before rendering it: the tint multiplies the model's own colours and its alpha the model's opacity. The alpha is first scaled by the size fade (The model), the ObjShow distance (below), a fade-in over the first second after the instance appears (instance +0x2c; skipped while object flags 0x800010 has a bit set) and the camera fade (×0.3 for an object between the camera and the player, instance +0x34, eased over 200 ms); an alpha under 10 is not drawn. While the camera fade is below 1 the draw turns z-writing off (RenderWare render state 8) for that object.

The ObjShow distance (+0x138, ObjectRender_ApplyFadeDistance 0x0017fa80): when it is above 0 and the camera is farther than the distance − 2 m, the alpha is multiplied by (distance − camera distance) / 2, fading the object out over its last 2 m. An attached object (flag 0x10) uses its parent's distance and is not drawn beyond it. Confirmed (code).

Glass types

The GlassTaskManager (world object 0x00512c7c +0x84c) keeps a table of 16-byte entries at +0x14 + 16 × type, confirmed (code) at 0x0038fab8, 0x0038fb00-0x0038fb10:

Offset Meaning
+0x00 window link (CfgSetGlassProperties' second argument)
+0x04 alarm (third)
+0x08 the whole pane's sprite word (fourth): the pane's sprite rectangle is its low 16 bits (Task_SetRect, Particles)
+0x0c the broken pane's sprite word (fifth); 0 hides the pane once broken

config_preload2.lua configures types 0-18.

The pane task (0x100 bytes), confirmed (code) at 0x003e29e8:

Offset Meaning
+0x10 centre: the first corner + half of each edge
+0x54 flags: 0x8001 at spawn; 4 hidden (also set while no camera is within 50 m), 0x800000 broken
+0x70 W, the width edge: third corner − first corner (SpawnBreakableGlass' cornerV − corner)
+0x80 the unit normal, normalise(H × W)
+0x90 H, the height edge: second corner − first corner (cornerU − corner)
+0xa0 centre (copy); +0x70-+0xaf is the matrix the pane is drawn with (Drawing a pane)
+0xb0 sprite word: batch 0 in the high half, the type's rectangle in the low half (type 14: its own batch, rectangle 0)
+0xb4 colour 0xRRGGBBAA: 0x808080e0 whole (0 for type 15), 0xffffff70 once broken
+0xb8-+0xc4 the script's two texture-coordinate pairs, u0, v0, u1, v1 in push order; nothing reads them back (inferred: no reader among the pane's methods or the draw)
+0xc8 size word: whole metres of width, height << 16 (truncated)
+0xcc / +0xd4 width / height in metres (floats), |W| / |H|
+0xd0 squared distance from the centre to the nearest player camera, refreshed each frame (0x0038ef30)
+0xd8 / +0xdc the two collision triangles
+0xe8 its collision body, or 0 (vtable +0xf4 returns it)
+0xf8 alarm bits (3 with the alarm, 0x0038ec30)
+0xfa type

The order of the pops (0x003e29e8) against the pushes (0x0039c0e0) gives the corner names. The normal's order (vopmula vf1, vf2 then vopmsub vf2, vf2, vf1 with vf1 = H, vf2 = W, at 0x003e2b6c) is H × W; the draw does not use it. Confirmed (code).

The pane updates every 180 ticks (3 s, Tasks); the update only calls an empty hook for an unbroken type 1 within 100 m (0x003e3058).

Swinging doors

dyn_door_swinging is a frame object with one or two leaves, each a separate sub_swinging_door object. The door task: position +0x10, rotation +0x20 (SpawnDoor's), flags +0x54 = 0x405, triangles +0xd8 / +0xdc, door number +0xe0, hitpoints mirror +0x128 (what GetHitpoints reads). Its data block (vtable +0x18c), confirmed (code) at 0x003fb5f8:

Offset Meaning
+0x00 the angle it swings to, degrees (0 closed)
+0x04 an angle DoorOpenDegree left while the door was pickable
+0x08 hitpoints: the type's CfgObj third argument (+0x58); message 0x19 sets it
+0x0c / +0x10 open and close sound (name hashes)
+0x14 / +0x18 left / right leaf handle (GetLeftDoorHandle, GetRightDoorHandle; message 0x10)
+0x1c 1 once the leaves are registered with the object manager
+0x20 pickable (the lock-pick door)
+0x24 the pickable glint's handle
+0x28 state byte, below

Per type (0x003f80f0, by name; leaves are the dyn_dr_* objects). A second leaf stands at position + rotation × (−2 × w, 0, 0) turned 180° about the vertical, where w is one leaf's width, CfgObj argument 15 (Leaves); the leaf objects get +0x124 = 2 (left) and 4 (right). Confirmed (code):

Door types Leaves Open / close sound
dyn_door_big_gate, _cemgate, _dblgate, _dblgate_d, _redgate 2 0x43e4b743 / 0x2d88622b
dyn_door_chainlnk_a, _chainlnk_pick, _chainlnk_ag, _ap, _ar, _aw 1 0xca9bdea4 / 0x53928f1e
dyn_door_dblstlwin, _steel_b, _stlslot (second leaf dyn_dr_stlslot), _stlwin 2 0xc09823be / 0x62b026b4
dyn_door_strip 2 0xc09823be / 0xd8831bf0
dyn_door_dblwoodfnce_xl (2), dyn_door_woodfnce_xl (1) 0xa75ac8f5 / 0x4954a9d9
dyn_door_red_fence, _sheetmtl 1 0x34062c42 / 0xb0443866
dyn_door_corr, _ornate_single (dyn_dr_ornate), _stall 1 default
dyn_door_cabin_a/_b/_c, _dclub, _liz, _ornate, _shackdoor, _store, _storeb, _subcan, _temple, _templedoor, _templeshutter, _wood 2 default

The default sounds are 0xecc3ab3c / 0xb70e3ee2. Other dyn_door_swinging types (_dblwood, _woodbrd, _woodp, _steel...) get no leaves. Also per type: _chainlnk_pick, _storeb and _templedoor start pickable; _dclub and _liz retag their number's links to 0x40; the three cabin doors start in state 10 with collision triangle bit 0x800 and a 180-tick update.

Collision: both triangles get triangle flag bit 1 (two-sided), bit 10 (0x400), bit 6 (0x40) when data +0x08 is non-zero at spawn, and the type's material (CfgObj 13th argument, type +0x64) as their material byte (Collision). Confirmed (code) at 0x003fb5f8.

Hitpoints (CfgObj third argument; disc values): dyn_door_store 1, dyn_door_wall_a/_b 3, dyn_door_fence and dyn_door_stall 10, the cabin doors 12, dyn_door_dclub 20, dyn_door_vargas 1, the rest 100.

The level's navigation links are the path data's D records (edges, Level loading): 8 bytes, +0x00 the node, +0x04 the edge flags (u16, the kind) and +0x06 a u16 whose low 13 bits are a door number and whose top bit is the word's bit 31, the "avoid" bit: route planning adds 1,600 to the edge's cost (AI: path planning), so a closed door makes a detour preferred, not mandatory. 0x00510590 is the array, 0x00510594 its count. Confirmed (code) at 0x00250960, 0x00250c00-0x00250d00.

A door's hole: opening and closing by number (0x00250c50 / 0x00250d00, from the door's state commands with the second argument 1, 0x003a4b00) change the avoid bit of every link with the number, walking the D records from the last, and the path flags of one polygon, found from the first link they meet (the highest index):

  1. 0x002508b8 (confirmed (code); the kind mask is 0x50): the link's node (D +0x00), then that node's last link of kind 0x10 or 0x40 (searching its D records from the end), and the middle of those two nodes, so the middle of the doorway;
  2. PathPolygon_FindAtPoint (0x00250100, confirmed (code)) at that point: of all polygons, only those with path flag 4 and at least one vertex whose box holds the point; one such polygon is the answer, several are narrowed by the inside test (0x0024eef0) to the one whose vertex average is nearest, none gives 0 (the original then writes through a null pointer: do nothing);
  3. opening sets bit 3 (8) of that polygon's u16 flags at +0x48, closing clears it.

The polygon is a hole of an area (Path data), never the area's outline, which has no flag 4. Opening it lets lines and routes cross the doorway (AI: path planning). Disc check (level87, door 15): links 1530 and 1537 (kind 0x10, nodes 219 and 220, 1.4 m apart); the middle lies in the boxes of the street's outline (polygon 0) and of polygon 33, a clockwise 4-vertex hole of flags 7, about 0.8 × 2.8 m, the only flag-4 candidate: polygon 33 gets flag 8.

Kind Who sets it How a follower takes it (Following a route)
4 the data 0x0029baa8: faces the waypoint, then either runs or starts a jump (Human_BeginJump)
0x10 the data; ConvertJumpToDoor (4 → 0x10) walked while the door is open; with the avoid bit set the move fails (brain +0x284 = 4)
0x40 breakable doors (by number) and window-linked panes 0x0029bca0: faces the waypoint, sets the move action to 4, and issues the charge (attack kind 19, command 0x20)

So an AI routed through a breakable door or a pane charges it. Confirmed (code) at 0x0029ad04-0x0029adb8; every human searches with the mask 0xff, which admits them (AI).

Behaviour

Dynamic objects

A dynamic object is a world object a script places with ObjSpawn(typeName, pos, rot, -1, zone, flags, tint, flagName): props, weapons, pick-ups and the front end's Wonder Wheel (dyn_s_wwheel_a, its carts dyn_s_wwcart_simple_* and the signs dyn_s_neon_*, Scripts). Its life: a spawn record, then an object made from the record when it is wanted, then a model attached when the model's resources are in memory, then whatever moves it.

Spawning

  1. Obj_Spawn (0x00396858): a name starting dyn_key (7 characters, 0x00581010) is skipped unless unlockable 13 is set, and dyn_powercuffs (0x00581018) unless unlockable 14 is set and game state 0x0041d160 is 0; then ObjRecord_Add with the position, rotation, zone, flags, tint and flag name, the parent NilHandle. Confirmed (code).
  2. ObjRecord_Add (0x00398940) fills the record and returns its handle. Only in level 84, a TYPE_GLASS type is spawned at once. Confirmed (code).
  3. The object appears when something wants it, by either path. Confirmed (code):
    • resolving the record's handle (Handle_Resolve → ObjRecord_GetHandle, 0x00398fe0) spawns it now when it is not live. Any binding taking the handle does this: SceneAddObject (Scene_BindObject, 0x00354280), ObjColor, ObjDestroy ...;
    • streaming (below) spawns it when a camera comes within 70 m.
  4. ObjRecord_Spawn (0x00399080): when the pool is full it first frees a slot (0x003998f0); it pushes the position (w = 1), the rotation (unpacked) and the parent into the message scratch and creates a task by the type's name (record +0x28) in the ObjectTaskManager (Tasks: classes); the record gets the object's handle index and the live bit; the object gets the tint (+0xc8, +0xcc) and the zone (+0x114); record bits then send message 0x12 (0x200000), mark a door leaf (+0x124 = 2 or 4), hide it (0x100000: message 0x3c and object flag 0x800000); a state other than 6 goes to the object's vtable +0x124.
  5. WorldObject_Init (0x003918b8): Task_Init; the type found by name (object +0x112 its index, +0xc0 the type's name); the type's class record of the script type table handed to vtable +0x194; colour words +0xc8/+0xcc = DAT_005fd268; hitpoints +0x128 from type +0x58; the collision body by shape (0x00391d48: shape 1 a box of the type's size, 2 a sphere of radius half its x); a handle (+0xe4). Then the class's own init runs with the pushed arguments (for simple_object, below). Confirmed (code).

ObjSpawn therefore returns at once, and nothing is drawn until the record is resolved or streamed in. In level100.lua the 29 Wonder Wheel objects are spawned when WonderWheelAnim:startScene binds them to the scene (SceneAddObject resolves each handle). Inferred from the paths above.

The level's placed objects (_objs.txt)

InitLevel's step 7 (Level loading), after the level script's main chunk, reads <level>_objs.txt (0x00398598): a count (%d), then one line per object, {name {x,y,z}, {qx,qy,qz,qw}, -1, zone, flags, tint, flagName ) (format at 0x005811f0), the arguments ObjSpawn takes. A name starting part (0x00581228) makes a particle emitter task by that name at the pose instead; every other line goes to ObjRecord_Add directly (no unlockable checks). Confirmed (code); that the fields mean what ObjSpawn's do is inferred from their order.

Emitter lines, confirmed (code) at 0x003987f4-0x0039882c: the loader pushes the position (w = 1), the rotation and a null parent into the message scratch and creates the task by name (Task_CreateParticleByName), which is SpawnParticle's path (Particles: spawning) with no parent; Particle_Spawn only also returns the new task's handle. The line's other fields (−1, zone, flags, tint, flag name) are read but not used, so an emitter is not a spawn record: it is made at once at level load, whatever its zone, is never streamed or stored, and lives until it ends itself or the level is freed. Any distance gating is the type's own: the steam vents (part_steam, _large, _huge) start on with built-in defaults and puff only near a camera (Steam vents); the fly swarms keep their bugs only while a view is near (Flies); the neon types place a sub_neon_light and end; part_strobe_red starts off (no light, its alarm sound emitter disabled) until message 0x12, which a store break-in sends to the nearest task whose type name contains strobe (a substring test, 0x00435d30) within 6 m of the store's flag (Crimes: stores), and then spawns a strober (a red light flashing in four steps) and enables the sound; 0x13 stops both (Small lights and strobes). part_s_shack_dust_puff and part_s_subway_sparks also wait for message 0x12. level99_objs.txt holds 112 lines: 14 emitters and 98 objects; 89 lines name zone 0 (the emitters among them) and 23 zone 26 (the two stores' jewellery and a cash register, flags 2), a zone BNESetup (global.lua, called by level99.lua's RegisterObjects) enables, as it does each of zones 21-32 the level defines. Disc check (NTSC-U, 2026-10-06, counts only); the store's items at runtime on Combat: breakables.

Streaming

ObjectTaskManager_UpdateSpawns (0x00399d88, called from TaskManager_TickGame) walks up to 512 records a call, round-robin from where it stopped (+0x44), and skips everything while the task manager's +0x82c is set. Confirmed (code):

  • In: the nearest record that is not live, not removed, whose zone is enabled (the bit mask at manager +0x20, Tasks) and whose squared distance to the nearest camera (0x00120230) is under 4,900 (70 m) is spawned, at most one a call; when the pool is full, the farthest storable live object beyond it is stored first.
  • Out: a live record not pinned, whose object can be stored (0x00395078: not pinned, flag 0x40000 clear, among checks not traced) and which is farther than the first camera's distance (its vtable +0x214) + 10 m, or whose zone is off, is stored (0x00399428: pose, tint and state written back, the object freed), or removed for good when its own check (0x00399718, objects that fell out of the world) says so.
  • Pinned records stay: SceneAddObject pins its object (0x00398df8, record bit 0x10000, object +0x10f = 1), so a scene's objects are never streamed out while bound. Confirmed (code).

What the first camera's vtable +0x214 returns (the draw distance, inferred) is not traced.

The model

An object's model is the Object List record whose name hash is the type's (CRC-32 of the type name, WAD contents); the class's initialiser asks for it. Confirmed (code) at 0x003a44c8, 0x00180f58, 0x001897a8:

  1. Obj_SetModel(object, hash) (0x003a44c8): releases a model the object had (+0x104); hash 0 means the type's own (CRC-32 of type +0x28); ObjectList_FindByHash (0x001811b0, a linear search of resource manager +0x94, count +0x90). With no record the object has no model and the call returns 0. Otherwise it returns the hash, which the class stores at object +0xc4, and, when the record's resources are resident (ObjectModel_IsLoaded, 0x001808b8), makes the instance at once.
  2. The resources of a record: the model (+0x08, a 0x47 clump resource; sizes +0x14 and +0x18 are what the memory pool is asked for), the texture dictionary (+0x0c, size +0x1c) and an optional second dictionary (+0x10, size +0x20), each looked up by hash in the resource manager's maps (+0x10, +0x30) and reference-counted. That packs, the level file and standalone WAD entries all feed those maps is inferred.
  3. The instance (ObjectModel_MakeInstance, 0x00180f58): a 0x40-byte object (0x0017faf8) over the clump and the one or two dictionaries; 0x00395498 puts it at object +0x104, sets +0x108 = 3 and points the instance back at the object (+0x3c).
  4. When not resident: the object stays without a model. ResourceManager_LoadNearestModel (0x001897a8, from the world manager's service 0x0040f8a0, which the game modes' updates call) takes one waiting human, object or car (handles at resource manager +0xbc4, +0xbd4, +0xbf4, the nearest by 0x00189750, inferred) and, for an object with no instance, requests its record's three resources (ObjectModel_Request, 0x001809c0, which first checks they fit), then attaches the instance once they are in. How the three handles are chosen is not traced.

Disc check (counts and hashes): the eight Wonder Wheel types have records whose model is <name>_geo, no second dictionary and no variant; the three cart types share one dictionary (0x4a9e1bf5) and the four neon types another (0xf2a65bf6), the wheel has its own (0xad06c88d). 33 of the 1,406 records have a second dictionary. Whether level100's packs hold them (resident at once) or they stream in is not checked. The models' own frames are the z-up to y-up turn with an authored translation (the wheel none, the neons (−29.32, 19.94, 0.07), the carts (−78.2, 0, 0)); drawn at their scene poses without that translation, the neons ring the hub and the carts ride the rim as at runtime (Front end), so the instance is taken to replace the frame's translation. Inferred.

Drawing (ObjectRender_Draw, 0x0017fd78, confirmed (code) for what is cited): the lights are chosen per object (LightManager_SelectLights, Lighting); an object whose +0x124 is 0 and whose bounding radius over its squared camera distance is under 0.0004 is not drawn, and fades out between 0.0004 and 0.0005. A dyn_s_wwcart_simple_* (radius about 1.5 m) at the front end's 80 m would be culled by that rule; simple_object gives the Wonder Wheel types +0x124 = 5, which exempts them (simple_object).

simple_object

The class of most props (dyn_s_* and others; record 0x00583a30 of the script type table: init 0x003eeea0, update 0x003ef840, message 0x003ef188, flags 8). Confirmed (code):

  • Init: update interval 240 ticks (4 s); pops the parent handle, the rotation and the position (+0x20, +0x10); flags +0x54 = 1, or 0x11 with a parent (attached to it, 0x003a18b8); Obj_SetModel(object, 0), the hash kept at +0xc4. Then by model hash: 14 hashes (the eight Wonder Wheel types among them) set +0x124 = 5; others set flags 0x80, 0x8080 or 0x20; two (0x14e8682c, 0x014d30ce) get an interval of 60 ticks and a looping sound (0x326071de, 0x26ac304b).
  • Update: nothing but the looping sound of those two: within the sound's range + 10 m it plays every 2 ticks, otherwise it stops and checks every 60. A plain prop does not move itself.
  • Messages: 0x12 shows it (clears flag 4); 0x13 hides it (sets flag 4; three model hashes set 0x14000000 instead); 0x3c hides it; 8 puts it back at its attached pose; 0x0a sets whether it can be hit; 0x1b, 0x1c, 0x32 attach it to or detach it from a human; 0x20 stops its sound.

That flag 4 stops the draw is inferred (it is a glass pane's hidden bit too, Glass types).

Objective markers (dyn_objective)

The marker a mission asks the player to walk into is two world objects: a disc with a "W" on it that turns about the vertical, at the top of a translucent coloured column standing on the ground. Scripts place it with ObjSpawn of a dyn_objective type, show it with ObjShow and remove it with ObjDestroy; the volume box the player walks into is a separate object (Scripting: triggers). The marker has no radar part: a script that wants one adds a radar objective itself (HUD).

In level99 (inferred from the script's calls): RegisterObjects spawns four dyn_w_mission (the script's Objects.dyn_w_mission01-04), each standing in a volume box. P1.SetupCam sets the first hint, calls ObjShow(Objects.dyn_w_mission02) and waits for message 3 of the box vMark01; P1.FirstGlow clears that handler, ObjDestroys marker 02, sets the next hint, waits on vMark03 and shows marker 03; P1.DoneCamera destroys marker 03. Confirmed (runtime), PCSX2 2.9.94 with the first hint on screen: marker 02 stands at (−283.27, 127.07, 0.3) in vMark01, marker 03 at (−287.32, 121.55, 0.3) in vMark03 with alpha 0 until shown.

The disc (dyn_objective, script type 45, record 0x005132ac: init 0x003e98e8, update 0x003e9b08, message 0x003e9be0, flags 8). Confirmed (code) unless marked:

  • Init: pops the position and rotation; flags +0x54 = 1; Obj_SetModel(object, 0) (the type's own model, hash at +0xc4); angular velocity (+0x40, set through vtable +0x7c) = (0, 0, π/2), which Task_Integrate applies each update: it turns about the world z axis at 90° per second, one turn in 4 s (confirmed (runtime): 180° in 120 ticks); update interval 2 ticks (30 Hz); +0x124 = 1, which exempts it from the size cull (The model).
  • The column: creates an object of type dyn_objective_a (Obj_SpawnChildByName 0x003a53f0, the name at 0x00585a70) at the disc's position, attached to the disc, and sends it message 0x34 with a colour chosen by the disc's model hash (below). Then it sets itself, and so the column, hidden.
  • Shown state (ObjectiveMarker_SetShown 0x003e9828): message 0x0a with 1 (ObjShow) or message 0x3c sets it shown and object flag 0x800000; 0x0a with 0 (ObjHide) sets it hidden and clears the flag. Either way the disc sends the same 0x0a to its column.
  • Update (every 2 ticks): the tint's alpha byte steps 8 towards 255 while shown and towards 0 while hidden (written to +0xcc, so +0xc8 follows an update later). A full fade takes 32 updates, about 1.07 s (confirmed (runtime): 0 → 255 and 255 → 0 each in 64 ticks, disc and column in step). Once the disc is dying (below) and its alpha is 0, the update returns 1 and WorldObject_Update removes it.
  • Messages: 0x15 (destroy by message, ObjDestroy(object, true)) marks it dying, sends 0x15 to the column and hides both, so they fade out and go; 0x19 and 0x22 change its model (Obj_SetModel with a popped hash); 0x20 removes the column at once.

The column's colour, a word 0xRRGGBBAA with alpha 0 (the fade supplies the alpha), by the disc's type:

Disc types Column colour
dyn_w_mission, dyn_objective_yellow, dyn_throwtarget 0xC1A04700 (193, 160, 71) yellow-gold
dyn_w_goto, dyn_objective_w, dyn_objective_red 0x99121300 (153, 18, 19) red
dyn_w_bonus, dyn_objective_green 0x5F447000 (95, 68, 112) purple
any other (dyn_w_cinematics, dyn_w_mission_b) 0xFFFFFF00 white

The code compares model hashes (0x27af4fe0, 0x39cbfb46, 0x646520ba; 0xa83a74da, 0xebef30bb, 0x34af4687; 0x14dc9db8, 0x7c280227); the type names are those whose CRC-32 they are (disc check). The disc's own tint stays the spawn record's, white for every marker level99 places, so the disc shows its model's colours.

The column (sub_objective_column, script type 222, record 0x00514080: init 0x003e9d60, update 0x003e9fd8, message 0x003e9ea0), type dyn_objective_a (TYPE_GLASS, no collision shape, box 0.68 × 0.68 × 3.68 m, Objects). Confirmed (code) unless marked:

  • Init: pops the parent, rotation and position; flags 0x11 (attached; parent at +0x58); its own model (hash 0x1f3b85ea); local position zero; angular velocity (0, 0, −π/2), the disc's turn backwards, so the column keeps its world orientation while the disc turns (inferred: an attached object's pose is relative to its parent's); update interval 2 ticks; shown.
  • Messages: 0x34 the colour (+0xcc = the word, +0xc8 = the word with alpha 1); 0x0a shown or hidden; 0x15 dying and hidden. Its update is the disc's fade.
  • Its model is a cylinder, white at the foot and fading out towards the top (the reference image); tinted gold it is the translucent yellow column of the runtime picture.

How it looks (confirmed (runtime), PCSX2 2.9.94, the tutorial's first hint): a translucent gold column on the ground, a little taller than a human, with the opaque "W" disc at its top, its face turning past the camera; the column is see-through and fades upwards. The disc's model (dyn_w_mission, hash 0x27af4fe0) is a gold coin with a black "W" (Objects), its CfgObj box 0.52 × 0.52 × 2 m.

Drawing. Both are ordinary world objects drawn by ObjectRender_Draw with their tint as the geometry colour (The tint): no separate pass, the scene's lights selected as for any object, nothing drawn while the alpha is under 10. The disc is never size-culled (+0x124 = 1). ObjShow's distance fades both over its last 2 m when a script passes one; level99 passes none. Confirmed (code). How the column's translucency is blended (its material and vertex alpha, the PS2 pipeline's blend) is not traced.

When it goes: the script's ObjDestroy(handle) (no second argument) removes the disc at once (its vtable +0x4c) and the attached column with it, without a fade. Confirmed (code) for the disc; confirmed (runtime) for both: their handles are empty three updates later and nothing is drawn. Only ObjHide or a destroy by message fades a marker out.

Other marker kinds, for later:

  • dyn_w_goto and dyn_w_mission as the Rumble race's carrots and the box glows (Rumble); dyn_objective_w (red, a larger "W") at level99 checkpoint 2 for Vermin (Scripting).
  • sub_objective_glow (script type 223, init 0x003ea0b0, update 0x003ea260, message 0x003ea198): a 120 × 120 sprite of the lighting sheet, rectangle 3 (a corona), alpha 255, pulled 0.32 m towards the camera every 2 ticks; message 0x0a shows or hides it. Confirmed (code) for the three functions (the table's record 223), the 2-tick interval, rectangle 3, alpha 255, the 0.32 m pull and message 0x0a's show and hide; the size and the sheet are inferred. Who spawns it is not traced.
  • dyn_throwtarget (gold), dyn_w_bonus and dyn_objective_green (purple), dyn_w_cinematics (white): the same class; their uses are not traced.

What moves them: the Wonder Wheel

No object code turns the wheel, swings a cart or flickers a neon: simple_object does not move, and the 29 types have no class of their own. A scene moves them. level100.lua binds them to WonderWheel_100 (scene id 34; slots 0-7 _a carts, 8-15 _b, 16 the wheel, 17-24 _c, 25-28 neon_a-d, Scripts) and plays it looping in world coordinates; level99.lua does the same with WonderWheel_99, the same tracks without a camera (Scenes). Each update (every 2 ticks, 30 a second) the scene's runner sets every bound object's transform from its track: positions linear, rotations slerped between keys (Scenes: objects). Confirmed (code) for the mechanism; the motion below is read from the disc (counts and values, 2026-10-06):

  • The loop: 600 frames (20 s). Every object ends where another starts, so the loop is seamless: the wheel and the four neons each end turned 45° from their start (one rim car's spacing), every rim car ends on the next one's start, and the sliding cars end on another sliding car's start.
  • The wheel (slot 16) and the neons (25-28): fixed at the hub (515.51, −68.89, −188.67), four rotation keys (frames 0, 255, 508/510, 600) turning them 45° about the axle: about 2.25° a second, a turn in 160 s. The neons turn with the wheel (their own start angles differ).
  • The rim cars (_c, slots 17-24): on a ring 21.55 m from the hub, 20 position keys and about 45 rotation keys each; they keep hanging level, swinging at most 6.6° about it.
  • The sliding cars (_a, _b, slots 0-15): between 12.6 m and 22.2 m from the hub, 16-372 position keys each: the cars that run along curved tracks between the rim and the inner ring, tipping up to about 60° on the way.
  • The neon flicker: scene events on the neon tracks, type 24 (message 0x12, show) and 25 (message 0x13, hide), confirmed (code) at 0x00354d98 and 0x003ef188. Frames (30 a second), from the start of the loop:

    Neon Hidden Shown
    neon_a 0-60, 120-210, 270-360, 420-510, 570-600 60-120, 210-270, 360-420, 510-570
    neon_b 0-30, 60-180, 210-330, 360-480, 510-600 30-60, 180-210, 330-360, 480-510
    neon_c 30-150, 180-300, 330-450, 480-600 0-30, 150-180, 300-330, 450-480
    neon_d never always

    So the signs take turns (c, b, a every 5 s, each lit for 1-2 s) over the always-lit neon_d. - The camera: the scene's own, fixed at (462.60, −122.35, −187.93) for the whole loop (Front end).

When the scene stops (SceneStop, a movie, a level start) the objects are released where they stand; playing it again restarts every track from frame 0. Inferred from Scenes: ending.

Pickable objects

Triangle's pick-up search (Breakables) and Human_PickUpObject take only objects with flag 0x8000 (object +0x54). Each class's init sets the flags, confirmed (code):

Class Init (+0x54) Pickable Exceptions
melee_weapon (0x003fd420) 0x218081 yes bit 0x80 cleared for model hash 0x13ff8ee4; its message 0x19 sets or clears 0x8000
thrown_weapon (0x00403090) 0x228081 yes
overhead_weapon (0x003ff6c8) 0x228001 yes not for model hash 0x8fc6ac30
dyn_molotv (0x00404b38, the Molotov) 0x228081 yes
pickup_item (0x003f17e0) 0x208081 yes not for model hash 0x2fd690d6 (also cleared in 0x003f23f8)
powerup_item (0x003f2700) 0x808081 yes, but the search skips the class: it is walked over
simple_object 1 (or 0x11) no 0x8080 added for model hash 0xfcbe9fbb

The search also refuses bit 0x10, bit 0x4000000, an object whose type value (CfgObj field +0x62, read through vtable +0xdc) is 75 or more unless the human's record +0x11b is 13, the model hash 0xd2cfcd44 for a non-player, and anything out of sight. At runtime (slot 1) dyn_masks, the cash register and the swinging doors had no 0x8000.

Weapon piles (dyn_pile)

A pile is a world object that is never picked up itself: each take makes a new object in the taker's hand and the pile stays. The 13 types of class dyn_pile (Script types) are pickable (flags 0x228481, so 0x8000), update every 20 ticks and have a solid box; dyn_donut_spawner has no model. Confirmed (code) at the cited addresses.

  1. Triangle (or an AI's pick-up action) sends the pile message 0 like any pickable object (Breakables: a player must have empty hands). DynPile_OnMessage (0x003d3d30) answers by sending the taker message 0x14 with the pile as the object.
  2. The taker's 0x14 (Human_HandleMessage, 0x00245920) plays the pick-up clip of the pile's pickup_anim (Combat: the pick-up clip; na gives 461, or 462 when the pile is more than 0.8 m above his feet). A spray-can box (type 44) is refused first when the player's spray charges (item 3) are at the limit.
  3. The clip's take event (event 9) sends the pile message 0x1b, which the pile answers with human message 3 (same function). That makes the object by the pile's object type (CfgObj's type, type record +0x86), plays the pile's take cue at the pile (3D, volume 1.0), and puts the new object in his hand with Human_PlaceItemInHand(human, name, 1, 0) (0x0024c280, the HuPlaceItemInHand path):
Pile type (value) Piles Object made Take cue
TYPE_BRICKPILE (17) dyn_brickpile dyn_brick 25
TYPE_CHUNKPILE (18) (none of class dyn_pile in the list) dyn_cueball 26
TYPE_BEERPILE (19) dyn_beerpile dyn_beerbottle 27
TYPE_BASEBALLPILE (20) dyn_baseballpile dyn_baseball 28
TYPE_LIQUORPILE (21) dyn_liquorpile dyn_bottle_a, _b or _c at random 29
TYPE_MOLOTOVPILE (22) dyn_molotovpile, dyn_molotovpile_b dyn_molotv 30
TYPE_POOLBALLPILE (23) dyn_poolballpile dyn_poolball08_ (a random number is drawn and not used) 31
TYPE_OILPILE (41) dyn_oilcan_box dyn_oilcan none
TYPE_SPRAYPILE (44) dyn_spraycan_box no object: one spray charge (item 3; the last up to 9), its pick-up sound, the spray hint once none
TYPE_DONUTPILE (46) dyn_donut_spawner dyn_donut_a, _b or _c at random, only while item 3 is below its limit (the spray-can test, copied) none
TYPE_BEERPILEHEAVY (47) dyn_beerpileheavy dyn_beerbottleheavy 27
TYPE_BOLTCUTTERBOX (49) dyn_boltcut_box dyn_boltcutter_b none

So the numbers a level's CfgObj line gives a pile (17, 19, 20, 47, ...) are its object type, and the type alone chooses what it hands out; no field names the object. dyn_blokpile is TYPE_GENERIC (2): its take falls to the default case, Human_PickUpObject and then the pile itself in the hand (inferred from the code; not seen). The take cue is the AudioManager cue table entry (0x00111130) of that number (Sound). 4. Running out. Only dyn_molotovpile_b (model 0x69b9d1a0) counts: each take adds 1 to record +0x18; when it reaches the limit (record +0x14, 5 from DynPile_Init 0x003d3c28, replaced by message 0x22) the pile toggles flag 0x8000 (no longer pickable), sets +0x1c = −5 and updates every 240 ticks; its next update (DynPile_Update, 0x003d4058) zeroes the tint +0xcc, and the one after (tint +0xc8 now 0) is done, so it is removed. Every other pile never runs out and never refills: it hands out an object on every take. 5. Messages 0x1b / 0x1c (handed / released) and 0x30 (knocked: 4-6 m/s along the knock, airborne) let a pile be carried and thrown like a prop; a thrown pile that falls below −500 m is removed.

AI use (confirmed (code)):

  • GoalManWeaponPile (ManWeaponPileGoal_Process, 0x002a3498; arguments on the binding page). Fetch state 0, empty-handed: with a style of 1-5 the object is put straight in his hand (dyn_brick, dyn_molotv, dyn_manqheadred, dyn_porcelain_weap_b, dyn_beerbottle) with no pile; with style 0 the nearest object with flag 0x20000 within pickupRadius that he can reach (Ai_FindObject with filter 0x0053f2d0 = ObjFilter_HasFlags 0x20000; a pile has it) gets a GetItem goal (gait 3), whose pick-up action takes from it as in steps 1-3. After 21 updates with nothing found the goal ends. Holding something (state 1): wait the delay (delayMs, 500 ms), turn to the target (within 15°), check the line when asked, look only when the target is beyond throwRange, else command 0x10 (throw), count the throw and wait pauseS, then fetch again (state 2 → 0) until maxThrows (−1: no limit) or a target within closeRange. Since piles do not run out, a scripted thrower keeps throwing as long as the goal lives.
  • ThrowSubTactic (ThrowSubTactic_Order, 0x00322ba8), with its pile option (+0x16 = 1): the objects within 30 m of the gang, nearest first, whose type's class is dyn_pile (name 0x005747a0), not held, not bit 26 and not already claimed by this gang, with a reachable navigation polygon under them, are claimed for 5 s and each given to a free member (not attacked, no goal 0x3f, top goal not ObjectPile or GetItem) as an ObjectPile goal (AI goals), until the order's count is met. Without the option it sends GetItem goals at loose objects of anim set 4 or 5 instead.
  • TacticManWeaponPile gives each member a ManWeaponPile goal (AI code).

Objects in a human's hand

One mechanism carries every object a human holds (a bat, a bottle, a brick, a stolen stereo, a hat being picked up): the object is attached to one bone of the human's skeleton at a fixed local offset, and both the bone and the offset come from an event in the clip that puts it in the hand, not from CfgObj or a weapon table. The bat is the worked case (Combat: a bat in hand); what the human does with it (anim sets, strikes, +0x338) is there too.

The attachment (confirmed (code) at 0x003a1810, 0x003a1858, 0x003a19b0): world object fields

Field Meaning
+0x54 bit 0x10 attached
+0x58 the parent task (here the human)
+0x6d u8, the parent's pose bone (Animation: bone transforms); 0 means the parent's own transform
+0x10 / +0x20 while attached: the local position (vec4) and rotation (quaternion x, y, z, w) in the bone's frame

Obj_Attach (0x003a1810) sets the bit, the parent and the bone. Obj_Detach (0x003a1858) first writes the object's pose "now" (vtable +0xa4, Obj_GetPoseNow) into +0x10 / +0x20, then clears the bit and the parent, so a detached object starts exactly where it was drawn. Confirmed (code).

The world pose (Obj_GetWorldPose, 0x003a19b0, function word +0x9c of the world object's vtable 0x005453a0 and the car's 0x00544c08; confirmed (code), both slots read): unattached, +0x10 / +0x20; attached, three transforms composed with 0x003359b0 (rotation qa · qb, position pa + qa · pb, no scale):

world = parent's world transform (its vtable +0x9c: for a human, its position and rotation)
      ∘ the bone's transform, when the bone is not 0 and the parent is a human with a model instance
      ∘ the object's local (+0x10, +0x20)

A human's bone transform (0x0023bde8, its vtable +0xbc) is the bone's entry of the per-update bone cache (Combat: the bone cache's 0x006b6880 + index × 0x470, built once per update from the playing pose, model space) with the position multiplied by the human's scale (+0x65c, 0.97 for Rembrandt) and the rotation as is. The object itself is never scaled. 0x003a1ad8 (vtable +0xac, the pose extrapolated to now) does the same for an attached object, the local rotation advanced by its angular velocity (+0x40, zero while held).

At runtime (confirmed (runtime), PCSX2 2.9.94, slot 1 copy, the player 1 m behind a dyn_bat_tuff, triangle, then stick 60 % up for 35 updates, square, R1 held 30 updates, stick 100 % up for 30 updates, triangle): the bat's RenderWare frame matrix (its instance +0x1c atomic → frame, LTM at +0x50, axes as (x, z, −y)) equalled human transform ∘ (scaled bone 25) ∘ local to within 0.005 m and 0.005 in every axis component on 256 of 257 held updates, read from the values of the update before the draw; the one exception was one update's run distance (0.26 m at 7.8 m/s). So the draw uses the pose of the latest update, with no smoothing or lag of its own.

Clip events that place a held object (confirmed (code) at 0x00101dd8, which sends the human message 0x80 + type with the event's bone (+6), position and rotation (Animation: events); the human's handler is 0x00245920):

Event Message What the human does
9 0x89 the object being picked up (human +0x33c) is sent message 0x1b take, with the human, the bone, the event's position × the human's scale, its rotation, and whether the human is in a scene
0x36 0xb6 the same (the left-hand clips 463 / 464 use it, bone 19)
0x22 0xa2 with a pick target and nothing in hand, as 9; otherwise the held object (+0x338) gets message 0x32: re-attach at this bone and offset, nothing else. GhettoPickUp's clips 549 / 550 carry one per frame from 13 to 39, animating the object in the hand
0x34 / 0x35 0xb4 / 0xb5 as 9 / 0x22, only for object types 24 and 27
0x37 0xb7 writes bone and offset straight into the object at human +0x348 (or +0x34c); what those hold is not traced
10 0x8a release with something in hand: a throw (0x002586d8) under state flags 0x1000010, else a drop (0x00257f38) under 0x4000 or in a scene

Take (message 0x1b; melee_weapon 0x003fe490, simple_object 0x003ef188, confirmed (code)): velocity and angular velocity zeroed, attached to the human at the bone, flags 0x6000000 cleared, the local pose set to the event's. When the human is not in a scene, the local position is then moved along the object's own y axis by the type's float at CfgObj +0x70 (the local rotation applied to (0, v, 0)), so a model whose origin is not at its grip is slid to it (0.39 m for dyn_bat_tuff). The object then sends the human message 3 (applies its anim set and puts it in +0x338, Combat: a bat in hand) and message 0x17 (with CfgObj +0x68; not traced). There is no blend: the object stays where it lay until the event's update and is in the hand on the next draw. melee_weapon keeps the event's position in its data block too, and moves the local position again along y by CfgObj +0x70 / +0x74 on its messages 0x12 / 0x13 (senders not traced).

Scripted placement (HuPlaceItemInHand, 0x00238540 → 0x0024c280 → 0x0024c6d8 → 0x00101360, confirmed (code)): drops what the human holds, creates the object by name at the human's position, and reads the first type-9 event of the low pick-up clip its pickup_anim selects (Combat: the pick-up clip) without playing it, sending the object message 0x1b at once; the +0x70 slide applies when object +0x110 is −1. Unlike the clip path the event's position is not multiplied by the human's scale. A left-hand or hat pick-up clip has no type-9 event, so nothing is attached.

Drop (0x00257f38, triangle with nothing to take, a pick-up of a two-handed or hat object, a scene's release; confirmed (code)): pops the anim set the object pushed, records the human as its last holder (object +0x11c) unless the type is 24 or 27, makes its physics body (Obj_CreatePhysicsBody), reschedules its task, and sends it message 0x1c detach with a zero velocity. If the object (flags 0x30000 without 0x400) now lies farther than 0.35 × scale − 0.01 m from the human across the ground, it is pulled back to that distance. A removal time is set (object +0x120 = now + 120 s for type 11, 1 ms for type 39, a game setting at 0x0051489c +0x26c for most weapons, none for model hash 0x03c5256c). It then falls under physics from the hand's pose. At runtime the bat dropped from the hand came to rest on the ground (z 0.3, the ground's height there), flags 0xc321a081 (confirmed (runtime)).

Throw (Human_ReleaseThrow, 0x002586d8, confirmed (code)): as the drop, but the physics body is made per the type (CfgObj +0x84 = 2 or +0x5a ≠ 1, else ThrownObject_MakeBody 0x003923b8) and gets body flag 1 (the first contact after the throw, Throws), the object is rescheduled, its velocity comes from Human_ComputeThrowVelocity and is sent in message 0x1c detach with the thrower, the thrower becomes its last holder (+0x11c) and +0x116 is set, and the human's target is warned (0x0021d5c0). The removal time is the game setting 0x0051489c +0x26c for every model but four (0x03c5256c gets none; three others keep theirs). The details are in Throws.

Detach (melee_weapon, MeleeWeapon_Detach 0x003fe6b8): the velocity, given in the holder's frame, is turned by the holder's world rotation and set (vtable +0x74). Five model hashes (object +0xc4), the knives dyn_bowie, dyn_hunter, dyn_tknife, dyn_swhbld and dyn_swhbld_super, are tested first (0x003fe7ac-0x003fe7f0): they take the holder's rotation and the angular velocity (−8π, 0, 0) rad/s (its vtable +0x7c) whether thrown or dropped. Otherwise a thrown object (velocity not zero) of anim set 1 or 2 takes the holder's rotation and (−4π, 0, 0); any other thrown object keeps its own rotation and gets (0, 0, −kπ), k random in 5-7; a drop keeps its rotation and gets no spin. The angular velocity is stored as given (+0x7c writes task +0x40, 0x003a19a0) and the integrator applies it as q += ½ ω ⊗ q dt (Quat_IntegrateAngular 0x00335b08), so it is in world axes, not the object's or the thrower's: a set 1 or 2 weapon thrown along world y tumbles end over end, one thrown along world x rolls about its line of flight. Confirmed (code). simple_object's detach only detaches.

Holstering. No code moves a weapon to another bone (back or belt): a human holds one object (+0x338), in the hand its clip event named, until it is dropped, thrown or broken; a weapon not in a hand is a free object. Inferred from the senders of messages 0x1b and 0x32 above (all clip events or the scripted placement).

HuRender (0x00237e98) draws a human and then every object attached to it that is not hidden (flag 4), giving each the human's alpha (confirmed (code)). In the normal draw an attached object skips the small-size fade and is drawn only within its parent's ObjShow distance (Drawing).

A weapon given to a Warrior

What a crew member does with a weapon the player hands him by triangle (the "Give Weapon" prompt, user item 37). The hand-over itself is AI: the weapon swap; this is what follows. Confirmed (code) at the addresses cited unless marked.

  1. It is in his hand, attached and drawn like the player's. The swap places the object with Human_PlaceObjectInHandByPickupClip (0x0024c6d8), the same path as HuPlaceItemInHand: the first type-9 event of the low pick-up clip of the type's pickup_anim gives the bone and offset (bone 25, the right hand, for 461, 481 and 498; Combat: a bat in hand), and message 0x1b take attaches the object to the Warrior (+0x54 bit 0x10, parent, pose bone) and puts it in his +0x338; message 3 applies its anim set. There is nothing player-only in any of this, and HuRender (0x00237e98) draws every human's attached objects after the human, so the weapon shows in the Warrior's hand on the next draw, posed by his bone cache. A left-hand or hat object (pickup_anim 5 or 6) has no type-9 event and stays on the ground (inferred, as for the scripted placement). The prompt that later offers it back ("Get Weapon", HUD string 0xd, he holds) reads the same +0x338 (AI: the Warriors' think, step 7).
  2. He fights with it. An AI attacks through the player's dispatcher (Combat: an AI's attacks), and square and cross choose their clip by the held object's set (0x00231a80, Combat: moving attacks with something in hand), so a Warrior holding a bat (set 3) swings 34 / 36 (37 at a grounded target, though the level34 Furies also used the unarmed grounded strikes 193 / 194 with a bat in hand, Combat: a bat's hit), a baton (set 2) 39 / 41, a knife (set 1) 45 / 47.
  3. The damage is that clip's damage table value plus the weapon's: Strike_Contact (0x0021b290) adds the object type's CfgObj u16 +0x58 and multiplies by the attacker's class record's first float when the held world object's body flags have 0x10000 (the melee weapons) (confirmed (code); whether this branch runs for any held weapon's swing is open, below). Correction (issue #492, confirmed (runtime) for dyn_bat in the level34 Furies' hands only): their bat swings (501, 36, 37) never reached Strike_Contact; each hit was the bat's own body touching the victim (WorldObject_OnContact 0x00394050 → ThrownObject_HitHuman 0x00392b88), which deals CfgObj +0x58 × the holder's class float, without the clip's damage (Combat: a bat's hit). Not checked: the knife (set 1), the baton (set 2), other bats, and the player's own bat swings, so whether a weapon hits as hard in a Warrior's hand as in the player's is open.
  4. His choices change too. Brain_PickAttack adds the armed bonus (+100 to every candidate of kinds 0-9 with a weight; category 14, the Warriors, is not one of the categories 9, 10, 13 that get 0) (AI: picking the attack kind); Human_CanStartAttack measures kinds 0, 1 and 5 with the set's clips (set 3: 36, 34, 35; set 2: 41, 39, 40; set 1: 47, 45, 46), and the fight goal lets an armed fighter attack from anywhere within 1.1 × the far range (AI: objects in an AI's hands). A set 1 throwable lets kinds 0 and 1 reach 12 m (thrown), set 4 15 m, set 5 30 m.
  5. What he keeps. Outside co-op a Warrior holding an object of kind 4 or 6 (the overhead props and the ghetto blasters) drops it once his enemy list is empty and no steal tactic runs (WarriorBrain_Think 0x003052f0, step 3); a bat, baton, knife or throwable stays in his hand until he uses, throws or swaps it, or a hit makes him drop it: during a throw clip or clip 602 with set 1 (Human_ApplyPendingDamage 0x00265f70), when the hit stuns or knocks him down while he is hurt, when it comes from a counter or special clip (620 and above), or when he is grabbed holding a bat; all but the throw case are stopped by HuSetKeepWeapon (Combat: dropping what is held when hit).

Coney fixes (user item 37; the hand-over works, the member holds the object, play_level_objects.cpp gives him its anim set):

  1. Draw every human's held object, not only player 1's: Coney's world-object draw places just m_player->human().script().heldObject (play_level_world.cpp); each AI human's script().heldObject needs the same attached pose (his hand bone from his own pose, his scale, the type's grip slide CfgObj +0x70).
  2. Attach the given object to the receiver's right hand with the pick-up clip's type-9 offset, as the player's pick-up does, and leave a left-hand or hat object on the ground.
  3. Check that an AI fighter with a held set picks its square / cross clips from the held object's set (34 / 36 for a bat) and that an AI's bat hit deals the bat's CfgObj +0x58 × his class float, as the bat's own contact does (Combat: a bat's hit). Only dyn_bat in an AI's hand was measured; the knife, the baton and the player's bat are unchecked, so keep their damage path an open question until they are.
  4. Add the armed bonus to Coney's attack pick and the set's clips to its reach test, if missing.
  5. Drop a held kind 4 or 6 object from a Warrior with no enemies outside co-op.
  6. Test: give a bat to a Warrior; assert he holds it, its world pose equals his transform ∘ scaled hand bone ∘ local on the next draw, and an attack of his on an enemy plays 34 or 36 and deals the bat's CfgObj +0x58 × his class float (not the clip's damage; fix 3).

Two-handed objects

How the original decides that a held object is carried in both hands (the cash register, a drum, a chair, an amp) rather than hung from the right hand like a bat or a bottle. Asked by issue #433: in Coney the level99 checkpoint 2 register is lifted with the right clip but then held like a one-handed prop.

There is no "two-handed" flag. The look comes from two bytes of the object's type record (CfgObj, Object types), set once from the objects file (config_preload3.lua's CfgObj lines, arguments 14 and 20, the binding), never per model or per level:

Type byte CfgObj argument What it chooses Two-handed value
+0x65 14 pickupAnim the pick-up clip (Combat: the pick-up clip) 2 TwoHandPickUp: 503, or 504 above 0.8 m
+0x87 20 animSet the move style pushed while it is held, which swaps the idle and locomotion clips 4 OVERHEAD_WEAPON_SET

The object is still attached to one bone, the right hand (25); both hands meet it because every clip the style plays (the idle, walk, run and turns below) poses both arms round the object (inferred: the bone and the clips are read, the arm pose is not measured). The object's class does not matter: overhead_weapon and dyn_cashreg behave alike because their take handlers are alike (below), and the class only decides breaking, wear and drops.

1. The pick-up uses +0x65 (Human_PlayPickupClip, 0x0025e5a8, from the human's message 0x14): 2 plays 503 ANIM_BARREL_PICK_UP, or 504 …_HIGH when the object is more than 0.8 m above the human's feet. Confirmed (code), as on Combat.

2. The take (the clip's type-9 event, human message 0x89 in Human_HandleMessage 0x00245920) sends the object message 0x1b with, in push order, the human, the event's bone, the event's position × the human's scale, its rotation, and whether the human is in a scene (Human_IsInSceneState). The two-handed classes' handlers (OverheadWeapon_OnPickedUp 0x003ff918, DynCashreg_OnPickUp 0x003c0108) zero the velocity and spin, attach the object at the event's bone (flag 0x10) and then set its local pose:

if not in a scene:  local position = type record +0x00 (holdPos, w = 1)      // ScriptObj_GetVectorProperty 11
                    local rotation = type record +0x0c (holdRot, x y z w)     // ScriptObj_GetVectorProperty 12
else:               local position, rotation = the event's

So in play the clip event gives only the bone; the offset is CfgObj's holdPos / holdRot, not scaled by the human and not slid by CfgObj +0x70 (that slide is melee_weapon's, Take). Confirmed (code) at 0x003c01f0-0x003c0244 and 0x003ff918; the property numbers at 0x003a3978 (11 reads three floats at the record's start, 12 four floats at +0x0c; Cfg_AddObjectType 0x00390f18 stores arguments 21 and 22 there). overhead_weapon additionally sets path flag 8 under its first pick-up spot and, for model hash 0xac6f5902, clears its own 0x8000.

3. The move style. The handler then sends the human message 3 with itself. The human's case 3 reads the held object's type +0x87 and, when it differs from the human's current style (Human_GetMoveStyle), calls Human_SetCarryMoveStyle (0x00221ed0):

  • style 0 (NONE_SET): nothing;
  • the human in the hidden style 0x14 (Stealth) and the object's style 4 or 6: the top style is replaced (0x00253e68, a pop then a push; confirmed (code));
  • no style yet: the object's style is pushed (0x00253ed0; confirmed (code));
  • another style on top: that one is popped, the object's pushed, and the old one pushed back on top.

A push applies the style with Human_ApplyMoveStyle (0x00253688): the anim slots are reset to the defaults and the style's overrides written, then the gait speeds are recomputed from the new clips' root motion (0x00253688 ends in the speed computation). Message 0x16 (sender not traced) calls the same function unless the human has state 0x180000. Confirmed (code) at 0x002460f4 and 0x002469c8.

Style 4, the two-handed carry (Human_ApplyMoveStyle case 4, confirmed (code); clip names from the anim id reference):

Slot Default Style 4 Clip
0 idle 388 509 ANIM_BARREL_NORMAL_IDLE
1 step forward 401 535 ANIM_BARREL_FIGHT_MOVEMENT_STEP_FORWARD
2 dash forward 403 537 ANIM_BARREL_FIGHT_MOVEMENT_DASH_FORWARD
3 sneak walk, 4 walk 407, 408 515 ANIM_BARREL_MOVEMENT_WALK
5 jog 409 516 ANIM_BARREL_MOVEMENT_JOG
6 run 410 517 ANIM_BARREL_MOVEMENT_RUN
7 sprint 411 518 ANIM_BARREL_MOVEMENT_SPRINT
8 slow turn right, 15 combat turn 390, 360 529 ANIM_BARREL_FIGHT_TURN_RIGHT
9 sneak walk start, 10 walk start 412, 413 519 ANIM_BARREL_MOVEMENT_WALK_START
11 combat idle 358 527 ANIM_BARREL_FIGHT_IDLE
12 combat shuffle forward 366 535 as slot 1
13 combat dash forward 368 537 as slot 2
14 combat walk forward 372 (player 380) 541 ANIM_BARREL_FIGHT_MOVEMENT_WALK_FORWARD
33 run stop 417 523 ANIM_BARREL_MOVEMENT_RUN_STOP

Every other slot keeps its default (the attack slots do not matter: with set 4 in hand square and cross throw, Combat: moving attacks with something in hand). Style 6 (GHETTO_SET, the two ghetto blasters, pickupAnim 7) is the same shape with the ghetto clips (idle 555, walk 561, run 563, run stop 569, ...). Styles 1-3 (knife, baton, bat) change only attack and block slots, which is why a bat is carried with the plain idle and walk (Combat: a bat in hand).

4. Letting go. The drop (Human_DropHeld, 0x00257f38, Drop) and the throw pop the style the object pushed, so the slots go back to the defaults. Before a pick-up clip, a held object is dropped first when the new object's pickupAnim is 5 or 6 or the human's style is 4 or 6 (Combat).

At runtime (confirmed (runtime), PCSX2 2.9.94, quick-save slot 8 copy (level99 checkpoint 2), scenario repo:research/traces/scenarios/cashreg_carry.toml: the player put at (53.23, 56.45) facing +y, 0.8 m south of the store's register at (53.23, 57.26, 1.63); triangle at update 20; stick 60 % for 80 updates from update 110; one sample per update):

Update Clip Move styles What happened
21 504 none triangle: the high clip (the register is 1.42 m above his feet)
28 504 depth 1, style 4 the take: +0x338 holds the register; slots 0 / 4 / 6 / 11 = 509 / 515 / 517 / 527
36 509 4 the clip ends in the barrel idle
111-120 519 4 the stick: the barrel walk start
121-190 515 4 the barrel walk, gait 1, 1.46 m/s
191 509 4 stick released: back to the barrel idle (no run stop from a walk)

The register's attachment through all of it: bone 25 (the right hand), local position (0.3075, 0.1306, 0.2869) m and rotation (−0.1485, 0.0522, −0.9863, −0.0484) (x, y, z, w), exactly its type record's +0x00 / +0x0c (type index 262; its +0x84-+0x87 read shape 1, axis 0, type 2 TYPE_GENERIC, set 4). The bone came from clip 504's event; that 503 (the floor clip) also names bone 25 is inferred.

Which objects share it (the reference list repo:research/references/objects.yaml, from the CfgObj lines):

  • pickupAnim TwoHandPickUp with animSet OVERHEAD_WEAPON_SET: all 66 overhead_weapon types (the drums, chairs, amps, crates, carts, trash cans and bags, dyn_box_stereo, ...), dyn_cashreg and dyn_cagedchic (class dyn_chicken) — 68 types, the same 68 that carry set 4.
  • dyn_cardrack (class dyn_masks) has TwoHandPickUp with NONE_SET, so it would be lifted with 503 and then carried with the plain clips; a dyn_masks object was not pickable at runtime (Pickable objects).
  • GHETTO_SET (style 6): the two ghetto blasters, carried the same way with their own clips.

So the register is not special: anything Coney lifts with 503 / 504 needs the same carry.

What Coney must do (the gap, from Coney's code: the take uses the pick-up clip's event offset for every class, world_objects::heldAttachment in repo:src/world_objects/object_tasks.h, and the animator's anim slots know only the hidden style; the pick-up returns to the plain idle 388):

  1. Read each type's animSet (CfgObj argument 20, +0x87) and holdPos / holdRot (arguments 21 / 22) with the other CfgObj fields.
  2. On the take of an overhead_weapon or dyn_cashreg object outside a scene, attach it to the event's bone with local position holdPos and rotation holdRot as given: no human scale, no +0x70 slide. Keep the event's offset (× scale, slid) for melee_weapon and the classes that do so today; in a scene use the event's pose for both.
  3. When an object is put in a human's hand (player or AI), if its animSet is not 0 and differs from his current move style, apply it by the rules of step 3 above (push; replace the hidden style 0x14 for 4 or 6; otherwise slip it under the top style).
  4. Implement move style 4's slot table above (and 6's, from the same function, when the blasters are done): reset the slots to the defaults, write the overrides, recompute the gait speeds from the new clips. The pick-up clip's follow-on then lands in slot 0, 509, not a fixed 388.
  5. Pop the style on the drop and the throw, restoring the default slots (or the style beneath).
  6. Test, unit: applying style 4 to the default slots gives exactly the table above, and popping it restores the defaults; a take outside a scene gives holdPos / holdRot unchanged at bone 25.
  7. Test, disc, through the player's path (coney::test::DiscSession, level99 checkpoint 2, a LivePad driver): walk the player with the stick at 60 % from the shop floor to the store's register (53.23, 57.26), stop facing it and tap triangle. Assert, in order: clip 504 starts; within 10 updates the register is held, his top move style is 4 and slot 0 is 509; when 504 ends the idle is 509; with the stick at 60 % he plays 519 then 515 (not 413 / 408); the register's local pose is its holdPos / holdRot to 1e-4 at bone 25; triangle again drops it and the idle goes back to 388. Print counts only. The same test against a dyn_trashcan on the street (slot 8 has one at (47.71, 2.28), Trash cans and bags) covers the overhead_weapon path.

Open: what the arms do in the barrel clips (that both hands reach the object is inferred from the clips' names and use), and the bone named by clip 503's take event (inferred 25, as 504's).

Throws

Confirmed (code) unless marked. Gravity in flight is 15.68 m/s² (the aim trace steps the z speed by −0.2613 per 1/60 s, Graphics).

Starting a throw. Square or cross with an object of set 4, 5 or 6 in hand (Player_CrossWithWeapon, 0x002880d8) plays a throw clip at once, by gait; nothing waits for the button's release:

Set Standing or fight stance Walking Jogging, running or sprinting Target search
4 (overhead_weapon and the like) 505 506 507 10 m
5 (thrown_weapon: bottles, bricks, balls) 467 471 472 20 m
6 551 552 553 20 m

Outside the aiming state, a player with no brain target picks one (Player_PickThrowTarget, within the search radius) and turns to face it over the time to the clip's first event; the held object is sent message 4, then Human_StartMeleeThrow plays the clip. The release is the clip's event of type 10: the event code (Anim_FireFrameEvents, 0x00101dd8) sends the human message 0x8a, and his handler (0x00245920) calls Human_ReleaseThrow when he holds an object with held flag 0x1000010. The event frames (30 per second, before the clip's rate; read from the disc's clips):

Clip Type 10 at frame Clip Type 10 at frame Clip Type 10 at frame
467 gen_1hand_throw 5 505 gen_2hand_throw_fwd 16 551 ghetto_throw 9
471 gen_1hand_throw_walk 11 506 gen_2hand_throw_walk 12 552 ghetto_throw_walk 10
472 gen_1hand_throw_run 11 507 gen_run_2hand_weapon_throw 13 553 ghetto_throw_run 7

The melee weapons' throws have one too: 491 gen_knifethrow frame 18, 502 gen_batthrow frame 15.

The aiming state (Human_MoveThrowAim, 0x00244770) is entered only with a set 5 object: L1 pressed with no target locked, or with a friendly one or one of his own gang (Player_OnL1Pressed 0x0027da10, then Human_EnterThrowAim 0x00227b30: state 0x200000000000, move style 0x13, clips 466-470). It stores the human's position at +0x610, his rotation at +0x620 and the pitch 0.157 rad (9°) at +0x630. Each frame the left stick turns the aim (outside a dead zone of 26 of 128, 0.000589 rad per step of the stick value) and pitches it, clamped to ±0.628 rad (36°). L1 again leaves it, and so does losing the object (held flag 0x20000 gone), a grab or a mug, or an AI in control. Square or cross then throws as above, along the aim.

The aim's heading is the human's own rotation, confirmed (code) at Human_SetThrowAimState (0x002446b0) and Human_MoveThrowAim (0x00244770): the move function writes the human's facing every frame and copies it to +0x620, so turning the aim turns him. It starts from the way he faces, with one exception on the first frame (human +0x256, cleared by Human_SetThrowAimState, set by the first Human_MoveThrowAim): for a player with one view per player camera (0x0050b198 = 0x0050b19c) whose active camera is of type 2, the follow camera (camera vtable +0x1ec, camera types), his heading is set once to the follow camera's: the direction from its position (vtable +0x21c) to its focus point (+0x24c), horizontal part only. Under any other camera, such as a script's locked camera, he keeps his own facing. The follow camera then frames the aim origin while he aims (Camera: the follow update, step 5). Each frame after that:

  1. Human_TraceThrowAim runs (below).
  2. With a human target (+0x638) he turns straight to face it (heading from his release point to the target's point, +0x620 set at once) on each of the 5 frames after a new target is found (+0x63c, set to 5 when the target changes, counted down here), and on any frame both stick axes are inside the dead zone. Objects are never turned to.
  3. Otherwise the stick's x outside the dead zone turns him by x × 0.000589 rad a frame (x = the stick's offset from the centre less 25, about ±0.06 rad a frame at full tilt), with action 10 (the turn); the stick's y pitches the aim by the same rate, clamped to ±0.628 rad.

The aim trace's targets, confirmed (code) at Human_TraceThrowAim (0x0018fee0) and IPhysics_CollideShape (0x0033e7f0): the 18 kept arc points make 17 segments; a sphere of radius half the held object's type +0x78 is swept along each in turn, in two passes:

  1. Humans: a query of bodies only (option 2, not the level mesh), of type mask 0x40 (humans), with body flag 0x20000 (THROWNWEAPONTARGET, layers) and none of 0x180000, the thrower's own body skipped, and the callback ThrowArc_TargetFilter (0x0018f7b8): only an AI human (+0x1b0 = −1) that is not friendly to the thrower, and with the stick pushed sideways only one on that side. The first segment that meets one gives the candidate human and its fraction.
  2. Anything in the way, from the first segment up to the candidate's (all 17 when there is none): the same query with the level mesh added (option 1), any type, still flag 0x20000 and not 0x180000, no callback. The first contact, unless it is in the candidate's segment and no nearer than the candidate, ends the arc there (the strip is cut at the contact) and replaces the candidate.

The results: +0x638 = the human target (0 when none or blocked); +0x634 = the hit's task when it has type bit 2, which every task class has (Tasks: type bits): the target human, or the first world object, car or other body with THROWNWEAPONTARGET the arc meets; 0 when the arc ends on the level mesh (a wall, the ground) or meets nothing. So a wall ends the trace and nothing behind it is seen. HuIsAimingAt(player, object) (Human_IsAimingAt, 0x0023a468) is true when the player's +0x634 is that object. The trace also clears the brain's target (Brain_SetTarget(brain, 0, 1)) and stores 0x006ebd30 at brain +0x128, so a throw from the aim has no locked target and follows the pitch rule of the velocity below. Which world objects have THROWNWEAPONTARGET is their CfgObj PHYFLAG argument; the objective marker dyn_w_mission has exactly that one flag (config_preload3: shape OBB 0.52 × 0.52 × 2, PHYFLAG.THROWNWEAPONTARGET), so it can be aimed at.

level80's throw lesson (Scripting): C3.throwObjective = Objects.dyn_w_mission, the marker level80.lua spawns at (−259.542, 278.038, 1.27) (beside the flag fBottleTarget, (−259.6, 278, 1.3)) and ShowDistractCopObjective shows (ObjShow). C3.TargetCheck, every 200 ms while the player holds a bottle (HuWhatAmIHolding 4) and his control is throwControl, shows TT_29 ("Press X to throw") once HuIsAimingAt(C3.throwPlayer1, C3.throwObjective); C3.ThrowCheck goes back to TT_28 when it stops being true. Confirmed (runtime), PCSX2 2.9.94, a contributor's run of level80 (re-mission-ref, 2026-10-08): +0x634 stayed 0 while the stick pitched the aim down from 0.304 rad and became the marker (0x00ef97c0) at pitch 0.139 rad; TT_29 was set 13 updates later. The bottle then passes through the marker and breaks beyond it (inferred: a flying world object's sweep meets only bodies with BLOCKOBJECTS (Sweeping a body, confirmed (code)) and the marker's body has THROWNWEAPONTARGET alone), which is why the script tests for the bottle's break inside the 8.2 × 6.5 m vBottleBox round the marker (message 6), not for a hit on the marker.

The aim's clips (move style 0x13, Human_ApplyMoveStyle 0x00253688, confirmed (code)): slot 21 (the block start) = 466 gen_1hand_throw_enter, slot 23 (the block sustain) and the idles in slots 0 and 11 = 469 gen_1hand_throw_cycle, slot 24 (the block shuffle) = 470 gen_1hand_throw_turn. So he enters with 466, holds 469 and plays 470 while the stick turns him (action 10); inferred from the slots' names, not watched at runtime. No code names 468 gen_1hand_throw_exit: leaving (Human_ExitThrowAim 0x00227aa8) only removes the style, so the idle returns; whether 468 plays through the anim data is not traced. The throw from the aim is 467 (standing).

The aim trace (Human_TraceThrowAim, 0x0018fee0, each aiming frame) writes the two holder fields the first flying update reads: +0x5f0, the release point = +0x610 + +0x620 turning (0.3009, −0.6354, 1.5865) (x right, y forward, z up, metres), the same offset ThrowArc_PlaceAtHand (0x0018f628) uses for the arc; and +0x600, the throw velocity = Human_ComputeThrowVelocity turned by +0x620. They are used only for a throw released from the aiming state: Human_ReleaseThrow then sets the object's +0x10c to 1 (and leaves the aim, putting the pitch back to 0.157), else to 0. A plain throw starts from the object's own pose in the hand with the velocity of the detach message; the holder's stale +0x5f0 and +0x600 are not read.

The velocity (Human_ComputeThrowVelocity, 0x002570e8) is in the thrower's frame (x right, y forward, z up; the detach and the aim turn it into the world):

  1. The weight factor w = 1 for an object of kind +0x86 = 11 (the knives), else 0.05 × the type's +0x62 (CfgObj argument 6, read through vtable +0xd4 with 1); halved when the thrower's scale is above 1.1. A bottle (+0x62 = 20) has w = 1.0, a crate or chair (50) 2.5, a fridge (75) 3.75.
  2. Aiming with no target: (0, cos p, sin p) × 20 / w, p the aim's pitch +0x630.
  3. With a target (aiming with one, or not aiming with a brain target or an object target): the direction from the thrower's right hand (bone 25, posed) to the target point, turned into the thrower's frame. A human's target point is his position plus, when his +0xd8 is set, a bone: bone 5 for an object of set 1 or 2; bone 6 (the head) unless the object has flag 0x10000 and is not of kind 10; else the middle height of bones 29 and 32 on his axis. A human in state 0xe0000 (down) is aimed at bone 3. An object target is its position plus its attach offset. With u the unit direction: when it points more sideways than forward (|u.x| > |u.y|) it is clamped to 45° (u.x = ±0.7071, u.y = 0.7071, u.z kept). When |u.z| < 0.85: speed s = 30 / w, v = u × s, and v.z += 7.84 × d / s (d the straight distance: half of 15.68 × the flight time d / s, so the arc comes back down to the target's height there). Then, unless the thrower has flag 0x40000 (+0xe0), a random spread: with f = d / 30 clamped to 0.1-1, the velocity turns about world z by f × r × 8° and about the horizontal axis across it by f × r × 8° (r random in −1 to 1 each, 0x6eb898; 8° × 0.6 for an AI of brain kind 3). A target almost straight above or below (|u.z| ≥ 0.85) falls to the default.
  4. The default (no target): (0, 1, 0.1) normalised × 30 / w, or × 50 / w when the human's stored gait +0x1a8 (Characters) is above 2 (jogging or faster) and he holds a set 4 or 6 object or a throwable one.

So a bottle aimed at a target leaves at 30 m/s and a set 4 crate at 12 m/s, each with the lift that brings it down on the target. Nothing clamps the speed; the 45° rule is the only limit on the direction.

A thrown object meeting a human (WorldObject_OnContact, 0x00394050, when the hit body's owner is a human with a body and a brain, +0xd4): ThrownObject_HitHumanTest (0x003928d0) handles two kinds first and the contact ends (answer 0): kind 0x1d deals the human his own record's +0x144 damage with a particle and a sound and clears his body flags 2 and 1; kind 0x22 deals 500 once per object (human +0x564 remembers it). Otherwise ThrownObject_HitHuman (0x00392b88) deals the damage, plays the impact sounds (Sound events), reports the noise and, for a knife that stays whole, sticks it in him (Human_StickThrownKnife). Only that stuck knife makes it answer non-zero, and then the contact's answer is 0x40000 (the object's move stops there) before the break test; for anything else the contact goes on to the break test below, so a thrown object that wears out on a human deals its damage first and breaks on the same contact (confirmed (code) at 0x00392b88 and 0x00394050). With no thrower (no holder and no last holder +0x11c), ThrownObject_HitHuman deals no damage and only plays the material pair (the object's, 26), or (its own, its own) at 1.0 and (its own, 26) at 0.5 when it is breaking. The damage is the type's +0x58 (× the holder's class factor while held), negated against a friend of the thrower (no hurt flag), × 0.25 for kind 0x24 when the thrower's stored gait +0x1a8 is above 2, × 0.5 from an AI of brain kind 3 against class 13, and from a player × 3 against human kinds 0x77 and 0x78 and × 1.25 against 0x87; a knife at its thrower's target or at a non-friend deals the type's +0x58 (the victim's health for kind 0x4f). The victim's reaction (HitReact_FromObject, 0x0026b170, from his pending damage) asks Hit_PickReactionClip with a severity from the object: set 2 → 3 (kind 10) or 2; set 3 → 2; otherwise by the weight +0x62: under 20 → 0, 20-49 → 1, 50 and over → 2. He reacts toward the thrower's side, or toward the object's point for kinds 0x22, 0x1d, 8 and cars (kind 8 plays no reaction).

Wear on contact (OverheadWeapon_ContactDamage, 0x003932c8, confirmed (code) from its assembly). It is the first thing WorldObject_OnContact (0x00394050) does with each contact of the object's own sweep, flying or held (not while its holder is in a car with +0x568 set), with the contact, the holder or else the last holder (+0x11c), and whether the other side is a world object whose counters are already spent. One hit is taken (WorldObject_TakeHit(obj, −1), 0x00393450: +0x10e first for an object with flag 0x4000000 (flying) or a held dyn_cashreg, else +0x10d; 255 never wears):

Object Which contacts wear it
class overhead_weapon (the name at 0x00580ed8), held or not every contact: the level mesh (floor, walls), bodies, humans
material 0x9a (154, GUITAR), any class every contact
a held dyn_cashreg (model 0x3c4e590b) every contact
any other class, held only a contact with a human, and only when the holder's body record's +0x38 is 0 (not traced)
any other class, not held only while its body has flag 1, and not against a world object already spent; with a last holder (a thrower) the flag is then cleared, so it wears once per throw

Body flag 1 is set by Human_ReleaseThrow (0x002586d8, on whichever body the throw made) and ThrownObject_MakeBody (0x003923b8, its only caller being the throw), and at body creation for kinds 0x1d (TYPE_MOVINGVEHICLE) and 0x22 (TYPE_CHATTERBOXTRAIN) (Obj_CreatePhysicsBody 0x00391d48). A knock (message 0x30) does not set it: the dyn_beerbottle the trash props drop (Trash cans and bags) never wears from its fall and stays whole, a bottle to pick up. A thrown_weapon (bottle, brick, ball) has hits 1, so it breaks on its first contact after a throw; an overhead_weapon with 1 (the trash cans and bags, the lawn chairs) on its first contact of any kind, thrown or swung into something while held.

At the end of WorldObject_OnContact, when either counter is 0, the object breaks (WorldObject_Break) and the answer is 0x10000 (the move goes on: no bounce). Before that test a level contact runs WorldObject_OnImpact (0x003939a8) and a body contact the human, car or object handlers above, so their sounds and damage come first. Riot props lists what a break makes.

WorldObject_Break (0x00393e20, called with the contact and the holder), confirmed (code), in order:

  1. A holder drops it (Human_DropHeld).
  2. Message 1 to the object's class with, as popped: the contact normal (contact +0x50) as the direction, the contact point (+0x90) as the hit point, kind −1, strength 100, then two handles. The class does the break: a thrown_weapon (Script types), an overhead_weapon (Trash cans and bags), a dyn_cashreg (Script types); a fade_object pops and ignores it.
  3. The collision body is freed (Obj_FreePhysicsBody); a holder's strike contacts are cleared.
  4. Kind 8 (TYPE_MOLOTOV, only dyn_molotv): an explosion of radius 1.5 m dealing half the contact damage (Explosion_DamageHumansInRadius). No level99 script spawns one and none was live in quick-save slot 8 (the hash 0x183cc260 only in the type tables; confirmed (runtime), read from the state), so no level99 object has it.
  5. Velocity and spin set to zero.

It plays no sound and removes nothing itself: the class's message 1 decides both. Its sounds come from the contact handlers before it: on the level mesh WorldObject_OnImpact plays, for a breaking object, the pair (its material, its material) at 1.0 and (its material, the surface's) at 0.5 at the contact point (else just the second at 1 / (bounces + 1)); against another world object ThrownObject_OnContact (0x00393538) plays (its, its) at 1.0 and (its, the other's) at 0.5; against a human ThrownObject_HitHuman plays its own pairs (above).

The removal time (+0x120). WorldObject_SetRespawnTimer (0x00395d20) sets now + the time and marks the spawn record (bit 0x1000000). The setting 0x0051489c +0x26c (CfgSetGlobalTimeToLive) held 30,000 ms in quick-save slot 6 (confirmed (runtime)), so a thrown or dropped weapon stays 30 s. The streaming walk (ObjectTaskManager_UpdateSpawns) checks it through ObjRecord_ShouldRemove (0x00399718): once the time has passed the object is no longer pickable (flag 0x8000 cleared), stops at its next ground contact (0x8000000), updates every 240 ticks (40 for one class), and its tint becomes white with alpha 0 (+0xcc = 0xffffff00, taken the next update: it vanishes, with no fade); the next check that finds that tint removes it for good. A live object with a removal time that is streamed out is removed, not stored.

How they get into a level

Glass and doors come from level scripts: SpawnBreakableGlass (929 calls in 32 chunks) and SpawnDoor (484 in 46), in the level's set-up after CfgSetDatabaseSizes; nothing else creates a glass_script or door task as far as their creators' callers show (inferred). A pane's geometry is its three corners; a door's is its position, rotation and type, and its model is the type name's (CfgObj +0x8c); both name two triangles of the level's static collision mesh that stand for them. Confirmed (code) for the spawns, counts from the disc.

A pane's life

  1. Spawn (0x0039c0e0): the three corners, the type, the texture coordinates, the flag and the two triangles are pushed, and a pane task named by the type in decimal is made. The initialiser (0x003e29e8) pops them in reverse and drops the flag. Confirmed (code).
  2. Setup: both triangles are made two-sided (0x003a4768) and given material 2 (GLASS, 0x0038f8a8); the sprite rectangle is the type's whole-pane word, except type 14, which makes a sprite batch of its own (0x00020000); type 15 is hidden (colour 0); types 17 and 18 start broken, their triangles disabled, and 18 also sets the path polygon's flag 8 (0x003a47c0). With the alarm, pane bits +0xf8 = 3 (0x0038ec30). Confirmed (code).
  3. Window link (0x0038f8a8): with the window link, the kind-4 link nearest the pane gets the avoid bit (0x00250b68) and, when the pane lies in a path polygon, it and its reverse are retagged kind 0x40. Confirmed (code).
  4. What breaks it: anything that sends message 1. There are no hitpoints: the first hit breaks a pane. The senders are Strike_Contact (0x0021b290), a thrown object (0x00393538) and BreakGlassInRadius (0x003963b8: every pane whose centre is within the radius, and every TYPE_GLASS object). Strike_Contact runs for a human's strike shape touching the pane's body, which covers a landed hit and a jump, and for an airborne human body touching it (Moving into a pane). Confirmed (code).
  5. Hit (message 1, 0x003e2d90), once, while not broken: the broken sprite (or the pane hidden when it is 0), a sub_glass shatter at the pane's centre with the pane's normal (+0x80), shard size 0.2 and size word (sub_stained_glass for type 14), the triangles disabled, its collision body removed (0x003a5340 → 0x0038eec0, +0xe8 = 0), colour 0xffffff70, the pane marked broken. Type 12 also frees every dyn_carstereo within 2 m (0x003a5870), for a pane a script places at a car; a parked car's own windows are its parts, not panes (Cars: windows). Message 0 is a shatter without the broken flag (the effect and the triangles only). Confirmed (code).
  6. Break (0x0038f378, called by the human and thrown-object paths, not by BreakGlassInRadius):

    • with the alarm (+0xf8 both bits): the CrimeScene flag moves to the pane and a break-in (crime type 1) is reported (AI: crimes);
    • with the window link: when the pane is in a path polygon, the polygon gets flag 8 and its 0x40 link loses the avoid bit; otherwise the kind-4 link does;
    • every enabled flag of activity 9 (_xwWindowLook, Flags) within max(width, height) of the pane is disabled (0x00417480), so nobody stands looking through a broken window.

    Confirmed (code). 7. Statistics: a player's (or a player's gang member's) pane counts crime 10 (Statistics), at 0x0021b290 and 0x00393538. Confirmed (code). 8. No respawn: nothing clears the broken flag; a pane comes back only when its level's script spawns it again. Inferred from the handlers (messages 0 and 1 only). 9. ObjDestroy(pane) is not a break: Obj_Destroy (0x00396c58) without its second argument calls the object's vtable +0x4c, which for a pane (vtable 0x00544ec8) is GlassPane_Release (0x0038ed08): body removed, script handler and handle freed, the task given back to the glass manager. No shatter, no sound, no broken sprite: the pane simply vanishes. BreakObjectsInRadius never reaches a pane either (it searches the object manager, and panes live in the glass manager; below). Only BreakGlassInRadius, a strike, a thrown object or an airborne body breaks one visibly. Confirmed (code).

BreakObjectsInRadius

BreakObjectsInRadius(object, radius) (World_BreakObjectsInRadius 0x00396390 → World_BreakObjectsAround 0x003961d0): takes the object's position, reports a crime there when the object's type has class byte +0x86 = 30, finds up to 384 tasks of the object manager within radius of that point (ObjectManager_FindObjects) and sends each message 0x15, the given object included (the "skip myself" argument is 0 on this path). What 0x15 does is up to each type: a door takes a hit two updates later (Doors), a dyn_molotv breaks itself (Script types: the Molotov). So the scripts' ObjSpawn("dyn_molotv", flag position) then BreakObjectsInRadius(molotov, 0.5) is a way to set off a molotov explosion at a point. Confirmed (code).

Moving into a pane

A whole pane is two things to a moving human: its two triangles in the level's collision mesh and its body (+0xe8), a box in the physics world. The triangles are an ordinary wall; the body is what breaks. Nothing here depends on the pane's type, its alarm or its window link: every pane gets the same body (0x0038ed68), and the only type tests in the pane's code are the spawn and hit cases of A pane's life. Confirmed (code) unless marked.

The triangles. Material 2 (GLASS) has no rule of its own in the human's wall sweep (0x00347c08 never reads a triangle's material) or in its contact handler (Human_OnContact, 0x00219d50, whose level-triangle branch tests only the fence materials 30, 31 and 122 while climbing, Characters). Both triangles are two-sided, so while they are enabled a pane stops a human from either side like any other wall face 0.25 m or taller. The hit disables them (message 1, A pane's life), and the sweep's resolution skips a contact on a disabled triangle (0x0033d9d8 checks the triangle's enabled bit), so a pane broken during a sweep stops blocking at once.

The body. GlassPane_CreateBody makes a box of half-extents width / 2, 0.25 and height / 2 (0x00342dc0 halves width, 0.5, height), posed by the pane's matrix, so it stands 0.25 m proud of the glass on each side and covers the whole pane. Its body flags (0x0033d070) are 0x8000007a: bits 0x2, 0x8, 0x10, 0x20 and 0x40. Confirmed (code) at 0x0038ed68, 0x0033d070 and 0x00342dc0. Two paths reach it.

  1. A strike shape (the jump, a punch or kick). A shape of the human's body is a strike shape while its flag 0x2 is set. The human's strike-on and strike-off messages (0x00247fc0 / 0x00248110, from Human_HandleMessage) go through Human_SetStrikeShape (0x0021c030) to 0x003428d0 (confirmed (code)), which keeps a bit mask in body +0xc0 (a bit per bone byte +0x31, from bone 2) and sets or clears flag 0x2 on the shape of that bone (shape vtable +0x1c). In the jump it is the body's own capsule: its flags read 0x11 on the ground and 0x13 from the launch to the landing (runtime, below). Human_TestStrikes (0x0033f110) runs once per update, after the move (0x0023fea8 calls the move 0x0023d8c8, then it), while body +0xc0 (or +0xd0) is non-zero. It gathers bodies near the human whose flags hold 0x18; for each, a ray from 1 m above the feet to the body's centre must hit nothing in the level mesh, the ray excluding material 2 (its exclusion list is {2, 1}) and triangles with flag 0x8 (and 0x40, unless the target is a human's body, body +0x44 bit 0x40), so glass never hides a pane from a strike. A body gathered with flag 0x8 or 0x10 (mask 0x18, so 0x20 alone is never gathered) that the human may strike (human vtable +0x10c, not traced) is then swept against each strike shape (shape flags 0x1 and 0x2, Physics Unit 10), and the first that hits calls Strike_Contact (0x0021b290). For a pane that is: the noise event (0x002936a8, 30 m), Glass_Break (0x0038f378), a player's crime-10 statistic and then message 1 (A pane's life, steps 5-7).
  2. The airborne body. The move's sweep asks the physics world for bodies whose flags meet a mask the human builds (0x0033d498, confirmed (code), for a body with +0x44 bit 0x40, a human's): 0x4 on the ground; 0x44 while airborne (object flag 0x4000000) or while record +0x08 holds 0x800; plus 0x20 while the sweeping shape is a strike shape and 0x40000 above gait 3. A pane's body has 0x20 and 0x40 but not 0x4 or 0x40000, so only an airborne or striking human's sweep meets it: walking and running on the ground (capsule flags 0x11) never touch the body, only the triangles. On the contact, Human_OnContact (object branch) goes on when the body has 0x40 and the human is airborne (or record +0x08 0x800, or the landing state flag 0x2000000000), or the body has 0x20 and the touching shape is a strike shape; it calls Strike_Contact (vtable +0x104) once per body (the body's contact list, vtable +0x34 / +0x44) and, because the pane's type flags hold 0x400 (0x0038f588), returns 0: the contact is ignored and the sweep goes on as if the box were not there.

So a pane breaks for a human who is in the air, or whose strike shapes are on, as soon as his body or a strike shape reaches the box 0.25 m in front of the glass; there is no minimum speed and nothing slows him. On the ground with no strike, the glass is a wall. Thrown objects use 0x00393538 (A pane's life).

At runtime (confirmed (runtime), PCSX2 2.9.94, a copy of slot 9, 2026-10-07; scenario glass_jump, stick at 100 % straight ahead, hooks on Strike_Contact and Glass_Break; their updates re-labelled one update later after the recorder fix, Recording a trace). Slot 9 stands Rembrandt at (63.6, −1.9, 4.20), heading 88°, on the roof of level99 checkpoint 3.4, facing the window: two type-11 panes in the plane x = 47.4, side by side (centres y 0.245 and −2.77, a 4 cm mullion between them at y −1.27 to −1.23), from z 4.36 to 7.54.

Run What happened
triangle at 51.4 m (update 60), launch at 9.5 m/s the jump clip 434 from update 60; from the launch (update 61) to the landing (update 84) body +0xc0 is non-zero and the capsule's flags are 0x13 (0x11 before and after); on update 72, feet ending at x 48.07 z 5.25, Strike_Contact called from Human_TestStrikes (return 0x0033fddc) for both panes, each then Glass_Break (return 0x0021bb7c); x kept falling by 0.26 m per update (7.8 m/s) through the window, landing at x 44.95 on update 84 (clip 435) and stopping at 44.38
no triangle: he runs off the roof edge at 49.6 m falling (clip 428), feet at z 3.71 on update 73: Strike_Contact from Human_OnContact (return 0x0021a0e0) for both panes and Glass_Break for each; the panes broke, but his body was below the sill and stopped at x 47.95 against the wall under the window (0.55 m from it) and fell to the street (z 0.22)

So the original's jump goes through the window on the jump's strike shapes, not on the airborne body path; both break the pane, and the airborne path alone is enough when the body reaches the box. In level99 this is the only thing that opens the window: the script's checkpoint 3.4 code breaks no glass (inferred: no BreakGlassInRadius or pane message in level99_lesson2.lua's P3 functions).

Drawing a pane

A pane has no model: it is one textured quad, a sprite of the resource manager's sprite batches (GUI: sprite batches), drawn in the translucent 3D sprite pass after the world. Confirmed (code) unless marked.

Every frame, before the draw (0x0038fbe8 → GlassPane_UpdateBodyByDistance, 0x0038f1c8, for each pane): +0xd0 = the squared distance from the centre to the nearest player camera (0x00120230); then

  • within 50 m (+0xd0 < 2500): a hidden pane (flag 4) is shown again; a shown pane whose colour is still 0x808080e0 (whole) gets its collision body back if it has none (0x0038ed68: a box of the pane's width and height in the collision world 0x00597198, posed by the pane's matrix);
  • beyond 50 m: a shown pane is hidden; a hidden whole pane loses its body (0x0038eec0).

So only whole panes within 50 m of a camera have a body. (One game state, 0x0051489c +0x33a = 4 under a mode check not traced, skips the distance test and keeps every whole pane's body.)

Queueing (GlassManager_QueueDraws, 0x0038fa50, from TaskManager_UpdateManagers, once a frame): for each pane, GlassPane_QueueDraw (0x0038ef60):

  • near, +0xd0 < 12 (within √12 ≈ 3.46 m): the pane goes on the resource manager's near-glass list (0x0018b008, +0xc54) and is drawn on its own after the batches (below);
  • otherwise: when its batch (+0xb0 high half) is resident and the pane has a body (vtable +0xf4, which returns +0xe8), one sprite is added to the batch (0x00183038): the rectangle's u0, v0, u1, v1 from the batch's sheet (0x00181e38, rectangle = +0xb0 low half), the colour +0xb4 as RenderWare {R, G, B, A} (0x808080e0 → 128, 128, 128, 224, GUI: sprite colours: RenderWare's 0-255, so half brightness, 88 % opaque), and the matrix built from +0x70-+0xaf (0x003368d8).

The quad. The matrix's rows are right = W, up = H, at = the normal, position = the centre, each turned into RenderWare's axes as (x, z, −y). The near path (Instance_DrawOneSpriteIm3D, 0x001831c0) makes the four corners as matrix × (±0.5, ±0.5, 0, 1) (the constants at 0x00552850), so in game space the quad is

centre − W/2 − H/2 = corner     → (u1, v1)
centre + W/2 − H/2 = cornerV    → (u0, v1)
centre − W/2 + H/2 = cornerU    → (u1, v0)
centre + W/2 + H/2              → (u0, v0)

drawn as one 4-vertex triangle strip (0x004a4f28 with 4 vertices, 0x004a49e8(4)), every vertex in the pane's colour. The script's own texture coordinates (+0xb8-+0xc4) take no part. The batched path hands the same matrix, rectangle and colour to the PTank, which builds its quad itself; that it gives the same corners is inferred.

Render states. Near path, set in 0x001831c0: the batch's texture raster, vertex alpha on, Z test on, Z write off, source blend source alpha, destination blend inverse source alpha, fog off (RenderWare states 1, 12, 6, 8, 10, 11, 14). Batches (ResourceMgr_Render3DSprites, 0x00185b38, per viewport after the world and before the ground rings): Z write off, culling off (states 8 and 20), Z test left on; each batch blends source alpha / inverse source alpha with vertex alpha (0x001972b0 writes 5, 6 and 1 into the PTank's data). Back faces are drawn either way: no cull state is set on the near path, and the batches turn culling off.

Order. The batches queued this frame are sorted by their key, farthest first (comparator 0x00184850: the camera distance² − radius²; 0x00197000), and drawn; then 0x0018b058 draws the near list in pane order, unsorted (after a list of other near sprites, 0x0039b6b8). Panes inside one batch keep the pane manager's order. So glass is drawn after the opaque world, back to front between batches, not sorted among themselves.

Which sheet. Every pane but type 14 draws from batch 0, one of the thirteen batches the resource manager makes at start (0x00184918): sheet part_page1 (sheet-table record 1, Particles), matrix format (format 2), 640 sprites, the 3D sprite pass. The init clears +0xb0 and sets only the low half from the type's table word (Task_SetRect), so the high half of CfgSetGlassProperties' words is ignored. The disc's words for types 0-18 are rectangles 20 (whole) / 21 (broken) of part_page1, or 22 / 23 for types 10, 11 and 16; types 17 and 18 use 21 for both (Glass types). Type 14 makes a one-sprite matrix batch of its own (PTank_New(max(width, height), 0x20000, centre, …), 0x003e2c84) over sheet-table record 2, rectangle 0, whose frame is placed at the pane's centre; no level places a type-14 pane.

A broken pane is not drawn. The hit sets the broken rectangle and colour 0xffffff70 (white, alpha 112) but also removes the body, and both draw paths need the body, while 0x0038f1c8 never gives a broken pane (colour no longer 0x808080e0) a body again. What the player sees is the shatter's shards and then an empty frame. Confirmed (code) as a chain of the reads above; not checked at runtime. The broken words matter only for types 17 and 18, which start with the broken rectangle as their whole sprite, keep the colour 0x808080e0 and so get a body (and are drawn) within 50 m although their triangles are off. Type 15 (colour 0, made with a body at spawn) is drawn, invisibly.

The shatter

sub_glass (0x003e4cb8) runs once and ends. Confirmed (code):

  1. Count = 10 × width × height (whole metres from the size word); a pane under 2, or exactly 1 × 1, uses 10 shards of size 0.06 and the sound of materials GLASS_SMALL × GLASS_SMALL (88); others the sound of GLASS × GLASS (2). The sound is the material pair's entry in the sound matrix (Sound).
  2. Culling: no shards unless some active camera is within 15 m of the pane's centre (Cameras_IsWithinRange(15, p), 0x003a5280: the smallest squared distance to any view's camera, Cameras_MinDistanceSq), and the centre is in some player view with a 10 m margin (Cameras_IsPointVisibleAny(10, p), 0x003a51f8 → Camera_IsPointInPlayerView 0x003a50e0: the point fails only when it lies more than 10 m outside one of the view's six frustum planes), and the particle budget (0x003a5a50) allows; game state bit 0x20 (0x0041cf30) forces them. Both tests are about cameras, never player 1's position: in a scene the scene camera is the view's camera, so panes near the scene camera shatter wherever the player stands (inferred: the scene camera is what the view's camera answers while a scene plays). Confirmed (code) at 0x003e4cb8 for the arguments 15 and 10.
  3. Shards: for each of the count, two tries, each taken at 2 in 3 (Random_Int(2) < 2, Random_Int(n) giving 0 to n): a glasstest particle at a random point within ±4/7 of the width (whole metres) and ±4/7 of the height on the pane's plane (0.5714286), turned by the shatter's rotation. Each gets the shard size (0.2 from a hit, 0.06 for a small pane), a count of miniglass pieces (2-4; 0-2 when the count is over 40; 0-1 over 79) and the shatter's sprite word, which is 0 (batch 0, the particle default, 0x0039aef0). Over 79 the code writes 0x4f into the data's size word, but the loop count is already taken, so every shard is still made.

A shard (glasstest, script type 62: GlassTest_Init 0x003e44e0, GlassTest_Update 0x003e4838), confirmed (code) unless marked:

  • Sprite: rectangle 24 + Random_Int(4), so 24-28, of the batch in the passed word's high half: batch 0, part_page1.
  • Size: the shard size × a random 0.1-1.5 (+0xc0). Colour +0xb0: white with alpha 128-224 (Random_Int(96) − 128 as 0xRRGGBBAA).
  • Motion: flags 0x14000001 (airborne; bit 0x10000000 cleared under game state bit 1); gravity −9.8 in its data block; a random rotation; an initial velocity with a random 1-2 m/s upward part (the rest, from the shatter's rotation, not traced).
  • Each update (every 2 ticks): ended at once when it fails the 10 m and 15 m tests or the particle budget (as the shatter's culling, also when its data word +0x24, not traced, is over 119), unless game state bit 0x20; otherwise a random ±1 m/s per axis is added to its velocity and its second colour +0xb4 becomes a random grey 64-255 in all four bytes (a glitter; which colour the particle draw uses is not traced).
  • Landing: when its flags gain 0x20000000 (inferred: set by the particle's ground contact) it makes its count of miniglass particles around its position (their random speeds and sizes are not summarised here) and ends. A shard therefore lives from the hit until it lands, with no timer of its own.

How the particle draw blends a shard is on Particles; miniglass is not traced.

Swinging doors: states and commands

The state byte (data +0x28), confirmed (code) at 0x003fbba0, 0x003f9770:

State Meaning Next
0, 1 closing; the update adds 1 at 2 the leaves are reset to the closed pose
2 closed
3 opening (the leaves swinging) 4 on the next update
4 collision off (state command 5), then 5
5 open; IsDoorOpen (message 0x0c) answers true only here
6 broken: the update returns 1, so the task ends
10 the cabin doors at rest

The door updates every 28 ticks (0.47 s); opening reschedules it for the next tick, so collision goes about 29 ticks after the swing starts (inferred from the scheduling, Tasks).

State commands (message 0x22, ObjectChangeState; the bindings below send them):

Command Effect
2, 8 (OpenDoor) when not pickable: reset the leaves, angle 170°, state 3, the open sound
3, 7 (CloseDoor) angle 0; from state 2 only a reset to state 0, otherwise swing back with the close sound and state 0; then as 6
6 triangles enabled, the object's collision body back (0x003a52c8), the number's links get the avoid bit and its hole loses path flag 8 (0x00250d00, A door's hole)
5 (DisableDoorCollision) the number's links lose the avoid bit and its hole gets path flag 8 (0x00250c50), triangles disabled, the collision body removed (0x003a5340)
10 / 0, 11 (SetDoorPickable) pickable on / off (Lock picking)

Messages, confirmed (code) at 0x003fb8d0:

  • 0x42 (DoorOpenDegree): when not pickable, angle = the argument, swing, state 2 → 3, the open sound; when pickable, the angle is only kept at data +0x04.
  • 0x0b (DoorOpen(door, human)), only when not pickable and closed (state 2): the leaves reset, the angle is ±170°, away from the human (the sign of the dot product of the door's turned axis with the human-to-door direction), or the kept data +0x04 angle after a state 5; state 3, the open sound; pickable cleared.
  • 0 with a human: Door_OpenAnimated(door, human), the human's object target becomes the door (0x00227080) and its action code 26 (0x1a, 0x002266a8). Action 26 plays anim 666 ANIM_SPECIAL_OPEN_DOOR, and its event opens the door away from the human. Confirmed (code); the steps are in AI and the world: Doors.
  • 0x15 (destroy, what BreakObjectsInRadius sends): flag 0x40 and update interval 2; the update then raises the interval by one each time and, on reaching 3, sends the door a hit (message 1) from itself.
  • 0x19: hitpoints = the argument. 0x20: calls each leaf's vtable +0x4c (not dyn_door_stall). 0x3e: answers the angle.

Swinging: DoorSwing_SwingTo (0x003f8fd0) sends each leaf message 0x35 with its target rotation: the door's rotation turned by the angle about the vertical axis (half-angle quaternion), the right leaf by the negated angle from its turned base. Confirmed (code). How a leaf turns there is under Leaves.

Leaves: model, hinge and swing

The frame draws nothing. The dyn_door_swinging object never calls Obj_SetModel (its init leaves the model word +0xc4 at 0 and DoorSwing_SetUpType only spawns leaves) and its flags 0x405 include 4, hidden; what is seen of a door is its leaves, and of a type with no leaves (_dblwood, _woodbrd, _woodp, _steel...) nothing but the level's own geometry around the doorway. Confirmed (code) at 0x003fb5f8.

A leaf (sub_swinging_door, 0x003fb330) is made by Obj_SpawnChildByName(name, position, rotation, door), which pushes the pose and the door's handle; it is not attached to the door (no flag 0x10). Its init pops the door's handle (data +0x10), the rotation (into +0x20 and data +0x00) and the position (+0x10), sets flags 0x1021, an update every 28 ticks, and its own type's model (Obj_SetModel(leaf, 0), the dyn_dr_* record's model, word at +0xc4; the model hash 0xb14109ba also sets flag 0x80). Confirmed (code).

The hinge is the model's origin. A leaf only ever changes its rotation, about the vertical axis through its position; the position stays where DoorSwing_SetUpType (0x003f80f0) and DoorSwing_ResetLeaves (0x003f9368) put it:

Leaf Position (the hinge) Rotation when closed
first (left, +0x124 = 2) the door's position the door's rotation
second (right, +0x124 = 4) door position + door rotation × (−2w, 0, 0) the door's rotation × 180° about z

where w is the door type's CfgObj argument 15 (f15, type +0x68, read by 0x003a3898 property 5). Confirmed (code). On the disc w is one leaf's width: dyn_door_store 1.09 with a collision box 2.18 wide (two leaves), dyn_door_chainlnk_a 2.29 for its one 2.31 m leaf, dyn_door_big_gate 4.43 with an 8.86 m box and 4.43 m leaves. So the two hinges are a whole doorway apart, the second leaf faces back toward the first, and each leaf's model extends from its origin along its local −x to about −w, meeting the other at the middle: inferred from the spacing, from the lock-pick glint at door position + door rotation × (−w, 0, 0) + 1.3 m up (the seam of a two-leaf door, the latch edge of a one-leaf one, 0x003f9588), and from the dyn_dr_store collision centre at x = −1.09; the model bounds themselves were not checked. A one-leaf door (_chainlnk_*, _red_fence, _sheetmtl, _corr, _ornate_single, _stall, _woodfnce_xl) has only the first leaf, hinged at the door's position.

The swing: a linear slerp over one leaf update (28 ticks, 0.467 s). Confirmed (code):

  1. Message 0x35 (0x003fb430) stores the target in data +0x00-+0x0c, sets data +0x14 = 1 and reschedules the leaf for the next tick.
  2. On that update the integrate step runs first (Task_Integrate, 0x003a2310): with flag 0x1000 it copies the previous target +0x40 into the rotation +0x20 and stamps the update time +0x50 (confirmed (code)). Then the leaf's update (0x003fb5a0) copies the new target into +0x40 and clears data +0x14.
  3. Between updates the object's pose "now" (Obj_GetPoseNow, 0x003a1ad8, flag 0x1000) is Quat_Slerp(t, +0x20, +0x40) with t = (time since +0x50) / the update interval in seconds (+0x64, 28/60), clamped to 0-1 (confirmed (code)). Quat_Slerp (0x00336a00) takes the shorter arc (it negates the second quaternion's weight when their dot product is negative) and falls back to a plain lerp when they are within 1e-6.
  4. The next update, 28 ticks later, makes the target the rotation, and the leaf rests.

So a leaf turns at a constant angular rate from where it was to the target in 0.467 s: 170° at about 364° per second, no ease-in or ease-out, whatever the angle. A new target that arrives mid-swing starts from the previous target, not from the in-between pose (step 2), so the leaf jumps to the old target first. That the drawn model takes this pose "now" is inferred: it is the only place the slerp happens. The door's own state steps (opening, state 3 → 4 → 5, states) run on the door's 28-tick schedule independently of the leaves.

How a human opens a door

  • Scripts: OpenDoor / CloseDoor / DoorOpenDegree (no human), DoorOpen(door, human) (away from the human), OpenDoorAnimated(door, human) (the human's state 26 with the door as target).
  • Use: a player whose current interaction record (human +0x660) names the door and has a kind other than 0, 2 or 3 sends the door message 0 on triangle (0x0024d530, the context action of Characters: jumping), so the same animated open. Confirmed (code) for the dispatch; what puts a door in the record is not traced.
  • The AI: a door's number marks its links (Doors and their numbers); a closed door's links carry the avoid bit, an open one's do not. AI humans do not open swinging doors by themselves as far as the route follower shows (its 0x10 and 0x40 handlers jump or charge). Inferred.

Breaking a door

A hit is message 1, the same as a pane's: a direction, a point, an int, the hit's kind and two handles (the attacker). The human path (0x0021b290) sends kind 0 for a plain hit, 2 while the attacker's record +0x08 has any of 0x1400000 (0x1000000: the run attack, the charge and, at runtime, the walk attack 23; 0x400000: the dive) and 3 while airborne (object flag 0x4000000); a thrown object (0x00393538) sends 1 (3 for TYPE_MISSIONTV, 0 for an object of animation set 5 other than a molotov). Every door and barrier takes 4 + 6 × kind hitpoints: 4, 10, 16 or 22. Confirmed (code) at 0x003f9ad0, 0x003fa438, 0x003b2180; the kinds of the moving attacks confirmed (runtime) on a fence (Barriers).

  • dyn_door_store (TYPE_BREAKANDENTER_DOOR, 1 hitpoint): the first hit plays sound 0xcf6586b2 and the GLASS × GLASS pair, triangles off, and the door sends itself 0x0b (the human pushed is inferred to be the attacker): it swings open away from them. Hitting any TYPE_BREAKANDENTER_DOOR also reports a break-in (crime type 1) at the attacker (0x0021b290). Confirmed (code).
  • Cabin doors (12): each hit plays 0xcf6586b2; below 7 both leaves are flagged broken and one, at random, takes its next model (message 0x19) with a small burst (0x003c6038); at 0 or below: triangles off, two loose pieces (dyn_cabin_aa, _bb or _cc), state 6. Two plain hits break the first leaf, three the door. Confirmed (code).
  • dyn_door_liz (100), dyn_door_dclub (20), dyn_door_stall (10): every hit makes dust (0x003c57d8, radii 2.75 and 3.75) and 20 splinters (0x003c5b00), and plays the type's material against CONCRETE. liz swaps its model at 80, 60, 40 and 20 % (with material sounds 35, 36, 106 and its own); dclub at 50 %. At 0 or below (stall on its first hit): triangles off, the number's links opened, alpha cleared, more dust and splinters, two wreck pieces (dyn_dre_liz_e/_f, dyn_dre_stall_a/_b, dyn_dre_dclub_c/_d), state 6. Confirmed (code).
  • Other swinging doors ignore hits.

Breakable barriers

dyn_door_fence and the classes beside it are single objects, not leaves: SpawnDoor with the same arguments; the triangles get two-sided, 0x40 and 0x400, the number's links are retagged 0x40 at spawn, and the update interval is 60 ticks. Hitpoints from the type, written to data +0x04 and object +0x128. A hit (0x003b2180) takes 4 + 6 × kind and makes dust and 20 splinters (confirmed (code)); one that leaves hitpoints does nothing more. At 0 or below: both triangle sets off, the number's links opened (0x00250c50), then by class name:

  • dyn_door_wall_a: a sound, flag 0x800000, the damaged model 0x7f97c350 and its collision body removed (Obj_RemoveBody, 0x003a5340; confirmed (code));
  • dyn_door_wall_b: sounds only;
  • the others (dyn_door_fence, dyn_door_vargas): more dust (unless bit 2 of game state word 0, 0x0041cf30), a sound, and three dyn_wooddmg_a/_b boards (unless bit 2 of word 3), each sent messages 0x19 and 0x30.

Then the type's material pair sounds (unless bit 4 of word 3), and the barrier either marks itself broken (data +0x00 = 1) and hides (flag 4), or, for dyn_door_vargas, which makes a second object at spawn (data +0x08), destroys that object (message 0x15), takes model 0x3b4fefad and loses its body. Message 10 sets whether it can be hit (0x003a17e8), 0x19 its hitpoints. Confirmed (code) at 0x003b2180, 0x003b2f40, 0x003b3158 for dyn_door_fence; the other five classes are alike by their shared calls, not read in full.

Message 2 is the removal. The class's update (0x003b3220, every 60 ticks) only answers whether data +0x00 is set. When it is, WorldObject_Update (0x00395b70) calls the object's vtable +0x4c, WorldObject_Remove (0x00391c10), which sends the object's own handlers message 2 with +0x00 = 0 (a script's callback gets (self, NilHandle), Scripts), takes it out of its spawn record for good (0x00398cb0, record bit 0x40000) and deletes it (0x00391b08, which frees its collision body). So a broken fence's script hears of it on the fence's next update, up to 1 s after the hit, and until then the hidden fence keeps its body. Nothing in the barrier's hit sends a message itself. Confirmed (code). Every world object removed this way sends message 2, which is why the script event list reads it as "finished or broke".

At runtime (confirmed (runtime), PCSX2 2.9.94, slot 10, level99 checkpoint 3.5; scenario charge_fence): the fence (type index 520, centre (23.6, −6.67)) had 10 hitpoints and broke on one charge, dive or walk-attack hit (kind 2). Its three boards were removed on their own first update, in the same update as the hit (the pair's second tick; type indices 1368 and 1369, along the fence at x 22.7, 23.7 and 24.6); why is not traced. The fence was removed 3, 10 and 17 updates after the hit in three runs (the hit re-labelled one update later after the recorder fix, Recording a trace), and 21.5 and 27.5 updates (43 and 55 ticks) in two more (fence_dealer, Scripts, item 44): anywhere from 0 to 29 updates, by the fence's 60-tick phase. In the same update the objective marker went, and 15 updates later P3.DealerPoizo's placement put the player at (18.5, −16.3), beyond the fence. Breaking in by charge: Combat.

Lock picking

The door side, confirmed (code) at 0x003f9588, 0x00397078:

  • Pickable on (state command 10, SetDoorPickable(door, true), and at spawn for dyn_door_chainlnk_pick, dyn_door_storeb and dyn_door_templedoor), only while its angle (data +0x00) is under 2: a sub_triglint glint at door position + door rotation × (−w, 0, 0) + 1.3 m up (Leaves), drawn while within 30 m (0x003eb0d0); door object +0x124 = 10; its collision body removed (0x003a5340).
  • Pickable off (commands 0, 11; SetDoorPickable(door, false)): the glint destroyed (message 0x15), +0x124 = 0, the collision body back (Obj_AddBody, 0x003a52c8; confirmed (code)). SetDoorPickable(false) first stops every human whose object target is the door (0x0022e400, state flag 0x20000000).
  • While pickable the door refuses OpenDoor, DoorOpen and DoorOpenDegree (which keeps its angle for later).

The minigame (CfgSetLockPickHandler's callbacks, pick_lock.anm) is not traced here.

Doors and their numbers

SpawnDoor's last value is the door's number in its level (1-27; 484 doors in 45 levels, unique per level). It ties the door to the navigation links carrying it (Navigation links):

  • the breakable doors retag them kind 0x40 at spawn (0x00250c00, through 0x003a4b30; confirmed (code) at the five initialisers below, which call it, and from DoorSwing_SetUpType 0x003f88c0; which swinging types reach that call is inferred): the classes dyn_door_fence, dyn_door_bar_bani, dyn_door_bnstr, dyn_door_fence_o and dyn_door_parapet (initialisers 0x003b2f40, 0x003b3250, 0x003b4128, 0x003b57d0, 0x003b6220), and the swinging types dyn_door_dclub and dyn_door_liz;
  • opening (state command 5) or breaking clears their avoid bit and sets path flag 8 on the door's hole; closing sets it and clears the flag (A door's hole).

DisableDoorLink / EnableDoorLink do the same by position for the nearest 0x10 link within 5 m and its reverse. Confirmed (code).

How a closed door stops a human

A closed door is two triangles of the level's own collision mesh, not a body. SpawnDoor names them (its fourth argument, two triangle indices), and the door's init writes their flags in the live level mesh (the world manager 0x005147c4 → +0x40 level → +4 mesh): DoorSwinging_Init (0x003fb5f8) calls Collision_SetTriangleTwoSided (0x003a4768, flag 0x2) on both, CollisionMesh_OrTriangleTypeBits (0x003a4a40) with 0x400 (and 0x800 for the cabin doors), CollisionTri_SetFlag40 (0x003a49e8) with hitpoints, and CollisionTri_SetMaterial (0x003a4a98). Confirmed (code). Collision_SetTriangleTwoSided has ten callers, every one an object init that owns triangles: the swinging door, the barrier classes (0x003b2f40, 0x003b3250, 0x003b4128, 0x003b5498, 0x003b57d0, 0x003b6220), dyn_door_barricade (0x003f6018), dyn_door_sliding (0x003f5d10) and the glass pane (0x003e29e8). Nothing else is needed: the walking sweep meets them like any wall (Characters, Physics), and it tests no type bit, so 0x400 and 0x40 do not exempt them. A dyn_door_swinging type has shape NONE (the steel doors, the gates) or an OBB (dyn_door_store, the cabin doors), but no PHYFLAG that a walker's sweep selects, so the triangles are what stop him. Inferred from the layers (Physics); the per-type flags were not read at runtime.

Why two-sided matters. On the disc a door's triangles are ordinary one-sided triangles: of the 150,567 triangles of the 64 level files only 4 carry flag 0x2 (Collision). The walking sweep skips a one-sided wall whose front the sphere is behind (0x00347edc), so before the init a doorway is a wall from one side only. In level95 the clubhouse's five exits (dyn_door_stlslot numbers 4, 10, 11, 16 and 22) are such pairs; door 10 (triangles 729 and 730, at x −187.63, y 93.99 to 96.16, z −194.31 to −191.99) faces −x, out of the clubhouse, so from inside the player is behind it. Read from the disc (2026-10-10, level95.lev and the SpawnDoor calls of level95.lua, numbers only). The init's two-sided bit is what closes the doorway from both sides.

At runtime (confirmed (runtime), PCSX2 2.9.94, 2026-10-11, a level95 state outside the clubhouse with every exit closed, read over PINE through the world manager's mesh): triangles 729 and 730 carry flags 0xf443 and material 13, where the disc has 0xf001 and material 0. That is enabled, two-sided 0x2, 0x40 and 0x400, with the disc's bits 12-15 kept, and the type's material. The live mesh (5,943 triangles) has 202 two-sided triangles, alike in two saved states and live: 703-746 (the doors and barriers, 0xf443 or 0xfc43, 0x800 on 703-714) and 3611-3768 (0xf003, material 2 GLASS: the panes). No triangle was disabled.

When it opens. Only the door's own state machine switches the triangles: state command 5 (DisableDoorCollision, also step 4 of an opening, states) clears their enabled bit, command 6 sets it; a breakable door or barrier clears it when it breaks (Breaking a door, Barriers). A script that opens a hangout exit (OpenDoorAnimated in level95.lua's exit handler on the fClubexit_<n> flag) goes through that same machine. Confirmed (code) for the commands at 0x003fbba0; the exit handler's order of calls is read from the script's constants, inferred.

Breakable props

The street props level34's riot meter counts (Scripts) are world objects (run-time type 0x08; a glass pane is 0x400, Tasks) of two classes. Values from the CfgObj lines of config_preload3; behaviour confirmed (code) at the cited addresses unless marked.

Type Class Hit points +0x58 Hits +0x5a Model hash
dyn_newsstand_a dyn_masks 0 1 0x18f31e91
dyn_newsstand_b dyn_masks 0 1 0x81fa4f2b
dyn_crate_stack dyn_masks 0 1 0x1a0686f7
dyn_parkbench_a dyn_masks 10 2 0x7852cedb
dyn_trashcan overhead_weapon 150 1 0xfbf3e3ae
dyn_gbags overhead_weapon 80 1 0x62502b03

ObjType_GetIntField (0x003a37b8) reads these fields (0: +0x58, 1: +0x5a, 3: the material +0x64).

  • Two counters on every world object: +0x10d and +0x10e, from the type's +0x5a and +0x5b (a 0 becomes 0xff, unbreakable; 0 means broken). WorldObject_TakeHit (0x00393450, kind k) takes one from +0x10e first while the object is airborne (0x4000000) or a held 0x3c4e590b; otherwise +0x10d loses 1 when k is −1 or +0x128 is 1, else 4 + 6k (floored at 0).
  • A strike (Strike_Contact, 0x0021b290) on a world object: kind 3 when the attacker has state 0x4000000 (Combat), 2 with something in hand (held flags 0x1400000), else 0. In order: notes whether either counter was already 0, WorldObject_TakeHit, the impact sound, burns the attacker within 1.5 m of a 0x1c21bdc3, message 6 to the boxes only if the object was intact before the blow (Scripts), Glass_Break if it is a pane, a crime report for class code 0x1e, then task message 1 to the object with (attacker, attacker, k, 0, contact point +0x90, direction +0x10).
  • dyn_masks keeps its own data: +0x00 broken, +0x04 path flag cleared, +0x08 update interval, +0x0c hit points (+0x58, −1 when 0), +0x10 hits (+0x5a, −1 when 0; +0x128 = 1 when set and +0x0c is not), +0x14 a child. DynMasks_Init (0x003b7538) sets flags 0x200001, interval 20, path-polygon flag 8 under it, and copies +0x0c into +0x10d. Its message 1, DynMasks_OnHit (0x003b7a88): both counters −10 → ignored; +0x0c not −1 → +0x0c −= 4 + 6k, mirrored to +0x10d; else +0x10 loses 1; below 1 → broken (also kind 3 with object-flag bit 4, or model 0xf21e6a91 with argument −1). Every hit sets object flag 0x800000, plays the material sound (survive: the material with 5; break: the material pair) and, when a visibility test passes (40 m and 10 m arguments; inferred to be distance and screen), dust; then a per-model effect. No velocity is given to the prop itself.
  • What each breaks into: the newsstands a sound and five paper debris particles, no piece; the crate stack a sound and one piece object at −0.604 m, turned at random; the bench 25 splinters, a dust cloud and one piece at −0.395 m. The trash can and the bags have no case. A broken prop then looks for a damaged model (Model_FindByHash): if one exists it takes it, re-reads both counters from the new type and keeps its body; if not, it loses its collision body (Obj_RemoveBody), the low byte of +0xcc is cleared and path flag 8 under it is cleared once.
  • Removal: DynMasks_Update (0x003bf548) answers "done" while +0x00 is set, so WorldObject_Update removes it on its next update (up to 20 ticks): message 2, spawn record bit 0x40000, never spawned again (Barriers has the path). Messages 4 and 0x15 set flag 0x40 instead; the update then counts data +0x08 up, using it as its interval, and when it reaches 4 sends the prop a kind-0 hit of its own (message 1, from itself; where the count restarts is not traced). Message 0x30 knocks a piece: velocity a random 4-6 × its horizontal part (vtable +0x74), a spin (+0x7c), flags |= 0x4200000 (airborne; Physics flies it).
  • Counting (inferred from the code above): a newsstand or crate stack breaks on the first blow of any kind, so it sends message 6 once. The bench takes three bare-handed blows (10 → 6 → 2 → broken) or one armed blow, and each blow while intact sends message 6. A strike on a trash can or bags takes +0x10d from 1 to 0, so only the first counts.
  • overhead_weapon objects are the throwables: OverheadWeapon_ContactDamage (0x003932c8) takes one point (WorldObject_TakeHit(-1)) per damaging contact while flying or held. Message 1 breaks one at once, whatever its counters say: Trash cans and bags.
  • F.Vandalize (level34.lua) tells the newsstand apart by name, not by type bits: GetRTTI 8 and GetObjectName dyn_newsstand_a or _b give 3 points, other 8 give 1, 1024 (a pane) gives 2.

The riot props' hit effects

What DynMasks_OnHit (0x003b7a88) does for the four dyn_masks riot props above. Confirmed (code) at that address; the materials, piece names and next models are confirmed (runtime), read from level99's object database and Object List (the hashes are the CRC-32s of the names in the table above):

Prop Material (+0x64) Next model Piece on a break
dyn_newsstand_a WOODFURNITURE (47) none none (paper debris particles only)
dyn_newsstand_b MEDWOOD (56) none none (paper debris particles only)
dyn_crate_stack CHAIR (86) none one dyn_wooddmg_a (material NONE) at (0, 0, −0.604) m from the prop, turned about z by a random angle (quaternion (0, 0, sin θ, cos θ), θ in 0-π)
dyn_parkbench_a WOODFURNITURE (47) none one dyn_parkbench_aa (SMALLBOARD) at (0, 0, −0.395) m in the prop's own pose, after 25 splinters
  • Sound: unless game-state set 3 has bit 0x4, a hit that leaves the prop standing plays the material pair (prop material, CONCRETE 5) at the hit point, and a break plays (prop material, prop material); one other prop (model 0xe42da444) breaks with (its material, TINBOX 23). Sound_PlayMaterialPairAt.
  • Dust: on every hit, standing or broken, when the particle pool has room and the hit point is within 40 m of a camera and visible from one (Cameras_IsWithinRange 40, Cameras_IsPointVisibleAny 10), two dust bursts at the hit point with radii 3.75 m and 2.75 m, colour 0x8b7d6964, unless game-state set 0 has bit 0x2. The per-prop effects come after them. Out of view, a hit that leaves the prop standing stops there; a break still swaps or removes the model.
  • Damaged model: none of the four has one (their Object List records' +0x04 is 0; only dyn_trashcan names one, dyn_trashcan_b, which its own class spawns as a piece instead, below). So each broken riot prop loses its collision body and is removed on its next update, as above; the pieces fly as knocked pieces.

Trash cans and bags (overhead_weapon props)

level99's street trash is placed by ObjSpawn like any dynamic object: checkpoint 2 has 11 dyn_trashcan, 18 dyn_gbags, 2 dyn_parktrash_a and 5 dyn_pstack, all of class overhead_weapon (Script types). Each is a solid box that stops a walker, a weapon the player can pick up and throw (Pickable objects, Throws), and a prop that any strike breaks at once.

Collision layers (CfgObj argument 12, +0x5e, as PHYFLAG bits). Confirmed (runtime): read from the object database (*0x00512c04 + index × 0x90) and from the live objects' bodies (object +0x118, body +0x40) in quick-save slot 8 (level99, PCSX2 2.9.94):

Type +0x5e PHYFLAG bits Body flags Walker Strikes, charge, jump Sprint (gait > 3)
dyn_trashcan 0xbf BLOCKOBJECTS, BLOCKHUMANS, MELEETARGET, WEAPONTARGET, JUMPTARGET, CHARGETARGET, COMBATPASSTHROUGH 0x1057e blocked (slides) struck blocked
dyn_gbags 0x2bf as the trash can, plus RUNTARGET 0x5057e blocked (slides) struck struck, then blocked
dyn_parktrash_a 0x3f as the trash can, without COMBATPASSTHROUGH 0x57e blocked (slides) struck blocked
dyn_pstack 0x2bd as the bags, without BLOCKHUMANS 0x5057a (inferred) passes through struck struck, passes through

The riot props dyn_crate_stack, dyn_newsstand_a and dyn_parkbench_a (above) have 0xbf too. The world-object body base is 0x500 (Physics); COMBATPASSTHROUGH is body bit 0x10000, RUNTARGET 0x40000. Game-state set 3 (byte +0x3eb of the game state *0x0051489c, read by GameState_TestFlag 0x0041cf30) changes all four types: bit 0x8 clears body flag 0x4 at init, so walkers pass through; bit 0x2 spawns no pieces; bit 0x4 silences the break; bit 0x1 keeps them off the path polygons (below). In slot 8 the byte was 0 (confirmed (runtime)); what sets it is not traced.

Walking into one (Human_OnContact, 0x00219d50, confirmed (code)): a world object that is not a powerup_item answers 0x20001, the slide response (Physics: contacts): the walker loses the part of his velocity that goes into the box and keeps the part along it. Nothing pushes, tips or moves the object: its own contact handler is not run by the human's sweep, and no velocity is given to it. Two cases come first: a body with RUNTARGET met by a human above gait 3 sets human +0x368 = 1 and is struck (Strike_Contact through his vtable +0x104, once per body: it then joins his body's ignore list); a body with JUMPTARGET is struck the same way while he is airborne (0x4000000, or record bit 0x800, or Human_IsTurnLimited6), and one with CHARGETARGET when his contact shape has flag 2 (charging). After such a strike a body without 0x4 answers 0x20000 (passed through); one with 0x4 still slides. Confirmed (runtime), slot 8 (props_trashcan): the player put 1.6 m south of the lone trash can at (47.71, 2.28) and walked at it (stick 60 %, gait 2) stopped with his root at y 1.5645, 0.72 m from the can's centre, and edged sideways 0.1 m along it over about 30 contacts while the can stayed at (47.705, 2.284, 0.703). Sprinting (L2, stick 100 %, gait 5, props_gbags_run) into the bags at (57.39, 13.72), his first contact struck them (Strike_Contact from 0x0021a0e0), they broke, and he stopped dead 0.8 m short of their centre on the next update; walking on into the trash can beside them (gait 2-3) only slid.

Why the sprinter stops. The bags' body has 0x4, so after the strike the same contact still answers the slide 0x20001; met head-on, the slide removes all of his velocity, and the blocked-sweep rule (Physics: contacts) zeroes what is left horizontally, so his speed +0x1ac and gait fall to 0 and the move set drops him to idle (confirmed (code) in Human_OnContact, 0x00219d50). Confirmed (runtime), props_gbags_run with L2 and the stick held at 100 % throughout: struck at step 35; steps 36-40 the idle clip 388 at speed 0; step 41 the run start clip 414 at gait 2-3, building up again from there. +0x368 is read only by OverheadWeapon_Break (the 0.65 volume), not by the movement; what holds him idle for those 5 updates is not traced.

A strike (any of the player's or an AI's strike shapes that meet a MELEETARGET body, Combat; square at a trash can plays 661 SPECIAL_BREAK_OBJECT_LOW) reaches the object through Strike_Contact (0x0021b290, above): the hit counter +0x10d goes 1 → 0, the impact sound (Human_PlayImpactSound with 9, or 26 armed, and the object's material), a noise report (30 m), message 6 to the volume boxes (the object was intact), stats crime 9 when the hit left a counter at 0 and the object has no flag 0x20, and message 1 with (attacker, attacker, kind, 0, contact point, contact +0x10). OverheadWeapon_OnMessage (0x00402bf8) calls OverheadWeapon_Break (0x003ffe90) for message 1 unconditionally: no hit points are read, so one blow of any kind, a thrown object's hit or a sprint into the bags breaks it. Confirmed (code); at runtime (props_trashcan, square at the can from 0.72 m) the strike landed 9 updates into clip 661 and the can broke on that update.

A thrown can or bags (or one swung into something while held) breaks on its first contact of any kind: the overhead_weapon class wears on every contact and their hits are 1 (Wear on contact). The break is the same OverheadWeapon_Break as a strike's, through WorldObject_Break's message 1: the hit point is the contact point, the direction d the contact normal (out of the surface), kind −1 (only −2 is tested, so −1 acts as a strike), and +0xb0 holds this contact's vector, so c is the normal too (the pieces and litter go up off a floor, the splat probe away from the surface). Two things differ because the object is flying (0x4000000): before the effects its position is moved 1 m along c (the unit contact vector added; the pieces and the bottle start from there), and the break's own material pair is skipped; the sound is WorldObject_OnImpact's on the floor or a wall, or the human or object handler's. A human it hits takes the contact damage (ThrownObject_HitHuman) before the break, on the same contact. Confirmed (code) at 0x003932c8, 0x00394050, 0x00393e20 and 0x003ffe90; not watched at runtime.

The break (OverheadWeapon_Break, confirmed (code) unless marked):

  1. Pickable flag 0x100000 cleared; path-polygon flag 8 under it cleared once (data +0x0c, set when the init left none); the collision body removed (Obj_RemoveBody); unless the fourth argument is −2, the state (data +0x04) becomes −5 and +0x10d 0.
  2. Only when the particle pool has room and the object is within 60 m of a camera and visible from one (Cameras_IsPointVisibleAny 10): the tint's alpha becomes 0 (+0xcc = +0xc8 & 0xffffff00, so it vanishes on the next update, no fade), flag 0x200000, +0xd4 = 0.1; two dust bursts 0.5 m above the hit point (radii 3.75 m and 2.75 m, colour 0x8b7d6994); then the per-model effects (table); then, unless the object is airborne, its material differs from the value ScriptObj_GetUserValue returns and set 3 has no bit 0x4, the material pair (material, material) at the hit point at volume 1.0, or 0.65 when the attacker's +0x368 is set (he broke it by running into it).
  3. Rescheduled with interval 1, so OverheadWeapon_Update (0x00402e70) runs next tick, sees state −5 and returns "done": WorldObject_Update removes it (WorldObject_Remove, message 2, spawn record bit 0x40000, never spawned again). Out of view the object is not hidden in step 2 but is removed the same way.

At runtime both breaks hid the object (tint +0xc8 0xffffffff → 0xffffff00, flags 0x8022a001 → 0x80228001) on the update after the strike and removed it on the next tick (WorldObject_Remove from 0x00395cb0, just after a message 0x20, which the class ignores). Confirmed (runtime).

Model Effects Pieces (ScriptObj_SpawnThrownDebris, 0x003a5530)
dyn_trashcan (0xfbf3e3ae) the "trash" set below dyn_trashcan_b (the dented can, fade_object) at the can's own pose, then message 0x15 (fade soon); plus the set's pieces
dyn_gbags (0x62502b03) the "trash" set the set's pieces only
dyn_parktrash_a (0xb0c69542) the "trash" set dyn_parktrash_aa (fade_object) at its pose, message 0x15; plus the set's pieces
dyn_pstack (0xfbd21393) the "cardboard" set (as dyn_cbox): litter kind 1 and 8 brown (0x4e4338ff) sub_debris pieces of 0.35-0.55 none
  • The "trash" set (mode 1 of the break, OverheadWeapon_Break 0x003ffe90, for models 0x57f87129, 0x3792e23d and 0x7f48b418; confirmed (code)), each with the hit point P, the message's direction d (normalised) and the contact vector c = +0xb0 normalised (zero for a prop never touched, below):
    • litter: Garbage_Throw(litter, 0, P, c) (0x00170b28): 5-9 pieces of the level's litter, when it has any waiting to respawn;
    • 10 black splinters: Particles_Splinters(2π, 1.0, 0.16, 1.2, P + (0, 0, 0.5), 1.5 c, 10, 0x101010ff, 0) (0x003c5b00), a sub_wood_splinter (Script types: wood splinters): sprite bits of 0.08-0.16 m within ±1.2 m of the point 0.5 m above P, at 1.5 c × 0.5-1.5 × 5-9 m/s (so none for a standing prop), spinning, that fall, lie flat and last 2 s;
    • a brown splat: Spawn_SubPaintSplat(0.75-1.0, P, d, 0x55423860) (0x003c5e50), an invisible probe sent at 8 m/s along d that leaves a paint_splat decal where it touches a surface within 15 ticks (Script types: paint splats); the colour's alpha 0x60 is below 0x80, so that decal is removed on its first update (2 ticks): the trash's splat never stays on screen (confirmed (code) at 0x003ec3a0);
    • a burst (Effects_SpawnBurst) at the object's position;
    • a dyn_beerbottle (thrown_weapon) placed 0.5 m above the path polygon under the hit point, when there is one, in the object's rotation, and knocked (message 0x30) with no velocity and a spin of (0, a, b), a and b random in ±π;
    • four fade_object litter pieces, dyn_trashbit_a, dyn_trashbit_b and two dyn_trashbit_d, each at a random offset from the object (the arguments ±0.43, ±0.43, 0.83 of Random_ScaledVector; their exact use is inferred).

The two hobo variants (dyn_hobo_trashcan, dyn_hobo_gbags) drop a random hobo food instead of the bottle. - The default set (a model with no case): 20 light-wood (0xbea780ff) and 24 dark (0x5c3f27ff) splinters. - A piece's flight (ScriptObj_SpawnThrownDebris): created by type name at the object's position plus the offset turned by its rotation, in the given rotation (here identity), with the object as parent, then message 0x30 with a spin of ±3π rad/s on each axis and a velocity of the normalised object vector +0xb0 × 1-1.5. A fade_object takes that knock as a horizontal speed of 2-5 m/s each axis, z 0, and flies airborne (FadeObject_Launch, 0x003c6ae0). Nothing is spawned while set 3 has bit 0x2. - +0xb0 is the object's last contact vector (ScriptObj_GetContactVector, 0x003a4bd0, reads it): the contact normal × the speed into the surface × dt, written by WorldObject_OnContact (0x00394050) on each contact. A prop that has never touched anything holds (0, 0, 0): confirmed (runtime), PCSX2 2.9.94, quick-save slot 8 read from the state, the bags at 0x00ef3780 and the trash can at 0x00ef2600. Normalising a zero vector gives zero on the PS2's VU (its reciprocal square root of 0 is the largest float, and 0 × that is 0), so the pieces of a struck standing prop get no velocity and drop straight from their offsets, spinning (inferred). A thrown prop that breaks on a contact gives them that contact's normal (out of the surface), 1-1.5 m/s before the fade_object's 2-5 × scaling. - The pieces' end (Script types: fade_object, confirmed (code)): a launched piece updates at 30 Hz; it is removed on any update where its position is more than 30 m outside every view's frustum; message 0x15 sets the age to 3400, so the dented can starts to fade 151 updates (5.0 s) after the break and is gone 51 updates (1.7 s) later; the litter, without 0x15, starts to fade after 3551 updates (118 s).

At runtime (props_trashcan) the can's break spawned dyn_trashcan_b (offset and rotation the identity) and dyn_trashcan / dyn_gbags breaks each spawned dyn_trashbit_a, _b, _d, _d, in that order, in the strike's update. Confirmed (runtime); the bottle's path was not hooked.

Path polygons. OverheadWeapon_Init (0x003ff6c8) sets path-polygon flag 8 at the object's point (PathPolygon_SetFlag8AtPoint, the flag of door holes) for every model but 18 small ones (dyn_gascan, dyn_cbox, dyn_tire, dyn_crate_c, dyn_crate_e, dyn_tvsmall, dyn_tv, dyn_box_stereo, dyn_ricebag_b, dyn_keyboard, dyn_drumkit_a, dyn_drumkit_e, dyn_paintcan, dyn_detergent, dyn_cinderblok, dyn_bagcash, dyn_bagdrug and one unnamed hash 0xe910f959), or for none while set 3 has bit 0x1; the break or the next update clears it. So the trash props mark the navigation mesh while they stand. Confirmed (code); what flag 8 means to the AI here is on AI.

Trains (moving hazards)

A train is a line of up to 12 objects (a subway train's cars) that AI humans step out of the way of; the scripts build one with ObjSetTrainPoint and run it with ObjStartTrain / ObjStopTrain. There are four records of 0x150 bytes at 0x006f3a10, built at boot (0x00414220, from the static initialiser stub 0x00414280) and stopped by InitLevel and UnloadLevel (0x004136f0); the task manager updates all four each frame (0x00413728). Confirmed (code).

Offset Field Evidence
+0x00 12 point positions (0x10 each), re-read from the objects every update confirmed (code)
+0xc0 a box around the points, grown by the radius confirmed (code)
+0xe0 12 object pointers (Train_SetPoint, 0x00413770 → TrainRecord_SetPoint, 0x004138a8) confirmed (code)
+0x110 12 gap flags: non-zero means no segment starts at that point confirmed (code)
+0x140 point count (the highest index set, plus one) confirmed (code)
+0x144 the radius in metres (ObjStartTrain's first argument) confirmed (code)
+0x148 running (Train_Start, 0x004137d8, after TrainRecord_Reset, 0x00413890; Train_Stop, 0x00413828) confirmed (code)
+0x149 the last point index set with gap 0 confirmed (code)

TrainRecord_Update (0x00413d90), while running: up to 60 humans in the box (0x002274a8); each one with an AI brain that is not in state 1 and not cuffed, and within the radius of a segment, gets the time stamped in its brain (+0x2e0); if its brain has no goal of type 7 it drops a type-0xf goal on top and pushes the dodge goal (0x002f9358, given the record and the nearest point). The dodge goal uses 0x004138f0 (still within the radius of a segment, and whether it is more than 0.5 m inside and within 10 m of the front point) and 0x00413a90 (the point's projection on the first segment long enough to hold it, and that segment's two indices: it walks the segments from point 0, measures the point along each one's direction, gives up (0) as soon as the point lies before a segment's start, and takes the first segment whose length is greater; it ignores the gap flags and never tries the last segment, so with fewer than three points it finds nothing; the dodge goal's use: RunFromTrain). Trains_IsPathClear (0x00414188) asks each running train (TrainRecord_IsPathClear, 0x00413c48) whether a move from one point to another passes within its radius of a segment; AI movement checks it. The record's constructor is 0x00413848. Confirmed (code).

Game-state functions

Functions of the game-state module (0x00417af0-0x00424e50, a range inferred from where its functions lie: inventory, statistics, flags, configuration workers) that belong to this page, by address.

Address Name What it does Evidence
0x0041d4f8 Cfg_SetGlobalTimeToLive CfgSetGlobalTimeToLive: +0x26c confirmed (code)

Coney's implementation

Object types and spawn records (src/world_objects/object_types.h, src/world_objects/spawn_records.h, the bindings in src/scripting/spawn_bindings.h): CfgObj adds a type (name, class, hitpoints and the model hash, the CRC-32 of the name) and still records its arguments for what reads the rest; ObjSpawn adds a spawn record (type, position, rotation, zone, flags, tint, flag name) and returns its handle; CfgSetDatabaseSizes makes the pool (its object count plus 500). Resolving a handle marks its record live and pinning keeps it. A fresh script state starts with no records but keeps the types, which the legal screen's preloads configure once (Scripting). Coney's choices: a record's handle comes from the counter every world object's handle comes from, not the record index << 16 (record 0 would be NilHandle); the rotation is kept as given rather than packed into s16 × 4,096; a record a binding resolved is "live"; and the keys and the power cuffs are always suppressed, as a fresh profile has neither unlockable. ObjEnableZone sets or clears a zone's bit of the records' zone mask (zone 0 on at a level's start, the rest off); ObjShow and ObjHide resolve the handle and mark the record shown or hidden and keep the show message (ObjShow's distance kept); ObjDestroy makes a human holding the object let go, then removes the record for good, except that with its second argument an objective marker is marked dying and fades out first (src/scripting/world_bindings.h). Disc check (NTSC-U, 2026-10-06, counts only): at the front end level100.lua leaves 29 Wonder Wheel records, each of a configured type, the wheel tinted 0x474542FF.

Placed objects (src/world_objects/placed_objects_file.h, from the objects file): after the level script's main chunk, runLevelScript reads <level>_objs.txt through the script source and adds a spawn record per line, each with a handle from the world objects' counter, and makes a particle system of each part line's name at its pose there and then, with no handle and its other fields unused, whatever its zone. A missing file adds nothing. Disc check (NTSC-U, 2026-10-07, counts only), records and emitters of the lines: level99 98 and 14 of 112, level80 212 and 30 of 242, level87 704 and 16 of 720, level2 468 and 30 of 498, level3 128 and 0 of 128, level5 615 and 31 of 646, level34 380 and 7 of 387, level95 258 and 11 of 269; so every line is placed. Of the emitters, only the steam vents draw yet (the flies, neon, alarm strobes, air conditioner, dust and sparks make inert systems).

Pickable objects and objects in a scene (src/world_objects/pickups.h, src/gamemodes/level_pickups.h): triangle takes the pickable classes (world_objects::pickable(), the model-hash exceptions included); CfgObj's 20th argument is kept as the type's anim set (+0x87; inferred from the field order and a bat's 3). A scene's moves of a bound object (position and rotation) go to its spawn record (SceneStage::setObjectMover): the record's pose stands for the object's, so the bats l99_c8 lays down are where the pick-up finds them. Coney's reading; the original writes the record only when the object is stored.

Models (src/platform/object_models.h, src/platform/placed_objects.h): an object's model is its type's Object List record (ObjectList::findByHash, the type's model hash), its 0x47 model read as the level file's and its dictionary's first texture on the first material, loaded once per type and shared. An object is drawn at its pose with the model's vertices taken in the game's axes (z up) and carried into RenderWare's as positions are, (x, y, z) → (x, z, −y); the model's own atomic frame is left out. Coney's reading: the frames are either the identity or the turn of z up into RenderWare's y up, some with a stray offset (the column's 2.69 m, the Wonder Wheel neons' 35 m), and only without the frame does the objective disc (an identity frame, its centre 1.4 m up) stand upright on its column as the runtime sees it. The draw (PlacedObjects::draw) follows The tint: the tint word multiplies the materials' colours and alpha, then the size cull (the model's bounding sphere), the ObjShow distance and the alpha floor of 10; opaque objects first, then the translucent ones. Not modelled: the camera fade and the second dictionary. The front end draws the live records' objects this way, without a camera (no cull), each lit as a world object (Front end); the eight Wonder Wheel types' frames are all the z-up turn, so leaving the frame out draws them as turning by it would.

World objects in play (repo:src/world_objects/object_tasks.h, repo:src/platform/play_level_world.cpp): each step, after the scripts and the scenes, ObjectTasks brings records in as streaming does (zone enabled, within 70 m of the camera), keeps them until they are beyond the draw distance + 10 m, and makes each object's draw; the play mode draws them between the s and d worlds with the objects' lights. Objective markers follow the page: the disc turns 90° a second about z; the alpha steps 8 an update towards 255 or 0 by the last show message and is drawn one update late; the disc is never size-culled; the column (model 0x1f3b85ea) stands at the disc's position turning back, coloured by the disc's type; a dying marker is removed at alpha 0, and ObjDestroy without its second argument removes it at once. Held objects (In a human's hand): player 1's object hangs from the bone of the first take event of its type's low pick-up clip, at the event's position × his scale slid along the object's y by CfgObj +0x70 (ObjectType::grip), composed with this frame's bone and his placement. Disc check (NTSC-U, counts only): coney_tests "[disc][objects]" at level99 checkpoint 1 after the intro: one marker disc and one gold column at alpha 255; the three bats, laid on the ground as l99_c8 leaves them, drawn there.

Coney's stand-ins for the world objects, where this page is silent:

  • The column's blend: alpha blended over what is drawn, Z tested without Z writes, after the opaque objects.
  • A new marker starts at alpha 0. Every record within reach comes in on the same step; a record a binding resolved or a scene pinned is drawn whatever its distance and zone. ObjHide hides a plain object (simple_object's 0x0a is not a hide). The fade-in over the first second is linear.
  • A held object: the bone's position is not scaled by the human's scale (Coney draws the body unscaled), the lean is left out, and only player 1's object is drawn in a hand. A dropped or thrown object falls from the hand (Physics).
  • Throws (repo:src/combat/throw_velocity.h, combat::throwAttack, the play mode's dropHeld): square, cross, the charge or the dive with a set 4-6 object plays the throw clip by gait, its type-10 event lets go, and the object leaves the hand's pose with Human_ComputeThrowVelocity's velocity turned by the thrower's heading and the detach's spin. Stand-ins: the target is the fighter's own (no Player_PickThrowTarget, no turn to face it), aimed at a fixed head height and from the object's place rather than the hand bone; set 5's smash, hits on humans, a held object's wear, the removal time and the AI's throws are not built.
  • The aiming state (above; repo:src/combat/throw_aim.h, the play mode's stepThrowAim): L1 with a set 5 object and no lock enters it (466 into 469; the stick stops walking him), the raw left stick turns him (470 while it does) and pitches the aim, the first frame takes the follow camera's heading, and each frame the arc's 18 points are traced in the two passes, a human target faced for 5 frames and while the stick rests. What the arc meets is kept for HuIsAimingAt, HuGetControlName gives throwControl, and a throw from the aim leaves from the release point with the aim's velocity. Stand-ins: the sweeps are sampled every 0.1 m along a segment (not IPhysics_CollideShape), humans are 2 m upright capsules, cars are not met, a target's point is a fixed head height, and the lock-on's friendly case is not modelled (Coney's lock never takes a friend). The arc is drawn (Graphics).
  • Wear and breaks in flight (above; world_objects::LooseObjects, world_objects::Props::breakFlying, the play mode's flyingBreak): a throw sets the flying object's body flag 1 and keeps its thrower. Each contact with the level mesh first takes one hit (WorldObject_TakeHit, the second counter first) as the table above says: an overhead_weapon or a GUITAR object on every contact, thrown or dropped; any other on a throw's first contact only, the flag then cleared. The contact that leaves a counter at 0 breaks it: no bounce, the material pairs (its, its) at 1.0 and (its, the surface's) at 0.5 / (n + 1), message 6 to the boxes its thrower stands in with the object where its move left it, then its class's message 1: a thrown_weapon goes to the hit point with dust of radius 1.6 (a dyn_beerbottle adds 4-8 brown glass pieces) and is removed at the next step; an overhead_weapon breaks from 1 m along the unit contact vector without its own pair, its pieces launched along that vector; a lifted cash register leaves its dyn_cashreg_c launched along the contact normal. The counters an object wore stay with it between flights (a register spends one per throw). Stand-ins: the flight meets the level mesh only (no body, so no human or car contact); the glass pieces are shards that do not fly, and the other models' rubble, glass and bursts are not made; the pairs' fallbacks (116, then 5) and CHAIR's 0.1 straight down are not built; another class's message 1 is not built, so such an object lies where it broke; the impact count n is counted per flight; and a held object's wear and the test against a world object already spent are not built.
  • Impact sounds and the pieces' flight (Sound events, Script types: fade_object; the play mode's impactSound): every loose object's level contact that does not break it plays its material against the surface's at 1 / (n + 1), for its first three impacts above 2 m/s. A broken trash prop's fade_object pieces (the dented can, the park bin's piece, the litter) and a register's piece are launched as loose objects: velocity the unit contact vector's x and y each × 2-5, z 0, spin ±3π rad/s on each axis (none for the register's piece), so a struck standing prop's pieces drop straight down; on the update after a level contact a piece stops dead and falls from rest. Their landings sound through the same impacts, by each piece's type material against the surface's, through the sound bank's material-pair table. Which samples those pairs pick for the pieces (garbpart_01-06, debris_part_01-03, bottledrop1/2, woodsplinter_01, smallboardpart_01/02, wood_drops_01/02, chairli8, chairy3, deep_debris_01) is not on these pages yet (open question). Stand-ins: the speed tested against 2 m/s is the object's speed as it meets the surface (the research does not say which); the pieces never fade (message 0x15, the age and the view test are not built), and the register's piece spills no money.
  • Weapon piles (above; world_objects::pileTake, world_objects::Props::takeFromPile, the play mode's takeFromPile): a dyn_pile is pickable; player 1's pick-up of one makes the object its type hands out at the pile, plays the take cue there as a 3D sound, and puts the new object in his hand while the pile stays. dyn_molotovpile_b runs out after 5 takes (no longer pickable) and goes two 240-tick updates later. Stand-ins: the spray-can box and the donut spawner hand out nothing; message 0x22, a carried or knocked pile, and the AI's pile goals (ManWeaponPile, ThrowSubTactic) are not built.

Glass and doors, written from this page and Crimes (2026-10-06), in repo:src/world_objects/:

  • LevelObjects (level_objects.h) holds a level's panes and doors and the world they change (ObjectWorld: the collision mesh's triangles, the path data's links, and ObjectServices for sounds, shards, crimes, flags, statistics, bodies, loose objects and models). Play mode ticks it at 60 Hz and sends hits to humanHit / thrownHit, finding the object by the struck triangle (objectOfTriangle); humanHitKind / thrownHitKind give the hit's kind.
  • Panes (glass.h): the type table, the spawn (geometry, GLASS two-sided triangles, sprites, types 14, 15, 17, 18, alarm bits, window link), message 1 and 0, Glass_Break and BreakGlassInRadius; the shatter's count, sound pair and shards drawn from the game's random numbers.
  • Doors (doors.h): the per-type set-up, leaves, triangle flags, the state machine and its timing in ticks, the state commands, DoorOpen away from the human, DoorOpenDegree, pickable doors, message 0x15, the breakable types' hits and the barriers. Links (nav_links.h): retag, open and close by number (the door's hole found as A door's hole gives, NavLinks::holeOf(), world::PathMap::holeAt()), nearest by position. The edge's door number is decoded into world::PathEdge::door.
  • Bindings (repo:src/scripting/object_bindings.h): the 22 glass, door, link and lock-pick bindings; SpawnDoor reads its type from the recorded CfgObj calls.
  • In play (repo:src/gamemodes/gameplay_mode.h, repo:src/platform/play_level_objects.cpp): gameplay owns the level's LevelObjects; the boot scripts' recorded CfgSetGlassProperties calls are applied before the level script spawns into them. The play mode gives them the level's collision mesh and path data, ticks them twice a step, and sends each human's strike-shape contacts to the pane or door the shapes meet (Combat). Player 1's object attack (661 / 662) lands when those shapes reach its pane or door, or, for a car or other object, when its strike shapes come on; never at the clip's start. Stand-ins: a pane or door the window passed without touching takes the hit as the window closes, and a clip with no strike events lands it as it ends. A broken barrier is removed at its next 60-tick update: its handlers get message 2 (itself, NilHandle) and its spawn record goes for good; the three boards it throws are made at the hit and removed on the next tick (the hit's own update, as in the original), so no board stands in the gap while the barrier waits for its removal (issue #233; before this they stood there until the dealer cut, which a player saw as the broken fence staying on screen). It draws each swinging door's leaves (hinged at their positions, w the type's CfgObj argument 15, swinging by the 28-tick slerp of Leaves) and each barrier as its type's model (gone once broken; world_objects::doorDraws()) with the world objects, and the panes as Drawing a pane gives (world_objects::glassDraws(), drawn from part_page1 after the humans and before the rings). Their lock-pick callbacks call the scripts, a break-in moves the CrimeScene flag, and their sounds go to repo:src/audio/object_sounds.h, which plays a name hash through the SoundPlayer.
  • Disc check (NTSC-U, counts only): coney_tests "[disc][objects]": level2 places its 26 doors (14 swinging, 12 barriers, 21 leaves), every type configured by a CfgObj, and 25 panes; 19 glass types are set. coney --play-level level2 hands all of them to the play mode; three plain punches (4 damage each) break a 10-hitpoint dyn_door_fence barrier.

The spawn's triangle writes reach the mesh bound later (fixed for checklist item 30; found 2026-10-10). GameplayMode runs the level script, and so every SpawnDoor and SpawnBreakableGlass, before the play mode has loaded the level's mesh (coney --play-level level95 prints the script's player start, then level95.lev: 5943 collision triangles, then objects: 79 glass panes, 22 doors and barriers). Until the fix, markTriangles did nothing while ObjectWorld::collision was null, so every door and barrier kept the disc's one-sided triangles (and no 0x400 / 0x40 bits or material) and every pane its own (no GLASS, one-sided): the player walked out through a door whose face looked away from him and was stopped by it on the way back (door 10, from (−184.92, 95.45) at 60 % stick: the feet crossed its plane at x −187.63 at about 3.3 m/s; inward he stopped at x −188.107, a radius and the 0.01 m back-off from the plane). Now every write the objects make to a triangle (markTriangles: the two-sided, door, breakable, testable-while-disabled and CamGhostDoor bits and the material; setTrianglesEnabled: the enabled bit) is kept in ObjectWorld::triangleWrites as well as applied to a bound mesh, and ObjectWorld::bindCollision (repo:src/world_objects/object_services.cpp, called by PlayLevelMode::bindObjects and with null when the mode ends) applies them all to the mesh it binds, so a freshly loaded mesh carries the same flags as the original's live one. A script's ChangeCollision (a volume box's triangles switched on or off) is kept the same way (ObjectWorld::boxWrites), since which triangles lie inside is known only with a mesh; the bind applies the bits and materials, then every switch on or off, single triangles and boxes, in the order they were made. The level's unload (LevelObjects::clear) forgets them all. Tests: repo:tests/world_objects/door_blocking_test.cpp (a synthetic doorway walked into from both sides at 60 % stick, closed, opened by OpenDoor and closed again; a barrier; a glass pane; box switches in order with single ones; the writes on a second mesh; the unload) and repo:tests/platform/disc_hub_doors_test.cpp (level95 checkpoint 1 through the game session: door 10's triangles carry 0x2 | 0x400, and player 1 steered at it from inside and from outside stops short of its plane both times).

Coney's stand-ins, where this page is silent:

  • A leaf model is dyn_dr_ and the type name after dyn_door_; a door model the Object List lacks draws dyn_dr_ and the name less a leading dbl (dyn_door_fence draws dyn_dr_fence). A model stage a splintering door or a cabin leaf takes is not drawn. Panes are not sorted by their distance within the frame (all share batch 0), a pane's body is a box only an airborne human meets (Moving into a pane: the push-out sphere of Characters stands for both the airborne body and the jump's strike shapes, and every whole pane has its body whatever its distance from a camera), and the leaf's pose steps per 60 Hz tick.
  • DoorOpen's "away" uses the door's turned y axis. OpenDoorAnimated and a lock pick's success open at once (DoorOpen), without human state 26.
  • A shard's offset is a random step of 1/1000 in ±1. A link's distance is to its middle. Of several holes whose boxes hold a doorway's middle and none of which takes it in, the nearest vertex average wins. Wreck pieces and boards spawn at the door; a cabin door keeps its leaves once broken. Game state bits 2 and 4 are not read. An object type no CfgObj names is a swinging door of 100 hitpoints.
  • Square with no human in front aims at a whole pane (its centre) as Player_PickTarget's object pass does (Combat); the object attack's hit breaks that pane. Doors are not object targets yet. Every other hit reaches an object through the strike shapes (Combat): a segment shape meets a pane's box as spheres every 5 cm along it, and a door through its two triangles while they are enabled. A broken barrier's triangles stay on until its removal, standing for the body the break leaves (dyn_door_wall_a, whose body goes, and dyn_door_vargas, which is never removed, turn them off at the break). Thrown objects do not reach the objects yet: nothing is thrown in play.
  • Sounds: a name hash plays as a positional sound at the object, a material pair from the sound matrix; the lock pick's click is counted, not played (its cue is not traced). Shards, crimes beyond the CrimeScene flag, statistics and models do nothing yet; a loose object (a wreck piece, a board, a broken prop's piece) becomes a spawn record that rests where it is made.
  • Shards (gamemodes/level_object_services.h, with Particles): the culling step's tests are player 1's camera (the scene camera while a scene plays) within 15 m of the pane and the pane within 10 m of its view's frustum (effects::effectNearView(), repo:src/effects/effect_culling.h), and the budget is 158 particles left in the pool; dust and bursts are a sub_shack_puff. A broken pane also frees the stereo of a parked car within 2 m of it (Cars).
  • ObjDestroy on a pane releases it (GlassPanes::release()): it vanishes with no shatter or sound, its triangles left as they are. BreakObjectsInRadius sends its destroy message to the doors and to the ObjSpawn objects that are live (and the centre object) within the radius (repo:src/world_objects/object_breaks.h): a dyn_molotv breaks itself 22 ticks later into a sub_explode 0.7 m up (Script types: the Molotov); other types ignore it. Coney's reading: 22 ticks for every bottle, the page's figure for one spawned that frame.

Breakable props (Breakable props, repo:src/world_objects/props.h): CfgObj keeps the counters' bytes, the body word, the material and the body's centre, size and shape; bodyFlagsOf and isStrikeTarget decide which world objects a strike may pick. Square offers player 1 the drawn ones within 2 m (Combat) and its object attack strikes the one picked; a moving attack's strike shapes strike any whose body they reach (a box of the type's size about its centre, or a sphere of half its x). Props::strike follows Strike_Contact's order: intact or not, WorldObject_TakeHit, the impact sound, message 6 only while intact, then DynMasks_OnHit for a dyn_masks prop: its sound and two dust bursts at the hit point, and on a break the crate stack's or the bench's piece (and the bench's splinters); then it loses its body and is removed (message 2, its record gone). GetRTTI gives 0x400 for a pane. Coney's stand-ins: the noise, statistic 9, the burn near a fire and the newsstands' paper are not built; the dust is the shards' sub_shack_puff; a piece rests where it is made; the crate piece's random turn is drawn in steps of 1/1000 of pi; a broken prop goes 20 ticks after the break; the size is read as whole extents, a cylinder or capsule body is tested as its box, and only drawn objects within 4 m are tested.

Trash props (Trash cans and bags, repo:src/world_objects/props.h): an overhead_weapon prop breaks on its first strike (OverheadWeapon_Break): two dust bursts 0.5 m above the hit point; the trash can's dented can or the park bin's piece at its pose; for the can, the bags and the park bin, 10 splinters, a burst at the prop and the four litter pieces, and the dyn_beerbottle 0.5 m above the path polygon under the hit point (none off the polygons), in the prop's rotation, falling as a loose object with a spin of (0, a, b), a and b in ±π; the paper stack's 8 debris pieces, or 44 splinters for another model; then its material against itself, at 0.65 of its volume when the attacker ran into it. It loses its body at once and is removed the next tick. The pieces are launched as loose objects along its contact vector (zero when struck standing, so they drop) and stop dead on landing (Wear and breaks in flight, above); a thrown or dropped can or bags breaks on its first contact. A human above jog whose walking sphere meets a RUNTARGET body (the bags, the paper stack) strikes it with a plain hit at the next step, once. Coney's stand-ins: the pieces never fade; the litter system's pieces, the splat, the splinters' colours, the camera test and the path-polygon flag 8 are not built; the polygon's height under the hit point is the mean of its vertices' heights; the hobo variants' food is not dropped; the paper stack's debris pieces are splinters; a litter piece's offset is drawn in ±0.43, ±0.43 and 0-0.83 m; and the sprinter is not stopped dead after the strike (he slides along the bags that step, and they are gone the next).

Cash register (Script types: cash registers, repo:src/world_objects/props.h): a dyn_cashreg makes its drawer (dyn_cashreg_b) when it first stands in the world, at (0, 0.02, −0.22) m in its own frame and pose, updating every 60 ticks. It takes 2 + 8 × the hit's kind from its type's +0x5a (16), so eight bare hits or one charge break it. A hit it survives sounds its material against concrete at the register and raises dust at the hit; the breaking hit swaps its broken model (0x59026f53, drawn through the spawn record's model), sounds its material against itself and opens its drawer; the register is no longer a strike target but keeps its body and is never removed. At its first update after the break the drawer jumps 0.35 m out along its local y; at its second it spills a dyn_money 0.22 m above it holding $25-50 (WorldObject_SetField124, the record's money), which rests there. Player 1 takes money by walking over it (Walking over a power-up, repo:src/gamemodes/level_pickups.h). Triangle's message 0 reaches the register's own class (DynCashreg_OnMessage, LevelPickups' native message): its drawer is deleted whatever happens, and unless it is broken or already lifted it asks to be picked up (0x14), so the press lifts it with its pick-up animation's clip (2: 503, or 504 above 0.8 m); a broken one refuses and the search passes it over. Coney's stand-ins: the dust is a burst; the game state flag that silences the hit sound is not built; a lifted register's break makes its dyn_cashreg_c piece (Wear and breaks in flight, above) without the contact offset, and the piece spills no money (its age is not built); and the walk-over's touch is Player state's. In level99 at checkpoint 2 the store's register stands at (53.23, 57.26, 1.63); eight squares from the shop floor south of it break it, the money lies behind the counter, and walking into it from there takes it; triangle from the same spot instead deletes the drawer and lifts the register with clip 504 (disc checks, 2026-10-08).

Open questions

  • Door blocking (How a closed door stops a human): read the PHYFLAGs of the door types with an OBB body (dyn_door_store, the cabin doors) to settle whether their body ever meets a walker.
  • Which search mask admits 0x40 links.
  • What human state 26 (the animated open) plays and when it sends the door 0x0b; what fills a player's interaction record (+0x660) for a door.
  • Check at runtime that a broken pane is no longer drawn (Drawing a pane), and that a leaf model spans its local x from 0 to −w (Leaves).
  • How the PTank builds a matrix sprite's corners and texture coordinates (inferred equal to the near path's), and which of a shard's two colours the particle draw uses.
  • How the draw blends a translucent object (the column's material and vertex alpha, the PS2 blend), and where the instance's colour bytes are set from +0xc8.
  • How the instance places the model: whether its clump's atomic frame (identity, or the z-up turn with an offset) is used; Coney leaves it out.
  • Who spawns sub_objective_glow; which levels use the purple and white marker kinds.
  • How the resource manager picks the object whose model it loads next (+0xbd4), and whether level100's packs hold the Wonder Wheel's models.
  • What the first camera's vtable +0x214 returns (the streaming-out distance).
  • Held objects: what fills human +0x348 / +0x34c (event 0x37), what message 0x17 to the holder does, who sends melee_weapon messages 0x12 / 0x13, and the frame of a thrown object's angular velocity.
  • Trash props (Trash cans and bags): what sets game-state set 3's bits, and who sends a broken prop message 0x20.
  • dyn_door_vargas' second object, and the leaf models of dyn_door_chainlnk_pick (no dyn_dr_chainlnk_pick record).
  • What a cabin door's leaves do once it breaks, and where the wreck pieces and boards appear.
  • Cash register (Script types): how many throws or swings break a lifted register at runtime.
  • Impact sounds (Sound events): which speed WorldObject_OnImpact tests against 2 m/s (the object's speed, or its speed into the surface; Coney uses the first), and whether its count +0x13c is ever reset (Coney counts per flight). Which materials the fade_object pieces (dyn_trashcan_b, dyn_parktrash_aa, the dyn_trashbit_*, dyn_cashreg_c) have, and which samples their pairs against the level's surfaces play (the debris sounds garbpart_*, debris_part_*, smallboardpart_*, wood_drops_* and the others): Coney plays the pair the type table gives and has no check that it matches.
  • Wear (Wear on contact): what message 1 does for a thrown object of a class other than thrown_weapon, overhead_weapon and dyn_cashreg whose counters run out (a melee_weapon with hits); Coney leaves it lying.
  • Moving into a pane (Moving into a pane): which clip events switch the strike shapes on in a jump. (Answered on Combat: the strike shapes' sizes and events, record +0x08 bit 0x800, and a knock-back flight, which is not airborne. Human vtable +0x10c, 0x00227180: any non-human body may be struck, a human unless its state word has 0x100000000.)