A Mac steps 8 ragdolls and 4 vehicles within 2 ms a tick #24

Merged
agra merged 8 commits from task/3904-a-mac-steps-8-ragdolls-and-4-vehicles-wi into master 2026-10-30 15:24:56 +02:00
Owner

Tracker: #3904

Step 5 of plan #3865. This PR holds the physics to its budget with a built bench that the gate now runs on m5's slower core tier. The measurement called for no optimisation: the worst tick is about 0.52 ms on the slower tier, about a quarter of the budget.

What it adds

  • The scene, tools/bench/physics_scene.sx (benchScene). It builds a physics world over the first map's collision world (baked/first_map.hbw) with the bench's own bodies alone, on the level clearing 13.5 m in front of the first spawn marker: 116 bodies and 104 joints.
    • 4 vehicles. Each has a convex hull chassis made with hullOf (4.4 m long, 1.4 m tall, 1.9 m wide, 1500 kg, centred on its centroid) and four 25 kg sphere wheels of radius 0.35 m, hinged at its corners with their axes across the car. They stand level, side by side, 1 m apart, with their wheels 0.5 m above the ground.

    • 8 ragdolls. These are free dummies from src/sim/dummy.sx's template, added by the new freeDummy, which hangDummy now shares its code with. They stand in two rows, tilted 5–25° toward the cars, with their lowest points 0.3–0.6 m up:

      • the inner row 0.9 m from the cars' ends, a dummy behind each car;
      • the outer row 1 m behind it, a dummy behind each of the inner row's. The one behind the first stands 0.25 m aside, clear of the frame's post beside it.

      Every body starts at least 0.38 m from any body of another vehicle or dummy. No torso part's top starts more than 2.11 m above the terrain, and no vehicle starts above a dummy.

    • Placement. The dummies' frame stands at the back of the clearing, so the dummies stand on its side and push the cars away from it. The free-wheeled cars roll at up to about 0.25 m/s when a dummy topples against an end. Laid out the other way round, one car rolled into a frame post.

    • What it does. The cars land, hop about 0.14 m and land again. Each inner dummy topples against its car's end, and each outer dummy topples onto the inner dummy in front of it. Over the 600 ticks, the contacts between bodies of different cars and dummies are these (first tick, then ticks holding a contact):

      dummy meets
      0 car 0 (tick 49, 267 ticks), dummy 4 (47, 455)
      1 car 1 (69, 30), dummy 5 (59, 438)
      2 car 2 (36, 23), dummy 6 (57, 192)
      3 car 3 (47, 33), dummy 7 (54, 347)
      4 dummy 0 (47, 455)
      5 dummy 1 (59, 438)
      6 dummy 2 (57, 192)
      7 dummy 3 (54, 347)

      No dummy holds a contact with the frame's posts (near-upright map triangles).

  • The bench, tools/bench/physics.sx, built into sx-out/tools/physics_bench.
    • It steps the scene 600 ticks, 5 times from the same start.
    • It times each tick by the stepping thread's CPU time, through clock_gettime_nsec_np(CLOCK_THREAD_CPUTIME_ID = 16) declared extern.
    • A tick's cost is its least time over the 5 runs.
    • It prints the worst and the median tick. For the worst tick it also prints the bodies awake, the contact points, the bodies swept, the triangles they took a time of impact against, the groups the sweep stopped, and the time spent finding contacts, solving, and sweeping and sleeping.
    • It exits 1 when the worst tick passes 2 ms.
  • The phases. PhysicsWorld.step now runs as begin (wake, find contacts, warm), solve (substeps, restitution, store) and end (sweep, sleep), and the bench times each one. PhysicsWorld.counts holds what the last step did. The code and its order are unchanged (see below).
  • The gate. tools/gate.sh builds the bench after the app and runs it under taskpolicy -b, before the tests.
  • The test, tests/physics_bench_scene.sx. It steps the same scene 600 ticks under tools/sx run, about 2 s, and checks:
    • Meetings: every car holds a contact with a dummy, and every dummy with another dummy, on some tick. With the layout of 0ac6ff9 (the outer row between the inner row's dummies), it fails for dummies 0, 1, 3, 4, 5 and 7.
    • Terrain: no body's lowest point ends a tick more than 6 cm below the terrain under it. The terrain is read by a ray down from 1 m above that point, which stays below the frame's beam. A ray that finds no terrain fails the check.
    • Bounds: no body leaves the map's bounds.
    • Limits: every dummy joint stays within its template's limits plus 20° on every tick.
    • Wheels: no wheel's hinge opens more than 5 cm, and one opened past 1 cm closes within 5 ticks.
    • Determinism: two runs are bit-identical at every tick.
    • Start: the bodies start clear of each other.
  • README. The Tests section names the bench, what it steps, how it times a tick, the tier it runs on, and its bound.

Bench figures (m5)

At the head, 39a9a74, from the bench gate run 951 built, at a one-minute load of about 4 (14:21 EET).

taskpolicy -b sx-out/tools/physics_bench:

physics bench: 116 bodies, 104 joints, 600 ticks, the least of 5 runs each
worst tick: 0.514625 ms, at tick 189
median tick: 0.381042 ms
worst tick held 116 bodies awake and 222 contact points; it swept 2 bodies, took a time of impact against 0 triangles and stopped 0 groups
worst tick spent 0.191000 ms finding contacts, 0.317500 ms solving, 0.006125 ms sweeping and sleeping
within the 2.000000 ms budget

At default QoS, sx-out/tools/physics_bench:

physics bench: 116 bodies, 104 joints, 600 ticks, the least of 5 runs each
worst tick: 0.239792 ms, at tick 187
median tick: 0.174333 ms
worst tick held 116 bodies awake and 220 contact points; it swept 0 bodies, took a time of impact against 0 triangles and stopped 0 groups
worst tick spent 0.092834 ms finding contacts, 0.144250 ms solving, 0.002708 ms sweeping and sleeping
within the 2.000000 ms budget

A second round a minute later read 0.513542 ms worst and 0.381125 ms median under -b, and 0.239166 ms and 0.175584 ms at default QoS. Gate run 951's own bench read 0.519417 ms worst and 0.381333 ms median under -b.

Master before any optimisation. Master's own src/physics/ (ef94076) has no phases or counts, so this bench times its whole step(). It ran in a git archive arm of master with this PR's scene (39a9a74's) and src/sim/dummy.sx added, interleaved with the head's runs above:

14:21, taskpolicy -b:  worst tick: 0.524708 ms, at tick 284   median tick: 0.377791 ms
14:21, default QoS:    worst tick: 0.238458 ms, at tick 92    median tick: 0.173125 ms
14:21, taskpolicy -b:  worst tick: 0.518042 ms, at tick 189   median tick: 0.378625 ms
14:21, default QoS:    worst tick: 0.239250 ms, at tick 185   median tick: 0.172917 ms

Interleaved rounds. Five earlier rounds at a load of about 3.5 (14:18–14:19) alternated a bench built from 39a9a74's sources in a git archive arm and master's:

head, -b master, -b head, default master, default
worst tick 0.515–0.529 ms 0.512–0.520 ms 0.259–0.279 ms 0.261–0.281 ms
median tick 0.378–0.380 ms 0.350–0.378 ms 0.190–0.199 ms 0.189–0.205 ms

So splitting the step costs nothing measurable. A first round just before these read 0.397 ms worst and 0.255 ms median for the head under -b, lower than every later run under -b, and 0.397 ms and 0.293 ms at default QoS.

How to read them.

  • The worst tick on the slower tier is 0.51–0.53 ms in every run above but that first one, under the 1.3 ms the plan estimated, so nothing called for an optimisation, and src/physics/ gains only the phase split and the counts.
  • At default QoS, at these loads, the worst tick is 0.24–0.28 ms and the median 0.17–0.20 ms in every run but that first one. Under a heavier load a default-QoS thread runs on the slower tier for some ticks, or for the whole run, as plan run 1680 found.
  • The worst ticks come while the two rows of dummies lie piled on each other and against the cars, mostly about 2.8–3.2 s in (ticks 167–192), with 206–222 contact points. They split as about 0.19 ms finding contacts, 0.32 ms solving and under 0.01 ms in the sweep.

Checks

  • The gate fails with the bench. In a git archive arm of fe94f97 with BUDGET_NS set to 200 000 (0.2 ms, below the measured figure), bash tools/gate.sh full built the app, built the bench, and failed at it with exit 1 before the tests:

    worst tick: 0.531917 ms, at tick 66
    ...
    FAIL: the worst tick takes 0.531917 ms, over the 0.200000 ms budget
    gate.sh exit=1
    

    tools/gate.sh and the bench are unchanged since.

  • The existing tests' results are as they were. Every one of the 37 existing tests/*.sx was copied as a v_ test whose check, near, nearVec and within print every call. The copies ran in git archive arms of master ef94076 and of fe94f97, and the outputs (2,883 printed figures) are byte-identical. src/physics/ and src/sim/ are unchanged since fe94f97.

  • The scene test's figures at the head (39a9a74):

    • deepest under the terrain: 7.4 mm, at tick 20, a wheel landing (the plan's 1D model: 6.6–7.6 mm); the deepest dummy part: 5.3 mm;
    • most past a limit: 7.43° (the plan's models: 0.43–10.57°);
    • widest wheel hinge: 2.64 cm, closed within 1 tick (the plan's model: 22.3–27.1 mm);
    • the cars hop about 0.14 m (the plan: 0.10–0.17 m).
  • Mutants of the head (39a9a74), each run in a git archive arm against tests/physics_bench_scene.sx:

    • killed:

      • no map contacts for capsules: the dummies fall through ("finds no terrain under it at tick 35"; 0.9998 m below), and no dummy meets a car or another dummy;
      • dummy joints without limits: 179.89° past;
      • a joint rigid in the biased pass: 26.16° past;
      • the layout of 0ac6ff9: six dummies meet no other dummy;
    • within the test's gross bounds, as the task expects; step 4's cases pin these rules:

      • only the stopped body rewound by the sweep: 7.5 mm, 7.33°, 2.73 cm;
      • the map's contacts first: 18.7 mm, 1.50 cm;
      • limits acting only once passed: 17.72°, 12.2 mm.

      The review found each killed by physics_joints, physics_bodies, physics_stack or sim_dummy.

    • At 0ac6ff9 a static counter that changed one of two worlds' gravity was killed (the runs differ at tick 0), and a hinge whose axes are never aligned made the test run past 10 minutes, and the run was stopped.

    The first mutant run showed the limits check reading the limits from the joints it checked, so removing them passed. The test reads them from the dummy template (542adea).

Gates

  • fast, gate run 939, at fe94f97: passed (40 passed, 0 failed; its bench read 0.502 ms worst under taskpolicy -b).
  • full, gate run 940, at 08a8f53: passed (40 passed, 0 failed; its bench read 0.507 ms worst under taskpolicy -b).
  • full, gate run 941, at 0ac6ff9: passed (40 passed, 0 failed; its bench read 0.521 ms worst under taskpolicy -b).
  • full, gate run 951, at 39a9a74, the head: passed (40 passed, 0 failed; its bench read 0.519 ms worst under taskpolicy -b).
Tracker: [#3904](https://sak.swipelab.com/tasks/3904) Step 5 of plan #3865. This PR holds the physics to its budget with a built bench that the gate now runs on m5's slower core tier. The measurement called for no optimisation: the worst tick is about 0.52 ms on the slower tier, about a quarter of the budget. ## What it adds - **The scene**, `tools/bench/physics_scene.sx` (`benchScene`). It builds a physics world over the first map's collision world (`baked/first_map.hbw`) with the bench's own bodies alone, on the level clearing 13.5 m in front of the first spawn marker: 116 bodies and 104 joints. - **4 vehicles.** Each has a convex hull chassis made with `hullOf` (4.4 m long, 1.4 m tall, 1.9 m wide, 1500 kg, centred on its centroid) and four 25 kg sphere wheels of radius 0.35 m, hinged at its corners with their axes across the car. They stand level, side by side, 1 m apart, with their wheels 0.5 m above the ground. - **8 ragdolls.** These are free dummies from `src/sim/dummy.sx`'s template, added by the new `freeDummy`, which `hangDummy` now shares its code with. They stand in two rows, tilted 5–25° toward the cars, with their lowest points 0.3–0.6 m up: - the inner row 0.9 m from the cars' ends, a dummy behind each car; - the outer row 1 m behind it, a dummy behind each of the inner row's. The one behind the first stands 0.25 m aside, clear of the frame's post beside it. Every body starts at least 0.38 m from any body of another vehicle or dummy. No torso part's top starts more than 2.11 m above the terrain, and no vehicle starts above a dummy. - **Placement.** The dummies' frame stands at the back of the clearing, so the dummies stand on its side and push the cars away from it. The free-wheeled cars roll at up to about 0.25 m/s when a dummy topples against an end. Laid out the other way round, one car rolled into a frame post. - **What it does.** The cars land, hop about 0.14 m and land again. Each inner dummy topples against its car's end, and each outer dummy topples onto the inner dummy in front of it. Over the 600 ticks, the contacts between bodies of different cars and dummies are these (first tick, then ticks holding a contact): | dummy | meets | |---|---| | 0 | car 0 (tick 49, 267 ticks), dummy 4 (47, 455) | | 1 | car 1 (69, 30), dummy 5 (59, 438) | | 2 | car 2 (36, 23), dummy 6 (57, 192) | | 3 | car 3 (47, 33), dummy 7 (54, 347) | | 4 | dummy 0 (47, 455) | | 5 | dummy 1 (59, 438) | | 6 | dummy 2 (57, 192) | | 7 | dummy 3 (54, 347) | No dummy holds a contact with the frame's posts (near-upright map triangles). - **The bench**, `tools/bench/physics.sx`, built into `sx-out/tools/physics_bench`. - It steps the scene 600 ticks, 5 times from the same start. - It times each tick by the stepping thread's CPU time, through `clock_gettime_nsec_np(CLOCK_THREAD_CPUTIME_ID = 16)` declared `extern`. - A tick's cost is its least time over the 5 runs. - It prints the worst and the median tick. For the worst tick it also prints the bodies awake, the contact points, the bodies swept, the triangles they took a time of impact against, the groups the sweep stopped, and the time spent finding contacts, solving, and sweeping and sleeping. - It exits 1 when the worst tick passes 2 ms. - **The phases.** `PhysicsWorld.step` now runs as `begin` (wake, find contacts, warm), `solve` (substeps, restitution, store) and `end` (sweep, sleep), and the bench times each one. `PhysicsWorld.counts` holds what the last step did. The code and its order are unchanged (see below). - **The gate.** `tools/gate.sh` builds the bench after the app and runs it under `taskpolicy -b`, before the tests. - **The test**, `tests/physics_bench_scene.sx`. It steps the same scene 600 ticks under `tools/sx run`, about 2 s, and checks: - **Meetings:** every car holds a contact with a dummy, and every dummy with another dummy, on some tick. With the layout of 0ac6ff9 (the outer row between the inner row's dummies), it fails for dummies 0, 1, 3, 4, 5 and 7. - **Terrain:** no body's lowest point ends a tick more than 6 cm below the terrain under it. The terrain is read by a ray down from 1 m above that point, which stays below the frame's beam. A ray that finds no terrain fails the check. - **Bounds:** no body leaves the map's bounds. - **Limits:** every dummy joint stays within its template's limits plus 20° on every tick. - **Wheels:** no wheel's hinge opens more than 5 cm, and one opened past 1 cm closes within 5 ticks. - **Determinism:** two runs are bit-identical at every tick. - **Start:** the bodies start clear of each other. - **README.** The Tests section names the bench, what it steps, how it times a tick, the tier it runs on, and its bound. ## Bench figures (m5) At the head, 39a9a74, from the bench gate run 951 built, at a one-minute load of about 4 (14:21 EET). `taskpolicy -b sx-out/tools/physics_bench`: ``` physics bench: 116 bodies, 104 joints, 600 ticks, the least of 5 runs each worst tick: 0.514625 ms, at tick 189 median tick: 0.381042 ms worst tick held 116 bodies awake and 222 contact points; it swept 2 bodies, took a time of impact against 0 triangles and stopped 0 groups worst tick spent 0.191000 ms finding contacts, 0.317500 ms solving, 0.006125 ms sweeping and sleeping within the 2.000000 ms budget ``` At default QoS, `sx-out/tools/physics_bench`: ``` physics bench: 116 bodies, 104 joints, 600 ticks, the least of 5 runs each worst tick: 0.239792 ms, at tick 187 median tick: 0.174333 ms worst tick held 116 bodies awake and 220 contact points; it swept 0 bodies, took a time of impact against 0 triangles and stopped 0 groups worst tick spent 0.092834 ms finding contacts, 0.144250 ms solving, 0.002708 ms sweeping and sleeping within the 2.000000 ms budget ``` A second round a minute later read 0.513542 ms worst and 0.381125 ms median under `-b`, and 0.239166 ms and 0.175584 ms at default QoS. Gate run 951's own bench read 0.519417 ms worst and 0.381333 ms median under `-b`. **Master before any optimisation.** Master's own `src/physics/` (ef94076) has no phases or counts, so this bench times its whole `step()`. It ran in a `git archive` arm of master with this PR's scene (39a9a74's) and `src/sim/dummy.sx` added, interleaved with the head's runs above: ``` 14:21, taskpolicy -b: worst tick: 0.524708 ms, at tick 284 median tick: 0.377791 ms 14:21, default QoS: worst tick: 0.238458 ms, at tick 92 median tick: 0.173125 ms 14:21, taskpolicy -b: worst tick: 0.518042 ms, at tick 189 median tick: 0.378625 ms 14:21, default QoS: worst tick: 0.239250 ms, at tick 185 median tick: 0.172917 ms ``` **Interleaved rounds.** Five earlier rounds at a load of about 3.5 (14:18–14:19) alternated a bench built from 39a9a74's sources in a `git archive` arm and master's: | | head, `-b` | master, `-b` | head, default | master, default | |---|---|---|---|---| | worst tick | 0.515–0.529 ms | 0.512–0.520 ms | 0.259–0.279 ms | 0.261–0.281 ms | | median tick | 0.378–0.380 ms | 0.350–0.378 ms | 0.190–0.199 ms | 0.189–0.205 ms | So splitting the step costs nothing measurable. A first round just before these read 0.397 ms worst and 0.255 ms median for the head under `-b`, lower than every later run under `-b`, and 0.397 ms and 0.293 ms at default QoS. **How to read them.** - The worst tick on the slower tier is 0.51–0.53 ms in every run above but that first one, under the 1.3 ms the plan estimated, so nothing called for an optimisation, and `src/physics/` gains only the phase split and the counts. - At default QoS, at these loads, the worst tick is 0.24–0.28 ms and the median 0.17–0.20 ms in every run but that first one. Under a heavier load a default-QoS thread runs on the slower tier for some ticks, or for the whole run, as plan run 1680 found. - The worst ticks come while the two rows of dummies lie piled on each other and against the cars, mostly about 2.8–3.2 s in (ticks 167–192), with 206–222 contact points. They split as about 0.19 ms finding contacts, 0.32 ms solving and under 0.01 ms in the sweep. ## Checks - **The gate fails with the bench.** In a `git archive` arm of fe94f97 with `BUDGET_NS` set to 200 000 (0.2 ms, below the measured figure), `bash tools/gate.sh full` built the app, built the bench, and failed at it with exit 1 before the tests: ``` worst tick: 0.531917 ms, at tick 66 ... FAIL: the worst tick takes 0.531917 ms, over the 0.200000 ms budget gate.sh exit=1 ``` `tools/gate.sh` and the bench are unchanged since. - **The existing tests' results are as they were.** Every one of the 37 existing `tests/*.sx` was copied as a `v_` test whose `check`, `near`, `nearVec` and `within` print every call. The copies ran in `git archive` arms of master ef94076 and of fe94f97, and the outputs (2,883 printed figures) are byte-identical. `src/physics/` and `src/sim/` are unchanged since fe94f97. - **The scene test's figures at the head (39a9a74):** - deepest under the terrain: 7.4 mm, at tick 20, a wheel landing (the plan's 1D model: 6.6–7.6 mm); the deepest dummy part: 5.3 mm; - most past a limit: 7.43° (the plan's models: 0.43–10.57°); - widest wheel hinge: 2.64 cm, closed within 1 tick (the plan's model: 22.3–27.1 mm); - the cars hop about 0.14 m (the plan: 0.10–0.17 m). - **Mutants** of the head (39a9a74), each run in a `git archive` arm against `tests/physics_bench_scene.sx`: - **killed:** - no map contacts for capsules: the dummies fall through ("finds no terrain under it at tick 35"; 0.9998 m below), and no dummy meets a car or another dummy; - dummy joints without limits: 179.89° past; - a joint rigid in the biased pass: 26.16° past; - the layout of 0ac6ff9: six dummies meet no other dummy; - **within the test's gross bounds,** as the task expects; step 4's cases pin these rules: - only the stopped body rewound by the sweep: 7.5 mm, 7.33°, 2.73 cm; - the map's contacts first: 18.7 mm, 1.50 cm; - limits acting only once passed: 17.72°, 12.2 mm. The review found each killed by physics_joints, physics_bodies, physics_stack or sim_dummy. - At 0ac6ff9 a static counter that changed one of two worlds' gravity was killed (the runs differ at tick 0), and a hinge whose axes are never aligned made the test run past 10 minutes, and the run was stopped. The first mutant run showed the limits check reading the limits from the joints it checked, so removing them passed. The test reads them from the dummy template (542adea). ## Gates - `fast`, gate run 939, at fe94f97: passed (40 passed, 0 failed; its bench read 0.502 ms worst under `taskpolicy -b`). - `full`, gate run 940, at 08a8f53: passed (40 passed, 0 failed; its bench read 0.507 ms worst under `taskpolicy -b`). - `full`, gate run 941, at 0ac6ff9: passed (40 passed, 0 failed; its bench read 0.521 ms worst under `taskpolicy -b`). - `full`, gate run 951, at 39a9a74, the head: passed (40 passed, 0 failed; its bench read 0.519 ms worst under `taskpolicy -b`).
agra merged commit ff70931b43 into master 2026-10-30 15:24:56 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sx/hulken!24
No description provided.