You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(cliffs): the collision box is the RAW rectangle - #88 was wrong (#84) (#90)
* docs(cliffs): shipping orientation is 2.4%, not 2.0% (#84)
The banner kept the pre-#88 figure. Since the collision box was corrected to
rotbb's rotated rectangle the rejection costs ZERO true positives, so the
shipping matched set is identical to the unfiltered one - 1531 either way, 37
wrong orientations, 2.42%. Measured on both paths.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRKSNgkidxc6daeHGJHqpt
* fix(cliffs): the collision box is the RAW rectangle - #88 was wrong (#84)
Disassembly, prompted by flagging #88's fix as the thing I was least confident
about. It was right to flag: #88 scored best on every metric and was wrong about
the mechanism.
The engine does NOT collide against rotbb's rectangle rotated 45 degrees. It uses
the RAW stored rectangle and discards the orientation tag entirely:
1. EntityMapGenerationTask::tryToAddCliff (0x101625038) loads the orientation's
box from proto + 0x5c0 + id*0x48 (20 bytes: four int32 edges at +4, the
orientation word at +0x14) and calls wouldCollide with Direction = 0 -
literally `mov x4, #0x0`.
2. wouldCollide (0x101625468) forwards box and direction to
BoundingBox::BoundingBox(BoundingBox const&, Direction) (0x101c04380), then
floors with (box + position) >> 8 over an inclusive tile rect.
3. That constructor zeroes the destination, writes sentinel 0x80010000 into the
destination's orientation word, and dispatches through a jump table whose
entry 0 is 0 (read at 0x102d01400) - the identity arm, which copies
left_top/right_bottom verbatim. The source orientation is never read; the
rotate arm is reachable only for a non-zero Direction.
Corroborated by the API mirror: BoundingBox is documented as {MapPosition,
MapPosition} or {..., RealOrientation} with orientation OPTIONAL, and
OrientedCliffPrototype::collision_bounding_box is a plain BoundingBox.
| box | false rej | recall | precision | evidence |
| ------------------------------ | --------- | ------ | --------- | ---------- |
| AABB (until #88) | 13 | 0.9675 | 0.9743 | assumption |
| 45-degree oriented rect (#88) | 0 | 0.9758 | 0.9727 | fit only |
| raw stored rect (this) | 6 | 0.9720 | 0.9713 | disasm |
The middle row is the trap. It shrank the box past the engine's, and the excess
shrinkage absorbed a DIFFERENT defect: 4 of the 6 cliffs the correct box still
rejects are cells where our orientation disagrees with the game's, so we load the
wrong box entirely. Those 4 belong to the standing orientation residual and
should stay visible. A model that scores perfectly by hiding a second bug is
worse than one that leaves it exposed.
Edges are quantised to 1/256 - MapPosition is 8-bit fixed point, so x_dist's
sqrt(2) cannot reach the engine at full precision.
test/cliffOrientation.spec.ts asserted the OPPOSITE of this until now, on the
stated reasoning that the AABB "is what the engine ends up scanning". That block
is rewritten: the shipped box is the raw rectangle, it DEPENDS on intersect (the
old file asserted independence), and its area is at most half the AABB's - though
it is not contained in it, since a small intersect pushes hx past size/2. An
attempt to assert containment on every axis failed for exactly that reason.
cliffBoxCoversTile and the separating-axis narrow phase are deleted; the broad
phase alone is now the whole rule, which is what the engine does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRKSNgkidxc6daeHGJHqpt
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments