WIP
This commit is contained in:
@@ -3,17 +3,17 @@
|
||||
You found this one yourself too, chasing the capstone's ledge: the hero gets
|
||||
pushed out of the moving ledge, lands **inside the pillar**, and the `NaN` you
|
||||
buried in step 12 climbs right back out of its grave. Your autopsy chain from
|
||||
last time is unchanged — the only new thing is *how a box you just freed ends
|
||||
up inside a wall again in the very same frame*.
|
||||
last time is unchanged — the only new thing is _how a box you just freed ends up
|
||||
inside a wall again in the very same frame_.
|
||||
|
||||
> Still a *fourth* thing, separate from the two bugs you're hunting in
|
||||
> Still a _fourth_ thing, separate from the two bugs you're hunting in
|
||||
> `engine/system/physics.ts` — no spoilers there.
|
||||
|
||||
## Why one pass isn't enough
|
||||
|
||||
Step 12's `safeMoveAndSlide` walks the walls **once, in array order**, fixing
|
||||
each overlap it meets. For one wall that's airtight. But a depenetration push is
|
||||
a *teleport* — and a teleport can land you inside a wall the loop already
|
||||
a _teleport_ — and a teleport can land you inside a wall the loop already
|
||||
checked and cleared, or one it hasn't reached yet (in which case it works, by
|
||||
luck of the ordering). A resolver whose correctness depends on the order of the
|
||||
wall array isn't a resolver — it's a coin flip.
|
||||
@@ -28,7 +28,7 @@ Two experiments in a scratch file, **predicting each outcome before running**
|
||||
2. Walls `{x: 10, y: -5, w: 4, h: 10}` and `{x: 6.5, y: -5, w: 2, h: 10}` — a
|
||||
gap 1.5 wide. Hero (2 wide) at `{x: 9, y: 0}`. Apply `penetrationVector`
|
||||
pushes in a loop and log `x` each time. Does it converge? What number does
|
||||
`x` bounce between, and *why will it never stop*?
|
||||
`x` bounce between, and _why will it never stop_?
|
||||
|
||||
## The negotiation, and when it honestly fails
|
||||
|
||||
@@ -36,11 +36,11 @@ The fix for experiment 1 is patience: don't do one pass — **repeat whole passe
|
||||
until a full pass finds nothing to fix**. That clean pass is your proof of
|
||||
freedom. Each pass is cheap, and in sane geometry it settles in one or two.
|
||||
|
||||
But experiment 2 shows the negotiation can be *unwinnable*: when the gap is
|
||||
But experiment 2 shows the negotiation can be _unwinnable_: when the gap is
|
||||
narrower than the box, **no overlap-free position exists**. No amount of math
|
||||
fixes that, because it isn't a math problem — it's a game-design question, and
|
||||
every game answers it differently. Mario between a Thwomp and the floor:
|
||||
crushed = death. Some engines let the wall shove you *through* its partner.
|
||||
every game answers it differently. Mario between a Thwomp and the floor: crushed
|
||||
= death. Some engines let the wall shove you _through_ its partner.
|
||||
Zelda-flavored games mostly refuse the situation: solid wins, the hero holds
|
||||
still until the gap opens. We take that one — it's the smallest honest answer:
|
||||
**cap the passes, and if the cap fires, report it** (`settled: false`) instead
|
||||
@@ -48,9 +48,9 @@ of pretending. You already believe in caps; your step-10 loop carries one for
|
||||
exactly the same reason.
|
||||
|
||||
Last session you proposed armoring `sweepInterval` against the `Infinity`
|
||||
directly. You can — see the optional section — but notice what that answer
|
||||
skips over: even with the `NaN` gone, *what should a crushed hero do?* The
|
||||
kernel can't know; it only measures. Deciding is the resolver's job. Keeping
|
||||
directly. You can — see the optional section — but notice what that answer skips
|
||||
over: even with the `NaN` gone, _what should a crushed hero do?_ The kernel
|
||||
can't know; it only measures. Deciding is the resolver's job. Keeping
|
||||
**detection** and **policy** separate is the actual lesson of this step.
|
||||
|
||||
## Task
|
||||
@@ -67,8 +67,8 @@ Two functions in `crush.ts`:
|
||||
One warning on the `EPSILON` slack: apply it **only along the axis you actually
|
||||
pushed**. Before you port your step-12 slack code verbatim, play computer with
|
||||
`pv = { x: -3, y: 0 }` and watch what your two lines do to `y`. (That was my
|
||||
"bonus" question last session — it's still open, and one of the tests refuses
|
||||
to look away.)
|
||||
"bonus" question last session — it's still open, and one of the tests refuses to
|
||||
look away.)
|
||||
|
||||
```sh
|
||||
bun test workshop/steps/13-crush
|
||||
@@ -76,13 +76,13 @@ bun test workshop/steps/13-crush
|
||||
|
||||
## Optional 1 — the airbag
|
||||
|
||||
Defense in depth: even if some future caller hands `moveAndSlide` an
|
||||
overlapping box directly, it should return finite numbers — wrong-ish, maybe,
|
||||
but *finite*. You traced in step 12 exactly which value poisons the well. The
|
||||
loop already clamps it once (`Math.max(0, …)`) — find the **other** line that
|
||||
trusts `nearest.time` to be non-negative. The fix is almost nothing. Then write
|
||||
the test step 12 should have had: `moveAndSlide` (not `safe…`) with an
|
||||
overlapping start returns finite coordinates.
|
||||
Defense in depth: even if some future caller hands `moveAndSlide` an overlapping
|
||||
box directly, it should return finite numbers — wrong-ish, maybe, but _finite_.
|
||||
You traced in step 12 exactly which value poisons the well. The loop already
|
||||
clamps it once (`Math.max(0, …)`) — find the **other** line that trusts
|
||||
`nearest.time` to be non-negative. The fix is almost nothing. Then write the
|
||||
test step 12 should have had: `moveAndSlide` (not `safe…`) with an overlapping
|
||||
start returns finite coordinates.
|
||||
|
||||
## Optional 2 — the capstone payoff
|
||||
|
||||
@@ -93,4 +93,4 @@ squirt around the ledge like a watermelon seed pinched between two fingers.
|
||||
|
||||
Then earn the effect: the ledge is 16 tall and the hero is 12. As the ledge digs
|
||||
deeper, which of `penetrationVector`'s four escapes wins, and at what depth does
|
||||
the winner change? That flip *is* the watermelon seed.
|
||||
the winner change? That flip _is_ the watermelon seed.
|
||||
|
||||
Reference in New Issue
Block a user