Skip to content

test(world): cover a blast inside a shape and null-world readers - #352

Merged
siracozmen01 merged 4 commits into
mainfrom
blast-inside-tests
Nov 8, 2026
Merged

siracozmen01 merged 4 commits into
mainfrom
blast-inside-tests

Conversation

@siracozmen01

@siracozmen01 siracozmen01 commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

No test sets off a blast inside a shape, or reads a null world's profile, kinetic energy or counters. A change to the blast's inside fallback or its center-of-mass skip, or to the three readers' null-world zeros their headers promise, would pass every suite (#351). The docs did not say what a blast inside a shape does: they promise "an outward impulse at its closest point", and for a shape with the blast inside it that point is the blast itself, where outward has no direction.

Change

m2ExplosionDef's header in include/maul2d/world.h and the explosions section of docs/user/bodies-and-shapes.md now state the rule the code follows: "A shape with the blast inside it, its rounding included, is pushed in the direction from the blast to its body's center of mass; a blast inside it at the center of mass itself pushes nothing."

test/test_world.c gains two tests that pin documented behavior:

  • TestBlastInsideAShape, a 1 m box without gravity:
    • A default blast at 0.25 m right of its center, inside the box, pushes it straight left: x velocity below -0.5, y velocity 0 and no spin.
    • A blast at its center of mass leaves its velocity, spin and world hash as they were.
  • TestNullWorldReadsZero: m2World_GetProfile(NULL) is zeroed, m2World_GetKineticEnergy(NULL) is 0, and m2World_GetCounters(NULL) is zeroed.

No result changes: the code is as it was.

Verification

  • test_world passes.
  • Mutants, each built into a scratch library and run against test_world:
Mutant Fails
a blast inside a shape uses the surface normal the inside blast
the inside fallback points toward the center the inside blast
a blast at the center of mass is not skipped the center-of-mass blast
a null world's profile is not zeroed the profile check
a null world's kinetic energy is 1 the energy check
a null world's counters count a body the counters check
  • tools/check.sh changed passes, the documentation check included. The changed files compile clean with gcc-14 and clang-20 under the strict flags.
  • test_golden prints the 13 lines of test/hashes.txt unchanged in the scalar (linux-clang-20-asan) and AVX2 (linux-clang-20) presets.

Closes #351

@siracozmen01 siracozmen01 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at 95f1f73: changes needed (1), unless a doc already states the rule below. Read, not run.

1. The blast-inside expectation pins behavior no doc I can find states.

  • m2ExplosionDef's header says each shape "whose closest point lies within radius + falloff gets an outward impulse at that point".
  • For a blast inside a shape, the closest point is the blast's own position, at distance 0. "Outward" from it has no direction there.
  • The test's rule is the library's choice for that case: the impulse acts away from the center of mass, through the blast point, so the box moves straight left with no spin, and a blast at the center of mass does nothing at all, hash included. Sensible, but world.h does not say it, and I found no user doc that does.
  • As written, the test fixes the implementation, not a promise. A change that pushed away from the closest face, for example, would break it while keeping to the header.
  • Please add the rule where blasts are documented, or point me to where it already is. With that, the test checks a documented result. The rule: a blast inside a shape pushes its body away from its center of mass along the line through the blast point, and one at the center of mass leaves it as it was.

The null readers match their headers exactly.

  • m2World_GetKineticEnergy: "0 for a null world".
  • m2World_GetProfile: "zeroed for a null world".
  • m2World_GetCounters: "zeroed for a null world".
  • The test reads the profile's five times and four of the counters. Those can each fail, and checking a subset of fields is enough to catch a mutant that returns uninitialized or nonzero values.

@siracozmen01 siracozmen01 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delta 95f1f73..54e418f: changes needed (1), the new sentence only. Read, not run; checks were pending at the time of review.

I checked the sentence against BlastShape (src/destruction.c:36-85). The direction is right. When dist = d.distance - target.radius is not above 0, dir = lc - local, and lc is localCenters[body], the body's center of mass, not the shape's centroid. So "center of mass" is correct, multi-shape bodies included, and len == 0 returns before any push or wake. Two parts of the wording go further than the code:

  1. "Pushed out through its body's center of mass" reads as an impulse whose line passes through the center of mass, so no spin. That holds only for a blast inside the core. The impulse lands at hitLocal = d.pointA + target.radius * d.normal, and the torque uses arm = hitLocal - lc. For a blast in a rounded shape's skin, dist <= 0 still takes this branch, but pointA is the closest core point, not the blast.

    • Example: a capsule from (-1, 0) to (1, 0) with radius 0.5, its center of mass at the origin, and a blast at (1.2, 0.3). The distance to the core is 0.36, under the radius, so the blast is inside.
    • Then dir is about (-0.97, -0.24), and hitLocal is about (1.28, 0.42).
    • arm x dir comes to about 0.07 per unit impulse, not 0, so the body spins.

    The test's box has no radius, so it pins only the core case.

  2. "A blast at the center of mass itself pushes nothing" holds only when that point is inside the shape. When the center of mass lies outside every shape, the normal branch runs, dist > 0, and each shape in reach is pushed and woken. Example: a body of a 1 m box at x = -1 and a 3 m box at x = 3, with its center of mass between them, and a blast placed there.

Suggested wording that matches the code: "A shape with the blast inside it, its rounding included, is pushed in the direction from the blast to its body's center of mass; a blast inside it at the center of mass itself pushes nothing." Then put the same words in world.h, bodies-and-shapes.md and the test comment.

@siracozmen01 siracozmen01 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delta 54e418f..002ff17: ready. The finding is fixed. Read, not run.

world.h's m2ExplosionDef comment, bodies-and-shapes.md and the test comment now carry the same sentence: "A shape with the blast inside it, its rounding included, is pushed in the direction from the blast to its body's center of mass; a blast inside it at the center of mass itself pushes nothing." This matches BlastShape (destruction.c:57-67):

  • dist <= 0 takes the branch, so the rounding is included.
  • dir = lc - local is the body's center of mass, a direction only, which makes no claim about spin.
  • len == 0 returns before any push or wake, and only on that branch, so only for a blast inside the shape.

The test still pins the core cases it can: straight left with no spin, and nothing at the center.

@siracozmen01 siracozmen01 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at 04ab568: ready, once CI goes green (every check was still pending when I read it).

  • Merge resolution: the diff against main touches only include/maul2d/world.h, docs/user/bodies-and-shapes.md and test/test_world.c. The merge of main brought in nothing else and dropped nothing.
  • TestBlastInsideAShape: the blast at (0.25, 0) sits inside the 1 m box and on the line through its center of mass. The impulse along that line has no y part and no lever arm, so v.y == 0 and zero spin hold exactly, and v.x < -0.5f fails for both of the wrong directions (the surface normal and toward the center). The center-of-mass blast checks the velocity, the spin and the hash, so a skipped push that still touched the state would also fail.
  • TestNullWorldReadsZero reads all five m2Profile fields, the energy, and the body, shape, joint and misuse counts. That covers the mutants listed in the body.
  • The new header and docs sentence matches the rule the manager settled for BlastShape (direction from the blast to the body's center of mass). It is the same in world.h and in the user docs.
  • Title and body follow the standard. No body line starts at column 0 with a name and (.

No findings.

@siracozmen01
siracozmen01 merged commit 8462704 into main Nov 8, 2026
29 checks passed
@siracozmen01
siracozmen01 deleted the blast-inside-tests branch November 8, 2026 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A blast inside a shape and null-world readers go untested

1 participant