Folge aus #147. Umgeschrieben am 2026-09-18 nach der Diskussion zu #147: Es geht nicht mehr um Schrägen und Stufen, sondern darum, dass Move mit „Collide with solids" nicht mehr starr korrigiert, sondern über die Kontaktnormale reagiert, mit Friction.
Befund
moveAndSlide in src/runtime/systems/solids.ts bewegt erst X, dann Y, jede Achse für sich. Trifft eine Achse etwas, wird sie nur entlang derselben Achse zurückgesetzt (stepAxis). Eine Kontaktnormale gibt es nicht. Folgen:
Ein Circle, der gegen eine Kante fährt, bleibt an der Ecke hängen, statt abzurutschen.
Schräg gegen eine gerade Wand gleitet ein Objekt immer mit voller Geschwindigkeit weiter. Friction am Collider (seit #146) spielt für Move keine Rolle.
An einer schrägen Fläche (gedrehter Box-Collider) bleibt ein Objekt stehen, statt entlangzugleiten. Das ist aus dem Code abgeleitet, gemessen ist es noch nicht.
Kein Character-Controller von Rapier. Er kennt weder Friction noch Bounciness (in character_controller.d.ts kommt keins von beiden vor), hakt an Nahtstellen (Messung in #147) und sieht nur den Stand des letzten Physik-Steps. Sein größter Vorteil, die Stufen, wird nicht gebraucht.
Keine Stufen, keine Grenzwinkel. Wird bei Bedarf später selbst über Nodes gebaut.
Keine Bounciness bei Move.Move gibt nur eine Verschiebung pro Frame vor und hat kein Gedächtnis für eine Geschwindigkeit, ein Abprall wäre nicht sichtbar. Abprallen gibt es mit einem Dynamic Body (Set velocity, Apply force), dort rechnet Rapier Bounciness schon heute.
Zu bauen
Geometrie über Rapier:Shape.castShape (Rapier 0.20, geometry/shape.d.ts) liefert für zwei Formen Zeitpunkt des Treffers und Normale, ohne Welt und ohne Broadphase. Move castet gegen die blockierenden Collider, die solids.ts heute schon mit ihren aktuellen Positionen sammelt. Damit gibt es kein Problem mit einem veralteten Stand, und die Formpaare in collider.ts müssen nicht von Hand erweitert werden. Polygone aus Stufe 2 (#144) gehen später ohne weiteren Aufwand mit.
Reaktion, in bis zu 3–4 Durchgängen pro Frame:
Bis zur Kontaktfläche fahren (bündig, mit dem kleinstmöglichen Abstand).
Den Rest der Bewegung in einen Anteil in Richtung der Normale und einen entlang der Fläche zerlegen.
Der Anteil in Richtung der Normale fällt weg.
Der Anteil entlang der Fläche wird um Friction × Anteil in Richtung der Normale gekürzt (Coulomb), höchstens bis 0.
Neue Ausgänge am Move-Node:hit (bool), normal x, normal y. Damit lässt sich ein Abprall oder eine andere Reaktion im Graph selbst bauen.
Schieben von Dynamic Bodies (#147) bleibt, der Stoß geht entlang der Normale statt entlang der Achse.
Editor-Run und Play gleich: Beide gehen über denselben Weg. Rapier ist im Editor ohnehin geladen (#145). Heute läuft der Editor-Run über das alte CollisionSystem.
Messlatte / Tests
Circle fährt gegen eine Kante und rutscht ab.
Schräg gegen eine Wand: bei Friction 0 ungebremstes Gleiten, bei hoher Friction Stillstand, dazwischen gemessen gegen die Formel.
Gedrehte Box als Schräge: gleitet entlang, hängt nicht.
Nahtstellen: Boden aus 12 Kisten (100 Schritte mit „Schwerkraft" per Move) und Wand aus 3 gestapelten Rechtecken (60 Schritte), wie in #147. Mit Normalen kann an Nähten dasselbe Haken auftreten wie beim Controller. Vorgabe: 0× hängen, bündig.
Tilemap mit Zellen nebeneinander, gleiche Messung.
Die bestehenden Kollisionstests, die gerade gegen Wände fahren, sollen ihre exakten Werte behalten. Wo schräg getroffen wird, ändern sich Werte, jede Änderung wird begründet.
e2e in Modul 41: Circle an der Kante im echten Play, Ausgänge hit/normal im Graph.
Offen (mit Vorschlag)
Bestehende Spiele ändern sich: Die Vorgabe für Friction am Collider ist 0,5. Eine Figur, die per Move „Schwerkraft" nach unten drückt und dabei läuft, wird dadurch gebremst. Vorschlag: physikalisch richtig lassen und an den Smokes messen, wie stark es sich auswirkt. Wenn es stört, gilt Friction bei Move erst ab einem Schalter am Node.
Friction von zwei Collidern kombinieren: Vorschlag: so wie Rapier es im PhysicsSystem für diese Paarung tut, damit Move und Dynamic Bodies gleich rutschen.
Gegenmittel, falls Nähte haken: Vorschlag: Tilemap als zusammenhängender Umriss statt einer Box pro Zelle. Bei einzelnen Objekten nebeneinander bleibt ein Rest, der dokumentiert wird. Erst bauen, wenn die Messung es verlangt.
Move ohne eigenen Collider bewegt heute gar nichts (Nebenbefund aus #147). Vorschlag: bei der Gelegenheit bewegt es sich dann frei.
Folge aus #147. Umgeschrieben am 2026-09-18 nach der Diskussion zu #147: Es geht nicht mehr um Schrägen und Stufen, sondern darum, dass `Move` mit „Collide with solids" nicht mehr starr korrigiert, sondern über die Kontaktnormale reagiert, mit Friction.
## Befund
`moveAndSlide` in `src/runtime/systems/solids.ts` bewegt erst X, dann Y, jede Achse für sich. Trifft eine Achse etwas, wird sie nur entlang derselben Achse zurückgesetzt (`stepAxis`). Eine Kontaktnormale gibt es nicht. Folgen:
- Ein Circle, der gegen eine Kante fährt, bleibt an der Ecke hängen, statt abzurutschen.
- Schräg gegen eine gerade Wand gleitet ein Objekt immer mit voller Geschwindigkeit weiter. Friction am Collider (seit #146) spielt für `Move` keine Rolle.
- An einer schrägen Fläche (gedrehter Box-Collider) bleibt ein Objekt stehen, statt entlangzugleiten. Das ist aus dem Code abgeleitet, gemessen ist es noch nicht.
## Entscheidungen (aus der Diskussion zu #147)
- **Kein Character-Controller von Rapier.** Er kennt weder Friction noch Bounciness (in `character_controller.d.ts` kommt keins von beiden vor), hakt an Nahtstellen (Messung in #147) und sieht nur den Stand des letzten Physik-Steps. Sein größter Vorteil, die Stufen, wird nicht gebraucht.
- **Keine Stufen, keine Grenzwinkel.** Wird bei Bedarf später selbst über Nodes gebaut.
- **Keine Bounciness bei `Move`.** `Move` gibt nur eine Verschiebung pro Frame vor und hat kein Gedächtnis für eine Geschwindigkeit, ein Abprall wäre nicht sichtbar. Abprallen gibt es mit einem Dynamic Body (Set velocity, Apply force), dort rechnet Rapier Bounciness schon heute.
## Zu bauen
1. **Geometrie über Rapier:** `Shape.castShape` (Rapier 0.20, `geometry/shape.d.ts`) liefert für zwei Formen Zeitpunkt des Treffers und Normale, ohne Welt und ohne Broadphase. `Move` castet gegen die blockierenden Collider, die `solids.ts` heute schon mit ihren aktuellen Positionen sammelt. Damit gibt es kein Problem mit einem veralteten Stand, und die Formpaare in `collider.ts` müssen nicht von Hand erweitert werden. Polygone aus Stufe 2 (#144) gehen später ohne weiteren Aufwand mit.
2. **Reaktion, in bis zu 3–4 Durchgängen pro Frame:**
1. Bis zur Kontaktfläche fahren (bündig, mit dem kleinstmöglichen Abstand).
2. Den Rest der Bewegung in einen Anteil in Richtung der Normale und einen entlang der Fläche zerlegen.
3. Der Anteil in Richtung der Normale fällt weg.
4. Der Anteil entlang der Fläche wird um **Friction × Anteil in Richtung der Normale** gekürzt (Coulomb), höchstens bis 0.
3. **Neue Ausgänge am `Move`-Node:** `hit` (bool), `normal x`, `normal y`. Damit lässt sich ein Abprall oder eine andere Reaktion im Graph selbst bauen.
4. **Schieben von Dynamic Bodies (#147) bleibt**, der Stoß geht entlang der Normale statt entlang der Achse.
5. **Editor-Run und Play gleich:** Beide gehen über denselben Weg. Rapier ist im Editor ohnehin geladen (#145). Heute läuft der Editor-Run über das alte `CollisionSystem`.
## Messlatte / Tests
- Circle fährt gegen eine Kante und rutscht ab.
- Schräg gegen eine Wand: bei Friction 0 ungebremstes Gleiten, bei hoher Friction Stillstand, dazwischen gemessen gegen die Formel.
- Gedrehte Box als Schräge: gleitet entlang, hängt nicht.
- **Nahtstellen:** Boden aus 12 Kisten (100 Schritte mit „Schwerkraft" per `Move`) und Wand aus 3 gestapelten Rechtecken (60 Schritte), wie in #147. Mit Normalen kann an Nähten dasselbe Haken auftreten wie beim Controller. Vorgabe: 0× hängen, bündig.
- Tilemap mit Zellen nebeneinander, gleiche Messung.
- Die bestehenden Kollisionstests, die gerade gegen Wände fahren, sollen ihre exakten Werte behalten. Wo schräg getroffen wird, ändern sich Werte, jede Änderung wird begründet.
- e2e in Modul 41: Circle an der Kante im echten Play, Ausgänge `hit`/`normal` im Graph.
## Offen (mit Vorschlag)
1. **Bestehende Spiele ändern sich:** Die Vorgabe für Friction am Collider ist 0,5. Eine Figur, die per `Move` „Schwerkraft" nach unten drückt und dabei läuft, wird dadurch gebremst. Vorschlag: physikalisch richtig lassen und an den Smokes messen, wie stark es sich auswirkt. Wenn es stört, gilt Friction bei `Move` erst ab einem Schalter am Node.
2. **Friction von zwei Collidern kombinieren:** Vorschlag: so wie Rapier es im `PhysicsSystem` für diese Paarung tut, damit `Move` und Dynamic Bodies gleich rutschen.
3. **Gegenmittel, falls Nähte haken:** Vorschlag: Tilemap als zusammenhängender Umriss statt einer Box pro Zelle. Bei einzelnen Objekten nebeneinander bleibt ein Rest, der dokumentiert wird. Erst bauen, wenn die Messung es verlangt.
4. **`Move` ohne eigenen Collider** bewegt heute gar nichts (Nebenbefund aus #147). Vorschlag: bei der Gelegenheit bewegt es sich dann frei.
admin
added this to the Development project 2026-09-18 19:25:16 +02:00
admin
moved this to Next in Development on 2026-09-18 19:25:21 +02:00
meleki
changed title from Physik: Move mit Kollision über Schrägen und Stufen (eigenes Move-and-Slide statt Character-Controller) to Physik: Move-and-Slide entlang der Kontaktnormale mit Friction (Ausgänge hit/normal, ohne Stufen, ohne Character-Controller)2026-09-18 19:47:02 +02:00
Move mit „Collide with solids“ reagiert über die Kontaktnormale: Das Objekt fährt bis zur Fläche, bleibt bündig davor stehen, und der Rest der Bewegung geht entlang der Fläche weiter. Pro Move höchstens 4 Kontakte nacheinander (Ecke Boden/Wand = 2).
Friction nach Coulomb: Der Anteil entlang der Fläche wird um Friction × Anteil in die Fläche gekürzt. Friction eines Paares = Mittelwert beider Collider, wie Rapier bei Dynamic Bodies (Antwort 2). Ein Tile zählt als 0,5.
Neue Ausgänge am Move-Node:hit, normal x, normal y (erster Kontakt, Weltkoordinaten, weg von der Fläche, wie On collision enter). blocked x/y bleiben und melden, gegen welche eigene Achse etwas stand: bergab auf einer Schräge ist x frei, bergauf nicht.
Schieben von Dynamic Bodies geht entlang der Normale: eine an der Ecke getroffene Kiste weicht schräg aus.
Move ohne eigenen Collider bewegt sich frei (Antwort 4).
Editor-Run und Play laufen beide über Solids, alle Kollisionstests laufen gegen beide Systeme.
Bewusst nicht drin: Stufen, Grenzwinkel, Bounciness, Character-Controller.
Abweichung vom Issue: keine Geometrie über castShape
Gemessen: Rapier rechnet in f32 und landet bei 200 px Weg 3·10⁻⁶ px hinter der Kontaktfläche, also schon in der Wand. Unsere Überlappungsprüfung in f64 würde das als Eindringen werten, bündige Werte gäbe es nicht mehr. Treffpunkt und Normale kommen deshalb aus der eigenen f64-Geometrie (contactBetween in collider.ts). Preis: Polygone aus Stufe 2 brauchen dort ein eigenes SAT.
Nahtstellen (Antwort 3)
Kein Haken gemessen: Boden aus 12 Blöcken (Box und Kreis, 100 Frames, jeden Frame exakt 2,25 weiter, bündig, nie blocked x), Wand aus 3 gestapelten Blöcken, Tilemap-Wand. Ein Umriss für die Tilemap war deshalb nicht nötig.
Tests
npm run check: 104 Files, 1469 Tests (vorher 1454).
Neue Unit-Tests gegen beide Systeme: Kreis rollt über die Kante; 30°-Schräge rutscht pro Frame genau g·sin − μ·g·cos (μ = 0 / 0,5 / 0,6, bei 0,6 bleibt er liegen); schräg gegen die Wand gleitet mit Friction 0 voll, mit 0,5 gar nicht; Nähte; Kiste an der Ecke geschoben (vy/vx = Normale); contactBetween; Node-Katalog; Objekt ohne Collider.
Geänderte alte Werte: Diagonale gegen die Wand endet bei y = 32 statt 40 (Friction), zwei Tilemap-Gleittests landen genau auf −200 + 60·(6,7 − 0,5·3,1). Gerade gegen eine Wand bleibt alles exakt.
Gegenprobe mit 8 eingebauten Fehlern, alle gefangen.
e2e 41d (2 neue Checks, echtes Play): der Ball rollt über die Kante auf die Oberseite, der Graph loggt normal = −0,8/−0,6.
Erledigt in `1b5e386` (Wiki `e1e8bbd`).
## Was sich ändert
- **`Move` mit „Collide with solids“ reagiert über die Kontaktnormale:** Das Objekt fährt bis zur Fläche, bleibt bündig davor stehen, und der Rest der Bewegung geht entlang der Fläche weiter. Pro Move höchstens 4 Kontakte nacheinander (Ecke Boden/Wand = 2).
- **Friction nach Coulomb:** Der Anteil entlang der Fläche wird um Friction × Anteil in die Fläche gekürzt. Friction eines Paares = Mittelwert beider Collider, wie Rapier bei Dynamic Bodies (Antwort 2). Ein Tile zählt als 0,5.
- **Neue Ausgänge am `Move`-Node:** `hit`, `normal x`, `normal y` (erster Kontakt, Weltkoordinaten, weg von der Fläche, wie `On collision enter`). `blocked x/y` bleiben und melden, gegen welche eigene Achse etwas stand: bergab auf einer Schräge ist x frei, bergauf nicht.
- **Schieben von Dynamic Bodies** geht entlang der Normale: eine an der Ecke getroffene Kiste weicht schräg aus.
- **`Move` ohne eigenen Collider** bewegt sich frei (Antwort 4).
- **Editor-Run und Play** laufen beide über `Solids`, alle Kollisionstests laufen gegen beide Systeme.
- Bewusst nicht drin: Stufen, Grenzwinkel, Bounciness, Character-Controller.
## Abweichung vom Issue: keine Geometrie über `castShape`
Gemessen: Rapier rechnet in f32 und landet bei 200 px Weg **3·10⁻⁶ px hinter** der Kontaktfläche, also schon in der Wand. Unsere Überlappungsprüfung in f64 würde das als Eindringen werten, bündige Werte gäbe es nicht mehr. Treffpunkt und Normale kommen deshalb aus der eigenen f64-Geometrie (`contactBetween` in `collider.ts`). Preis: Polygone aus Stufe 2 brauchen dort ein eigenes SAT.
## Nahtstellen (Antwort 3)
Kein Haken gemessen: Boden aus 12 Blöcken (Box und Kreis, 100 Frames, jeden Frame exakt 2,25 weiter, bündig, nie `blocked x`), Wand aus 3 gestapelten Blöcken, Tilemap-Wand. Ein Umriss für die Tilemap war deshalb nicht nötig.
## Tests
- `npm run check`: 104 Files, 1469 Tests (vorher 1454).
- Neue Unit-Tests gegen beide Systeme: Kreis rollt über die Kante; 30°-Schräge rutscht pro Frame genau g·sin − μ·g·cos (μ = 0 / 0,5 / 0,6, bei 0,6 bleibt er liegen); schräg gegen die Wand gleitet mit Friction 0 voll, mit 0,5 gar nicht; Nähte; Kiste an der Ecke geschoben (vy/vx = Normale); `contactBetween`; Node-Katalog; Objekt ohne Collider.
- Geänderte alte Werte: Diagonale gegen die Wand endet bei y = 32 statt 40 (Friction), zwei Tilemap-Gleittests landen genau auf −200 + 60·(6,7 − 0,5·3,1). Gerade gegen eine Wand bleibt alles exakt.
- Gegenprobe mit 8 eingebauten Fehlern, alle gefangen.
- e2e 41d (2 neue Checks, echtes Play): der Ball rollt über die Kante auf die Oberseite, der Graph loggt `normal` = −0,8/−0,6.
## CI zu `1b5e386`
- Gate (Task 138): 104 Testdateien grün, `CHECK EXIT 0`.
- Suite (Task 139): `ALL PASSED – 1057 checks`, die Summe ist damit gemessen.
- Smokes 24 / 19 / 21: Die drei Beispielspiele laufen auch mit Friction am Boden und an Wänden grün.
#144 bleibt offen bis Stufe 2.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Folge aus #147. Umgeschrieben am 2026-09-18 nach der Diskussion zu #147: Es geht nicht mehr um Schrägen und Stufen, sondern darum, dass
Movemit „Collide with solids" nicht mehr starr korrigiert, sondern über die Kontaktnormale reagiert, mit Friction.Befund
moveAndSlideinsrc/runtime/systems/solids.tsbewegt erst X, dann Y, jede Achse für sich. Trifft eine Achse etwas, wird sie nur entlang derselben Achse zurückgesetzt (stepAxis). Eine Kontaktnormale gibt es nicht. Folgen:Movekeine Rolle.Entscheidungen (aus der Diskussion zu #147)
character_controller.d.tskommt keins von beiden vor), hakt an Nahtstellen (Messung in #147) und sieht nur den Stand des letzten Physik-Steps. Sein größter Vorteil, die Stufen, wird nicht gebraucht.Move.Movegibt nur eine Verschiebung pro Frame vor und hat kein Gedächtnis für eine Geschwindigkeit, ein Abprall wäre nicht sichtbar. Abprallen gibt es mit einem Dynamic Body (Set velocity, Apply force), dort rechnet Rapier Bounciness schon heute.Zu bauen
Shape.castShape(Rapier 0.20,geometry/shape.d.ts) liefert für zwei Formen Zeitpunkt des Treffers und Normale, ohne Welt und ohne Broadphase.Movecastet gegen die blockierenden Collider, diesolids.tsheute schon mit ihren aktuellen Positionen sammelt. Damit gibt es kein Problem mit einem veralteten Stand, und die Formpaare incollider.tsmüssen nicht von Hand erweitert werden. Polygone aus Stufe 2 (#144) gehen später ohne weiteren Aufwand mit.Move-Node:hit(bool),normal x,normal y. Damit lässt sich ein Abprall oder eine andere Reaktion im Graph selbst bauen.CollisionSystem.Messlatte / Tests
Move) und Wand aus 3 gestapelten Rechtecken (60 Schritte), wie in #147. Mit Normalen kann an Nähten dasselbe Haken auftreten wie beim Controller. Vorgabe: 0× hängen, bündig.hit/normalim Graph.Offen (mit Vorschlag)
Move„Schwerkraft" nach unten drückt und dabei läuft, wird dadurch gebremst. Vorschlag: physikalisch richtig lassen und an den Smokes messen, wie stark es sich auswirkt. Wenn es stört, gilt Friction beiMoveerst ab einem Schalter am Node.PhysicsSystemfür diese Paarung tut, damitMoveund Dynamic Bodies gleich rutschen.Moveohne eigenen Collider bewegt heute gar nichts (Nebenbefund aus #147). Vorschlag: bei der Gelegenheit bewegt es sich dann frei.Physik: Move mit Kollision über Schrägen und Stufen (eigenes Move-and-Slide statt Character-Controller)to Physik: Move-and-Slide entlang der Kontaktnormale mit Friction (Ausgänge hit/normal, ohne Stufen, ohne Character-Controller)Erledigt in
1b5e386(Wikie1e8bbd).Was sich ändert
Movemit „Collide with solids“ reagiert über die Kontaktnormale: Das Objekt fährt bis zur Fläche, bleibt bündig davor stehen, und der Rest der Bewegung geht entlang der Fläche weiter. Pro Move höchstens 4 Kontakte nacheinander (Ecke Boden/Wand = 2).Move-Node:hit,normal x,normal y(erster Kontakt, Weltkoordinaten, weg von der Fläche, wieOn collision enter).blocked x/ybleiben und melden, gegen welche eigene Achse etwas stand: bergab auf einer Schräge ist x frei, bergauf nicht.Moveohne eigenen Collider bewegt sich frei (Antwort 4).Solids, alle Kollisionstests laufen gegen beide Systeme.Abweichung vom Issue: keine Geometrie über
castShapeGemessen: Rapier rechnet in f32 und landet bei 200 px Weg 3·10⁻⁶ px hinter der Kontaktfläche, also schon in der Wand. Unsere Überlappungsprüfung in f64 würde das als Eindringen werten, bündige Werte gäbe es nicht mehr. Treffpunkt und Normale kommen deshalb aus der eigenen f64-Geometrie (
contactBetweenincollider.ts). Preis: Polygone aus Stufe 2 brauchen dort ein eigenes SAT.Nahtstellen (Antwort 3)
Kein Haken gemessen: Boden aus 12 Blöcken (Box und Kreis, 100 Frames, jeden Frame exakt 2,25 weiter, bündig, nie
blocked x), Wand aus 3 gestapelten Blöcken, Tilemap-Wand. Ein Umriss für die Tilemap war deshalb nicht nötig.Tests
npm run check: 104 Files, 1469 Tests (vorher 1454).contactBetween; Node-Katalog; Objekt ohne Collider.normal= −0,8/−0,6.CI zu
1b5e386CHECK EXIT 0.ALL PASSED – 1057 checks, die Summe ist damit gemessen.#144 bleibt offen bis Stufe 2.