HOOK_CHAIN: Rückfall-Prüfung auf einen einzelnen Haken läuft zu spät und ist wirkungslos #6

Closed
opened 2026-08-31 21:01:54 +02:00 by gilli · 1 comment
Owner

⁨⁩⁨HOOK_CHAIN: Rückfall-Prüfung auf einen einzelnen Haken läuft zu spät und ist wirkungslos⁩ ⁨⁩#⁨6⁩⁨⁩"
By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩
bug
level-generation
medium

Komponente: CORE (buildPattern)
Betroffene Version: main @ 238ed66, index.html Zeilen 934–943

Beschreibung

Die Ketten-Hakengrube will auf genau einen Haken zurückfallen, wenn der zweite Haken nicht mehr
in die Grube passt:

// index.html:934-943
const firstFx = hookCount === 1 ? rng.range(0.45, 0.7) : 0.4;
const spacing = hookCount === 1 ? 0 : rng.range(252, 286);
for (let i = 0; i < hookCount; i++) {
  const hx = e0 + (i === 0 ? firstFx * w : firstFx * w + i * spacing);
  const hy = baseY - (hookCount === 1 ? rng.range(248, 300) : rng.range(252, 288) + i * 8);
  c.hooks.push({ id: 0, x: clamp(hx, e0 + 60, g1 - 26), y: hy, ... });   // beide Haken schon angelegt
}
if (hookCount === 2 && firstFx * w + spacing > w - 40) hookCount = 1;    // <-- zu spät, ohne Effekt

Zu diesem Zeitpunkt stehen bereits zwei Haken in c.hooks, und c.pits[0].hooks wurde
schon mit hookCount === 2 befüllt (Zeile 933). Die Zuweisung hookCount = 1 ändert an
Layout und Metadaten nichts mehr – toter Code.

Folgeverhalten

Der zweite Haken wird bei knapp bemessener Grubenbreite über clamp(..., g1 - 26) an die
rechte Grubenkante gezerrt:

  • w = 1.6 … 1.85 × jumpDistance; bei v = 360 (jumpDistance = 270) → w = 432 … 499.
  • firstFx·w + spacing = 173…200 + 252…286 = 425…486, Grenze w − 40 = 392…459 → Bedingung
    greift häufig.
  • Ergebnis: zweiter Haken bei g1 − 26, also 26 px vor der Landekante und damit praktisch über
    dem Landepunkt statt über der Grube.

Damit ist das Muster „HOOK_CHAIN“ nicht mehr deterministisch wie dokumentiert
(AGENTS.md: HOOK_CHAIN = zwei Haken in Folge), und die Hakenplatzierung weicht von der
Vorgabe „Haken sitzen … seitlich über oder kurz vor der Grube“ ab.

Nicht verletzt

Die harten Vorgaben zur Löschbarkeit bleiben erfüllt (über 150 Generierungs-Läufe gemessen):

  • Kein Hakenabstand > 1.3 × GRAB_RADIUS (0 Verstöße).
  • Keine Grube breiter als maxGapJumpable ohne Haken im Wurfraum (0 Verstöße bei
    Nicht-Plattform-Gruben; Plattformgruben werden über ihre Teilintervalle bewertet).
  • Kein Haken exakt auf einer Grubenkante durch das clamp (in 150 Läufen 0 Treffer auf
    e0+60/g1−26). — Der oben beschriebene Fall ist also selten, aber möglich.

Lösungsvorschlag

Entscheidung vor das Anlegen ziehen und die Gruben-Metadaten konsistent halten:

if (hookCount === 2 && firstFx * w + spacing > w - 40) hookCount = 1;   // vor dem loop
const pit = { x0: e0, x1: g1, hooks: hookCount };
for (let i = 0; i < hookCount; i++) { ... }
c.pits.push(pit);

Zusätzlich absichern, dass ein Haken mindestens GRAB_RADIUS·0.6 von der Landekante entfernt
bleibt, damit er nicht über dem Landepunkt hängt.


Dokumentiert in issues/06-hook-chain-rueckfall-pruefung-wirkungslos.md (Commit c171694).

1 comments

