Say what was hit, which way is out, and how far

The narrowphase has been producing a contact since it went in, and the interpreter
was throwing it away. `RCOLLISION(n, f)` reports it: what was hit (a sprite or a
`SOLID` rectangle), which one, the contact normal, the penetration depth, the
contact point, and which axis to reverse.

**The normal points out of the other thing and toward this one**, so a program
moves along it by the depth and is exactly clear. That sign is the one assertion
in the new test that could not be caught any other way -- both parties of a
sprite-against-sprite hit get their own record, each pointing the way *that*
sprite has to move, and sharing one would tell both to go the same direction,
which is how two things end up stuck inside each other.

**Field 7 is the one that deletes the most BASIC.** It is the minimum translation
axis, computed from the normal in C, and it is there because doing it in BASIC
means comparing two floats -- which is exactly where this dialect's left-operand
rule catches people. `BALLBRICKS`/`TESTCELL` in the artwork breakout spend six
lines computing an overlap rectangle and comparing its width to its height to get
this number.

The record is **sticky and deepest-wins**: replaced whenever that sprite is in a
contact and otherwise left alone, so `BUMP` stays the event and this stays the
detail of it. Making it clear itself when nothing touches would break the pairing,
because `BUMP` accumulates across steps and a once-a-frame poll would find the
detail already gone. Reading `BUMP` clears both, so they cannot disagree.

Deliberately narrower than `akgl_Contact`: no actor pointers, because BASIC has no
actor; no tile fields, because there is no tilemap; no z, because every test is
planar; and **no `dt` and no `sensor`**, which libakgl documents as filled in by
the resolver. This interpreter never resolves anything, so those two come back
zero and mean nothing, and an always-zero field in a reference table is a lie.

Documented with the two caveats that matter: fields 2, 3 and 4 are floats and want
a `%` variable, and the contact *point* is exact only for two boxes -- libakgl's
solver returns a point on the portal it converged to, while the normal and depth
are exact for every pair.

Chapter 8's collision section stops claiming only type 1 exists, which has been
false since the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
This commit is contained in:
2026-08-02 10:53:26 -04:00
parent 5709dc160c
commit a9600c3fcc
12 changed files with 401 additions and 4 deletions

View File

@@ -2220,7 +2220,12 @@ should loop — and the third is why both games are built out of `LABEL` and `GO
`akgl_CollisionShape` and a pooled proxy. Three things were deliberately left for later, and
each is here so the reasoning does not have to be reconstructed.
38. **Finish moving `COLLISION` and `BUMP` onto the subsystem.** What landed is the narrowphase
38. **Finish moving `COLLISION` and `BUMP` onto the subsystem.** **Half done.** The contact
is no longer discarded: `RCOLLISION(n, f)` reports what was hit, the normal, the depth,
the contact point and the minimum translation axis, and
`tests/akgl_backends.c::test_contact_geometry` pins the normal's *sign* for both parties
of a sprite-against-sprite hit -- getting that backwards tells both to move the same way,
and nothing else would have noticed. What stands is the pairing and the threshold below. What landed is the narrowphase
and the shapes; the seam is still `collisions(self, uint16_t *mask)` and the pairing is
still all-against-all over eight slots with a bounding-box reject in front of it.