Der Reichweiten-Halo am Haken ignoriert die Seilkühlzeit: 25–42 ms lang zeichnet das Spiel „greifbar“ an einem Seil, das grabbable() gerade verweigert #26
Labels
No labels
accessibility
bug
cleanup
consistency
crash
critical
game-feel
gameplay
high
input
level-generation
low
maintainability
medium
pause
physics
responsive
security
spec-gap
ui
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gilli/pulse-sprint#26
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Komponente: GAME (
render, Zeilen 3459–3463), CORE (grabbable701–705,findGrab706–717,releaseHook749–763,integrateHang806–812 / 826–833),RENDER.drawHook2088–2107Betroffene Version:
main@86a481d,index.html(3.684 Zeilen, MD5777d18c3edbb834aa41fccc7d10a7d18)Beschreibung
Der Renderer zeichnet um jeden Haken einen pulsierenden Kreis (in
reducedMotionzusätzlich einHäkchen,
drawHookZeilen 2096–2107) als Signal „von hier aus greifbar“. Die Bedingung dafürsteht in
render:Die Engine entscheidet an anderer Stelle —
grabbable()(Zeilen 701–705) verlangt zusätzlich,dass die Seilkühlzeit abgelaufen ist:
coolTwird gesetzt vonreleaseHook(0,3 s), vom schlaffen Seil (0,3 s) und vomAuspendel-Rückfall (0,5 s). Direkt nach dem Loslassen hängt der Körper aber noch fast am Ende
des Radius — die Render-Bedingung ist also für mehrere Bildraten wahr, während jeder
Greifversuch (Tastatur
E/K, Autogreif, Klick auf genau diesen Haken übergrabTarget)abgewiesen wird.
Zwei Signale, zwei Antworten: Das HUD (
el.grabHint, gespeist ausS.hookHint = CORE.findGrab(...),Zeilen 3315–3318) meldet korrekt „kein Ziel“, der Kreis am Haken meldet weiter „greifbar“.
Messprotokoll
Node gegen das extrahierte
CORE, reale Kette (Seed 4242, Grube 171 084 … 171 758,Tempo 640 px/s), Schrittweite
K.FIXED_TIMESTEP. Pro Schritt wird für jeden Haken dieRender-Bedingung (
h !== b.hook && d ≤ K.GRAB_RADIUS) gegenCORE.grabbable(b, h)geprüft —exakt die beiden Formeln aus Zeile 3462 und Zeile 701, ohne den Renderer zu rufen.
coolT > 0)coolT0,30coolT0,30Der Widerspruch sitzt also bei 223 px — 2 px innerhalb der Reichweite, unmittelbar am
Loslassmoment, den der Spieler gerade gesetzt hat. Bei einer Kette passiert das zweimal
(nach jedem der beiden Seile).
Der Auspendel-Rückfall (
hangT > 6bzw. Stall > 0,5 s,coolT = 0.5) ist derselbe Fall, nurlänger: Dort hängt der Körper zur Ruhe kommend direkt unter dem Haken (d ≈ Seillänge ≤ 225) und
der Kreis bleibt 0,5 s lang stehen.
Ursache im Code
renderprüft nur die Geometrie (Abstand, „nicht der eigene Haken“), nicht den Zustandderselben Anfrage.
findGrab(706–717) filtert zusätzlich übergrabbableund verwirft Hakenhinter dem Körper (
h.x < b.x - 70),grabTarget(723–726) verlangt für das Klick-Zielebenfalls
grabbable. Zwei Stellen prüfen die Distanz gegenK.GRAB_RADIUS, ohnegrabbablezu rufen:hookAtPoint(Zeile 3213, Klick-Treffer-test) undrender(Zeile 3462). Beim Klick läuft die Entscheidung danach noch durch
grabTarget→grabbable— ein falscher Klick kann dort nichts erzwingen. Nur am gezeichneten Halobleibt die reine Geometrie die letzte Auskunft, und damit die einzige sichtbare
Zusage, die widersprüchlich werden kann.
Folgeverhalten
Kreis), bekommt nichts — ohne Ton, ohne Meldung, ohne Unterschied zum „daneben gegriffen“.
reducedMotionzeichnet dort zusätzlich ein Häkchen: Das Barrierefreiheits-Signal ist dannexplizit „hier entlang“, während die Simulation ablehnt.
h.x < b.x - 70(Haken hinter demKörper) steht nur in
findGrab(Zeile 709).grabTarget(723–726) prüft für dasKlick-Ziel allein
grabbable— ein Klick auf einen Haken hinter dem Körper greift also,E/Kund Autogreif greifen nicht. Der Halo gilt für beide Lesarten gleichzeitig.Nicht verletzt (bewusst abgegrenzt)
grabbable/grabTarget/findGrabselbst sind korrekt: Kein Greifen findet statt, das dieKühlzeit missachtet; das Klick-Ziel fällt wie vorgesehen auf
findGrabzurück (#9 hält).S.hookHintund damitel.grabHintsind unverändert richtig — der Widerspruch ist rein derHalo am Haken.
Lösungsvorschlag
Zeile 3462 auf dieselbe Quelle wie die Eingabe stellen:
Wer den „Halo = freie Auswahl“-Charakter erhalten will, ergänze denselben
h.x >= body.x - 70-Filter ausfindGrab— oder, sauberer:COREexportiert einereachability(body, hook)-Funktion, und sowohlfindGrabals auchrenderrufen sie.Eine dritte Entscheidung (nur Distanz) sollte es nicht mehr geben.
Erwarteter Test
tests/issue26.mjs(Node, extrahiertesCORE+ die Render-Bedingung als extrahiertereinzeliger Ausdruck oder als Bridge-Aufruf): nach
releaseHookan einer real generiertenKette darf für keinen Schritt gelten
render-reach && !CORE.grabbable(...); Vorher-Zahl:5 bzw. 3 Schritte (42 ms / 25 ms), Nachher: 0.
tests/browser_issue9.pymitlaufen): echtes Dokument, Hakengefasst, Leertaste zum Loslassen, danach sofort
E— Erwartung: kein zweiter Einhang undkein Halo auf dem gekühlten Haken (Screenshots des Haken-Bildbereichs vor/nach dem Loslassen
vergleichen).
Dokumentiert in
issues/26-reichweiten-halo-ignoriert-seilkuehlzeit.md(Commit86a481d).Behoben in
171d332(Doku63dd7d4).Geändert:
CORE.reachability(body, hook)=grabbable(Seilkühlzeit,GRAB_RADIUS) + der Hinters-Filterh.x < b.x - 70ausfindGrab.findGrabruft sie,renderruft sie — eine Entscheidung statt drei; die reine Geometrie ist keine Auskunft mehr.grabTargetbleibt die bewusst abgegrenzte Ausnahme: das angeklickte Ziel gewinnt auch hinter dem Körper (#9 hält), erzwungen wird nichts,grabbablebleibt vorgeschaltet. Der Auspendel-Rückfall (coolT = 0,5bei Distanz = Seillänge im Radius) ist derselbe Fall und stillt mit; dasreducedMotion-Häkchen hängt an demselben Signal.Test:
tests/issue26.mjs(14 Prüfungen, vorher 5 rot) — die Render-Bedingung wird per Ankerif (body && !S.dying) reach = …als einzelner Ausdruck aus dem Dokument extrahiert und gegenCORE.grabbablegehalten, Kühlung pro Frame abgebaut wie imupdate():coolT = 0,29)CORE.reachabilityrufen — jede dritte Entscheidung (nur Distanz) macht die Suite rotgrabTargethinter dem Körper gewinnt weiter)Die im Befund vorgeschlagene Browser-Variante (Halo-Pixel vor/nach dem Loslassen) ist nicht zusätzlich gebaut; die extrahierte Zeile ist genau die Bedingung, die
drawHooksteuert.Alle Node-Suiten + Soak grün;
browser_smoke,issue9,issue22,issue23grün.