⁨⁩⁨HOOK_CHAIN: Rückfall-Prüfung auf einen einzelnen Haken läuft zu spät und ist wirkungslos⁩ ⁨⁩#⁨6⁩⁨⁩" By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩ bug level-generation medium > **Komponente:** CORE (`buildPattern`) > **Betroffene Version:** `main` @ `238ed66`, `index.html` Zeilen 934–943 > > ## Beschreibung > > Die Ketten-Hakengrube will auf genau einen Haken zurückfallen, wenn der zweite Haken nicht mehr > in die Grube passt: > > ```js > // index.html:934-943 > const firstFx = hookCount === 1 ? rng.range(0.45, 0.7) : 0.4; > const spacing = hookCount === 1 ? 0 : rng.range(252, 286); > for (let i = 0; i < hookCount; i++) { > const hx = e0 + (i === 0 ? firstFx * w : firstFx * w + i * spacing); > const hy = baseY - (hookCount === 1 ? rng.range(248, 300) : rng.range(252, 288) + i * 8); > c.hooks.push({ id: 0, x: clamp(hx, e0 + 60, g1 - 26), y: hy, ... }); // beide Haken schon angelegt > } > if (hookCount === 2 && firstFx * w + spacing > w - 40) hookCount = 1; // <-- zu spät, ohne Effekt > ``` > > Zu diesem Zeitpunkt stehen bereits **zwei** Haken in `c.hooks`, und `c.pits[0].hooks` wurde > schon mit `hookCount === 2` befüllt (Zeile 933). Die Zuweisung `hookCount = 1` ändert an > Layout und Metadaten nichts mehr – toter Code. > > ## Folgeverhalten > > Der zweite Haken wird bei knapp bemessener Grubenbreite über `clamp(..., g1 - 26)` an die > rechte Grubenkante gezerrt: > > * `w = 1.6 … 1.85 × jumpDistance`; bei `v = 360` (`jumpDistance = 270`) → `w = 432 … 499`. > * `firstFx·w + spacing = 173…200 + 252…286 = 425…486`, Grenze `w − 40 = 392…459` → Bedingung > greift häufig. > * Ergebnis: zweiter Haken bei `g1 − 26`, also 26 px vor der Landekante und damit praktisch über > dem Landepunkt statt über der Grube. > > Damit ist das Muster „HOOK_CHAIN“ nicht mehr deterministisch wie dokumentiert > (`AGENTS.md`: `HOOK_CHAIN` = zwei Haken in Folge), und die Hakenplatzierung weicht von der > Vorgabe „Haken sitzen … seitlich über oder kurz vor der Grube“ ab. > > ## Nicht verletzt > > Die harten Vorgaben zur Löschbarkeit bleiben erfüllt (über 150 Generierungs-Läufe gemessen): > > * Kein Hakenabstand > `1.3 × GRAB_RADIUS` (0 Verstöße). > * Keine Grube breiter als `maxGapJumpable` ohne Haken im Wurfraum (0 Verstöße bei > Nicht-Plattform-Gruben; Plattformgruben werden über ihre Teilintervalle bewertet). > * Kein Haken exakt auf einer Grubenkante durch das `clamp` (in 150 Läufen 0 Treffer auf > `e0+60`/`g1−26`). — Der oben beschriebene Fall ist also selten, aber möglich. > > ## Lösungsvorschlag > > Entscheidung vor das Anlegen ziehen und die Gruben-Metadaten konsistent halten: > > ```js > if (hookCount === 2 && firstFx * w + spacing > w - 40) hookCount = 1; // vor dem loop > const pit = { x0: e0, x1: g1, hooks: hookCount }; > for (let i = 0; i < hookCount; i++) { ... } > c.pits.push(pit); > ``` > > Zusätzlich absichern, dass ein Haken mindestens `GRAB_RADIUS·0.6` von der Landekante entfernt > bleibt, damit er nicht über dem Landepunkt hängt. > > --- > > *Dokumentiert in `issues/06-hook-chain-rueckfall-pruefung-wirkungslos.md` (Commit `c171694`).* 1 comments
Author
Owner

Behoben in 6179809 (main).

Was die Prüfung vorher tat: die Rückfall-Entscheidung stand hinter der Haken-Schleife
(Alt-Zeile if (hookCount === 2 && firstFx * w + spacing > w - 40) hookCount = 1;). Beide
Haken und c.pits[0].hooks = 2 waren da schon gesetzt – toter Code. Übrig blieb nur der
clamp(hx, e0 + 60, g1 - 26) der Schleife, der den zweiten Haken auf 26 px vor die Landekante
zerrte.

Geändert in CORE.buildPattern:

  • Entscheidung „zwei Haken oder einer?“ fällt vor c.pits.push() und vor der Haken-Schleife –
    Grubenmetadaten und verlegte Haken sind damit immer identisch.
  • Neue Konstante K.HOOK_EDGE_GUARD = 135 (0,6 × GRAB_RADIUS) als einzige Quelle für den
    Abstand eines Hakens zur Landekante; der clamp benutzt sie statt 26 px.
  • Passt der zweite Haken nicht, bleibt eine Ein-Haken-Grube – und die Grube schrumpft auf das
    vermessene Einzelhaken-Format (≤ 1,45 × jumpDistance), denn eine Kettengrube von
    1,6…1,85 × jumpDistance trägt ein einzelner Haken nach Messung nicht (~1,5 × jd). Ohne das
    hätte der Rückfall Muster erzeugt, die der Solver verwirft.

Auslieferung unverändert: im Gating-Band der Haken-Muster (ab Sektor 5 ⇒ v = 640) stehen in
der Kette weiterhin zwei Haken, Breite 1,6…1,85 × jumpDistance, Rand zur Landekante 179 px.
Generierungsstatistik über je 6 Läufe vor/nach dem Fix gleich: HOOK_CHAIN 10 → 9, PIT_HOOK
40 → 38, erzwungene SAFE 0 → 0, Fallbacks 4 → 4. Der gemeldete Fall war im aktuellen Gating
latent (natürlicher Abstand 146…205 px) – jede Umstellung von Gating/Balancing hätte ihn
geschaltet.

Tests: tests/issue6.mjs (neu, Node, ohne Browser) – vorher 7 von 14 Prüfungen rot, jetzt
grün. Abgedeckt: Konstante in CORE.K, pit.hooks == verlegte Haken, kein Haken näher als
K.HOOK_EDGE_GUARD an der Landekante, keine Klemmung auf g1 − 26 (Messung über 900 Muster:
256 unter der Schranke, 44 auf der Klemmstelle, Mindestabstand 26 px), Kettenabstand
≤ 1,3 × GRAB_RADIUS, Rückfall in beide Richtungen (360 px/s → genau ein Haken in schmaler
Grube; 640 px/s → zwei Haken in Kettenbreite), Solver löscht beide Ausprägungen,
Generierungsläufe ohne Verletzung und mit HOOK_CHAIN in der Artenliste.
tests/soak.mjs misst die Landekante jetzt mit derselben Schranke und prüft zusätzlich
pit.hooks == verlegte Haken (10.000 Muster grün). Gesamtsuite inkl. aller Browser-Suiten grün;
die bekannte Messschärfe von tests/browser_flow.py (Teleport-Schranke) trat wechselnd gegen
main vor dem Fix öfter auf als dagegen – kein Zusammenhang mit dieser Änderung.

Behoben in `6179809` (main). **Was die Prüfung vorher tat:** die Rückfall-Entscheidung stand hinter der Haken-Schleife (Alt-Zeile `if (hookCount === 2 && firstFx * w + spacing > w - 40) hookCount = 1;`). Beide Haken und `c.pits[0].hooks = 2` waren da schon gesetzt – toter Code. Übrig blieb nur der `clamp(hx, e0 + 60, g1 - 26)` der Schleife, der den zweiten Haken auf 26 px vor die Landekante zerrte. **Geändert in `CORE.buildPattern`:** * Entscheidung „zwei Haken oder einer?“ fällt vor `c.pits.push()` und vor der Haken-Schleife – Grubenmetadaten und verlegte Haken sind damit immer identisch. * Neue Konstante `K.HOOK_EDGE_GUARD` = 135 (0,6 × `GRAB_RADIUS`) als einzige Quelle für den Abstand eines Hakens zur Landekante; der `clamp` benutzt sie statt 26 px. * Passt der zweite Haken nicht, bleibt eine Ein-Haken-Grube – und die Grube schrumpft auf das vermessene Einzelhaken-Format (≤ 1,45 × `jumpDistance`), denn eine Kettengrube von 1,6…1,85 × `jumpDistance` trägt ein einzelner Haken nach Messung nicht (~1,5 × jd). Ohne das hätte der Rückfall Muster erzeugt, die der Solver verwirft. **Auslieferung unverändert:** im Gating-Band der Haken-Muster (ab Sektor 5 ⇒ v = 640) stehen in der Kette weiterhin zwei Haken, Breite 1,6…1,85 × `jumpDistance`, Rand zur Landekante 179 px. Generierungsstatistik über je 6 Läufe vor/nach dem Fix gleich: HOOK_CHAIN 10 → 9, PIT_HOOK 40 → 38, erzwungene SAFE 0 → 0, Fallbacks 4 → 4. Der gemeldete Fall war im aktuellen Gating latent (natürlicher Abstand 146…205 px) – jede Umstellung von Gating/Balancing hätte ihn geschaltet. **Tests:** `tests/issue6.mjs` (neu, Node, ohne Browser) – vorher 7 von 14 Prüfungen rot, jetzt grün. Abgedeckt: Konstante in `CORE.K`, `pit.hooks` == verlegte Haken, kein Haken näher als `K.HOOK_EDGE_GUARD` an der Landekante, keine Klemmung auf `g1 − 26` (Messung über 900 Muster: 256 unter der Schranke, 44 auf der Klemmstelle, Mindestabstand 26 px), Kettenabstand ≤ 1,3 × `GRAB_RADIUS`, Rückfall in beide Richtungen (360 px/s → genau ein Haken in schmaler Grube; 640 px/s → zwei Haken in Kettenbreite), Solver löscht beide Ausprägungen, Generierungsläufe ohne Verletzung und mit HOOK_CHAIN in der Artenliste. `tests/soak.mjs` misst die Landekante jetzt mit derselben Schranke und prüft zusätzlich `pit.hooks` == verlegte Haken (10.000 Muster grün). Gesamtsuite inkl. aller Browser-Suiten grün; die bekannte Messschärfe von `tests/browser_flow.py` (Teleport-Schranke) trat wechselnd gegen `main` vor dem Fix öfter auf als dagegen – kein Zusammenhang mit dieser Änderung.
gilli closed this issue 2026-09-01 09:38:57 +02:00
Sign in to join this conversation.
No description provided.