Aus #144 („Später“). Zwei Körper verbinden: Scharnier (Tür, Pendel, Rad), Feder, Seil, feste Verbindung, Schiene.
Befund
Rapier hat Impulse-Joints: revolute, spring, rope, fixed, prismatic (createImpulseJoint(data, body1, body2)), mit Grenzen und Motoren bei revolute/prismatic.
Joints wirken nur zwischen Rapier-Bodies, also nur im Play (Editor-Run simuliert nicht, #148). Ein Joint braucht zwei Objekte; heute verweist kein Kind-Objekt auf ein anderes Objekt der Scene. Einen Property-Kind object gibt es für Graph-Ports (kinds.ts), aber nicht als gespeicherte Referenz im Inspector.
Ein Objekt ohne Rigidbody ist intern ein Kinematic Body (#146) und taugt damit als fester Ankerpunkt.
Zu bauen
Neues Kind-Objekt Joint am ersten Körper, Felder: Typ (Hinge / Spring / Rope / Fixed / Slider), Connected object (Referenz auf ein Objekt der Scene, leer = fester Punkt in der Welt), Anker am eigenen und am anderen Objekt, typabhängig Länge, Steifigkeit, Dämpfung, Winkel-/Weggrenzen, Motor.
Anker im Scene-View als Punkte zum Ziehen, Verbindungslinie gezeichnet.
Referenz überlebt Kopieren, Prefab-Instanzen und Löschen des Ziels (dann: Joint inaktiv + Hinweis).
Tests: Pendel mit bekannter Periode, Feder schwingt um Ruhelänge, Seil begrenzt, Hinge-Grenze hält, Motor dreht.
Offen (mit Vorschlag)
Welche Typen zuerst? Vorschlag: Hinge, Spring, Rope, Fixed. Slider und Motoren in einem zweiten Schritt.
Objekt-Referenz als neuer Property-Kind — Vorschlag: allgemein bauen (objectRef per Id), weil ihn später auch andere brauchen (Follow, Spawner-Ziel).
Joints per Node anlegen/lösen? Vorschlag: nur „Break joint“ als Node, Anlegen bleibt im Editor.
Editor-Run: Joints erst mit #148 simulieren; bis dahin im Editor-Run ohne Wirkung und im Inspector so vermerkt.
Aus #144 („Später“). Zwei Körper verbinden: Scharnier (Tür, Pendel, Rad), Feder, Seil, feste Verbindung, Schiene.
## Befund
- Rapier hat Impulse-Joints: `revolute`, `spring`, `rope`, `fixed`, `prismatic` (`createImpulseJoint(data, body1, body2)`), mit Grenzen und Motoren bei revolute/prismatic.
- Joints wirken nur zwischen Rapier-Bodies, also nur im Play (Editor-Run simuliert nicht, #148). Ein Joint braucht **zwei** Objekte; heute verweist kein Kind-Objekt auf ein anderes Objekt der Scene. Einen Property-Kind `object` gibt es für Graph-Ports (`kinds.ts`), aber nicht als gespeicherte Referenz im Inspector.
- Ein Objekt ohne Rigidbody ist intern ein Kinematic Body (#146) und taugt damit als fester Ankerpunkt.
## Zu bauen
- Neues Kind-Objekt **Joint** am ersten Körper, Felder: Typ (Hinge / Spring / Rope / Fixed / Slider), **Connected object** (Referenz auf ein Objekt der Scene, leer = fester Punkt in der Welt), Anker am eigenen und am anderen Objekt, typabhängig Länge, Steifigkeit, Dämpfung, Winkel-/Weggrenzen, Motor.
- Anker im Scene-View als Punkte zum Ziehen, Verbindungslinie gezeichnet.
- Referenz überlebt Kopieren, Prefab-Instanzen und Löschen des Ziels (dann: Joint inaktiv + Hinweis).
- Tests: Pendel mit bekannter Periode, Feder schwingt um Ruhelänge, Seil begrenzt, Hinge-Grenze hält, Motor dreht.
## Offen (mit Vorschlag)
1. **Welche Typen zuerst?** Vorschlag: Hinge, Spring, Rope, Fixed. Slider und Motoren in einem zweiten Schritt.
2. **Objekt-Referenz als neuer Property-Kind** — Vorschlag: allgemein bauen (`objectRef` per Id), weil ihn später auch andere brauchen (Follow, Spawner-Ziel).
3. **Joints per Node anlegen/lösen?** Vorschlag: nur „Break joint“ als Node, Anlegen bleibt im Editor.
4. **Editor-Run:** Joints erst mit #148 simulieren; bis dahin im Editor-Run ohne Wirkung und im Inspector so vermerkt.
admin
added this to the Development project 2026-09-18 23:03:37 +02:00
passt - aber kein folge-issue, sondern hier in diesem issue dann weitermachen im zweiten step
weiß zwar nicht was du meinst, aber dein vorschlag wird schon passen
je nachdem wie "anlegen" funktioniert, sollte zur laufzeit eigentlich auch möglich sein
passt, kein editor-run, ist gekoppelt mit #148
1. passt - aber kein folge-issue, sondern hier in diesem issue dann weitermachen im zweiten step
2. weiß zwar nicht was du meinst, aber dein vorschlag wird schon passen
3. je nachdem wie "anlegen" funktioniert, sollte zur laufzeit eigentlich auch möglich sein
4. passt, kein editor-run, ist gekoppelt mit `#148`
admin
moved this to Done in Development on 2026-10-02 21:01:49 +02:00
Schritt 1 ist gebaut: Hinge (mit Winkelgrenze), Spring, Rope und Fixed. Das Issue bleibt offen für Schritt 2 (Slider und Motoren), wie in Antwort 1 gewünscht.
Entschieden (Rückfragen vom 2026-10-02)
Umfang: Schritt 1 mit Winkelgrenze am Hinge. Slider und Motoren kommen in Schritt 2, hier im Issue.
Anker: Hinge und Fixed haben nur einen Punkt, die Position des Joints. Spring und Rope haben zusätzlich Connected anchor X/Y am anderen Objekt (Standard 0,0 = dessen Ursprung, ohne Objekt ein Punkt der Szene).
Zur Laufzeit: ein Node Add joint. Lösen läuft über das vorhandene Destroy am Joint, einen eigenen Break joint gibt es nicht.
Connected object ohne Rigidbody: ein beweglicher Ankerpunkt. Es wird selbst nicht gezogen, der Joint folgt ihm aber.
Scene View: zweiter Griff zum Ziehen am anderen Ende von Spring und Rope.
Was neu ist
Asset-Typ Joint, als Kind-Objekt des gehaltenen Objekts. Rechtsklick auf ein Objekt → Add joint… → Create new joint oder ein vorhandenes. Ein Objekt kann mehrere tragen.
Wo der Joint sitzt, ist der Punkt, an dem er sein Objekt hält. Den Joint verschieben heißt diesen Punkt verschieben.
Zeilen im Inspector, je nach Type nur die passenden:
Type: Hinge, Spring, Rope, Fixed.
Connected object: Auswahl aus den Objekten der Szene oder per Pipette. Leer = fester Punkt der Szene.
Connected anchor X/Y und Length (0 = as placed): nur Spring und Rope.
Stiffness und Damping: nur Spring.
Limit angle, dann Min angle und Max angle: nur Hinge. Die Grad zählen ab der Lage beim Start.
Collide with connected: aus = die zwei Objekte gehen durcheinander durch.
Scene View: eigenes Icon, eine Linie zum anderen Ende, ein Ring am gehaltenen Punkt. Bei gewähltem Spring oder Rope sitzt am anderen Ende ein runder Griff. Ziehen verschiebt den Connected anchor und ist ein Undo-Schritt.
Node Add joint: Eingänge joint asset (Vorlage für Typ und Werte), object, connected, x, y. Ausgang ist der neue Joint. Properties mit Type Joint ändert die Werte eines platzierten Joints.
Der Verweis folgt seinem Objekt wie jeder Objekt-Verweis seit #258: Beim gemeinsamen Einfügen zeigt die Kopie auf die Kopie, und in einem Container hält jede Platzierung ihre eigene Kopie.
Kein Editor-Run (Antwort 4 vom Issue, hängt an #148). Der Inspector eines platzierten Joints sagt das.
Was ich selbst festgelegt habe
Der Halter braucht einen Rigidbody, den eigenen oder den eines Objekts darüber. Ohne hält der Joint nichts, und die Konsole sagt es einmal.
Ziel im Editor gelöscht: Der Verweis wird leer (wie seit #269 jeder Verweis), der Joint hält dann an der Szene. Das weicht vom Issue-Text ab („Joint inaktiv + Hinweis“). Inaktiv mit Hinweis ist er nur, wenn das Ziel im laufenden Spiel zerstört wird.
Joint am eigenen Objekt verbunden: hält nichts, mit Hinweis.
Ausgeschalteter Joint lässt los und hält nach dem Einschalten wieder.
Einheiten: Längen in Welteinheiten. Stiffness ist Kraft je 100 Einheiten (N/m), Standard 50. Damping Standard 1. Winkel in Grad, Standard -45 bis 45.
Length 0 nimmt den Abstand, in dem die zwei Enden platziert sind.
Destroy mit keep children nimmt die Joints mit dem Objekt weg, wie Behaviors und den Rigidbody.
Add joint speichert das Ziel als Verweis per Id, nicht als Laufzeit-Objekt. So zeichnet die Scene View die Linie auch im Play.
Under the hood
src/core/model/joint.ts (Schlüssel) und src/core/properties/joint.ts (was die Engine liest, ohne Engine prüfbar).
src/runtime/physics/joints.ts: ein Engine-Joint je Joint-Objekt. Ein Ende ohne Rigidbody bekommt einen eigenen kinematischen Körper ohne Collider, der dem Objekt folgt.
Neue Schema-Option shownFor: eine Zeile nur bei bestimmten Werten eines Enums.
src/viewport/jointLinks.ts (Linie, ohne Pixi) und src/editor/viewport/jointAnchor.ts (Griff).
Gemessen an Rapier 0.20: Die Winkelgrenze eines Revolute-Joints zählt absolut, nicht ab Start. Deshalb wird der Startwinkel aufgeschlagen.
54 neue Unit-Tests: 40 in tests/core/joints.test.ts (Seil, Feder, Pendel, Winkelgrenze, feste Verbindung, das andere Ende, der Node, Container, Einfügen, Löschen), 6 für die Linie der Scene View, 4 Katalog-Fälle des Nodes. Die übrigen 4 entstehen in Tests, die je Asset-Typ einen Fall erzeugen.
Gemessen in den Unit-Tests:
Pendel mit 100 Einheiten: Nulldurchgang nach 0,50 s (Periode gerechnet 2,007 s).
Feder mit 40 N/m an 1 kg: Umkehr nach 0,50 s (Periode gerechnet 0,993 s).
Seil mit Länge 150: Der Körper fällt auf 150 und nicht weiter.
Hinge mit Grenze 30 Grad: bleibt zwischen 27 und 33 stehen, auch bei gedreht platziertem Objekt.
Vier neue e2e-Checks in Modul 41 mit echter Maus: Add joint… im Menü, Rope zum Haken mit Linie und Griff, Griff ziehen samt einem Undo, im Play hängt die Kiste, während die ohne Joint fällt.
e2e 04, 40 und 46 zählen den neuen Typ in ihren Listen mit.
21 von 22 Unit-Gegenproben greifen. Die eine übrige ist gleichwertig: Rapier ignoriert das Entfernen eines Joints, der mit seinem Körper schon weg ist. Die Schutzabfrage bleibt.
Alle drei Browser-Gegenproben greifen (2, 1 und 2 Checks fallen): keine Linie, keine Joints in der Engine, Griff nicht greifbar.
Einen Screenshot angesehen: Icon, Linie, Griff und die Rope-Zeilen im Inspector.
Die e2e-Summe 1691 ist gerechnet (1687 + 4). Die 1687 hat CI an e066613 gemessen. Volle Suite und Smokes liefen nicht.
Nicht im Browser geprüft (nur Unit-Tests): Spring, Hinge mit Grenze, Fixed, der Node Add joint, ein bewegtes Connected object, Joints in Containern.
Gar nicht geprüft: der veröffentlichte Spiel-Link, und alles nach dem Build.
Nicht behandelt: Winkelgrenzen unter einem gespiegelten Parent zählen im Drehsinn der Engine.
Nebenwirkung: das +-Menü scrollt bei 800 px Höhe
Mit Joint hat das +-Menü 25 Einträge und passt bei einer Fensterhöhe von 800 px nicht mehr ganz unter seinen Knopf. Der letzte Eintrag (Vector art) ist dort nur mit Scrollen zu erreichen. e2e 04 prüfte bisher, dass er ohne Scrollen sichtbar ist, und scrollt jetzt hin.
Schritt 1 ist gebaut: Hinge (mit Winkelgrenze), Spring, Rope und Fixed. Das Issue bleibt offen für Schritt 2 (Slider und Motoren), wie in Antwort 1 gewünscht.
## Entschieden (Rückfragen vom 2026-10-02)
1. **Umfang:** Schritt 1 mit Winkelgrenze am Hinge. Slider und Motoren kommen in Schritt 2, hier im Issue.
2. **Anker:** Hinge und Fixed haben nur einen Punkt, die Position des Joints. Spring und Rope haben zusätzlich `Connected anchor X/Y` am anderen Objekt (Standard 0,0 = dessen Ursprung, ohne Objekt ein Punkt der Szene).
3. **Zur Laufzeit:** ein Node `Add joint`. Lösen läuft über das vorhandene `Destroy` am Joint, einen eigenen `Break joint` gibt es nicht.
4. **Connected object ohne Rigidbody:** ein beweglicher Ankerpunkt. Es wird selbst nicht gezogen, der Joint folgt ihm aber.
5. **Scene View:** zweiter Griff zum Ziehen am anderen Ende von Spring und Rope.
## Was neu ist
- **Asset-Typ *Joint***, als Kind-Objekt des gehaltenen Objekts. Rechtsklick auf ein Objekt → *Add joint…* → *Create new joint* oder ein vorhandenes. Ein Objekt kann mehrere tragen.
- **Wo der Joint sitzt, ist der Punkt, an dem er sein Objekt hält.** Den Joint verschieben heißt diesen Punkt verschieben.
- **Zeilen im Inspector**, je nach *Type* nur die passenden:
- *Type*: Hinge, Spring, Rope, Fixed.
- *Connected object*: Auswahl aus den Objekten der Szene oder per Pipette. Leer = fester Punkt der Szene.
- *Connected anchor X/Y* und *Length (0 = as placed)*: nur Spring und Rope.
- *Stiffness* und *Damping*: nur Spring.
- *Limit angle*, dann *Min angle* und *Max angle*: nur Hinge. Die Grad zählen ab der Lage beim Start.
- *Collide with connected*: aus = die zwei Objekte gehen durcheinander durch.
- **Scene View:** eigenes Icon, eine Linie zum anderen Ende, ein Ring am gehaltenen Punkt. Bei gewähltem Spring oder Rope sitzt am anderen Ende ein runder Griff. Ziehen verschiebt den *Connected anchor* und ist ein Undo-Schritt.
- **Node `Add joint`:** Eingänge *joint asset* (Vorlage für Typ und Werte), *object*, *connected*, *x*, *y*. Ausgang ist der neue Joint. `Properties` mit Type *Joint* ändert die Werte eines platzierten Joints.
- **Der Verweis folgt seinem Objekt** wie jeder Objekt-Verweis seit #258: Beim gemeinsamen Einfügen zeigt die Kopie auf die Kopie, und in einem Container hält jede Platzierung ihre eigene Kopie.
- **Kein Editor-Run** (Antwort 4 vom Issue, hängt an #148). Der Inspector eines platzierten Joints sagt das.
## Was ich selbst festgelegt habe
- **Der Halter braucht einen Rigidbody**, den eigenen oder den eines Objekts darüber. Ohne hält der Joint nichts, und die Konsole sagt es einmal.
- **Ziel im Editor gelöscht:** Der Verweis wird leer (wie seit #269 jeder Verweis), der Joint hält dann an der Szene. Das weicht vom Issue-Text ab („Joint inaktiv + Hinweis“). Inaktiv mit Hinweis ist er nur, wenn das Ziel im laufenden Spiel zerstört wird.
- **Joint am eigenen Objekt verbunden:** hält nichts, mit Hinweis.
- **Ausgeschalteter Joint** lässt los und hält nach dem Einschalten wieder.
- **Einheiten:** Längen in Welteinheiten. *Stiffness* ist Kraft je 100 Einheiten (N/m), Standard 50. *Damping* Standard 1. Winkel in Grad, Standard -45 bis 45.
- **`Length` 0** nimmt den Abstand, in dem die zwei Enden platziert sind.
- **`Destroy` mit *keep children*** nimmt die Joints mit dem Objekt weg, wie Behaviors und den Rigidbody.
- **`Add joint` speichert das Ziel als Verweis per Id**, nicht als Laufzeit-Objekt. So zeichnet die Scene View die Linie auch im Play.
## Under the hood
- `src/core/model/joint.ts` (Schlüssel) und `src/core/properties/joint.ts` (was die Engine liest, ohne Engine prüfbar).
- `src/runtime/physics/joints.ts`: ein Engine-Joint je Joint-Objekt. Ein Ende ohne Rigidbody bekommt einen eigenen kinematischen Körper ohne Collider, der dem Objekt folgt.
- Neue Schema-Option `shownFor`: eine Zeile nur bei bestimmten Werten eines Enums.
- `src/viewport/jointLinks.ts` (Linie, ohne Pixi) und `src/editor/viewport/jointAnchor.ts` (Griff).
- Gemessen an Rapier 0.20: Die Winkelgrenze eines Revolute-Joints zählt absolut, nicht ab Start. Deshalb wird der Startwinkel aufgeschlagen.
## Tests (auf `melektaus-atx`)
```
Gate → 218 Files, 3390 Tests, CHECK EXIT 0 (3336 + 54)
e2e 41 → ALL PASSED – 25 checks (21 + 4)
e2e 1-16,24,39-46,51,52,57,58 → ALL PASSED – 831 checks
```
- **54 neue Unit-Tests:** 40 in `tests/core/joints.test.ts` (Seil, Feder, Pendel, Winkelgrenze, feste Verbindung, das andere Ende, der Node, Container, Einfügen, Löschen), 6 für die Linie der Scene View, 4 Katalog-Fälle des Nodes. Die übrigen 4 entstehen in Tests, die je Asset-Typ einen Fall erzeugen.
- **Gemessen in den Unit-Tests:**
- Pendel mit 100 Einheiten: Nulldurchgang nach 0,50 s (Periode gerechnet 2,007 s).
- Feder mit 40 N/m an 1 kg: Umkehr nach 0,50 s (Periode gerechnet 0,993 s).
- Seil mit Länge 150: Der Körper fällt auf 150 und nicht weiter.
- Hinge mit Grenze 30 Grad: bleibt zwischen 27 und 33 stehen, auch bei gedreht platziertem Objekt.
- **Vier neue e2e-Checks** in Modul 41 mit echter Maus: *Add joint…* im Menü, Rope zum Haken mit Linie und Griff, Griff ziehen samt einem Undo, im Play hängt die Kiste, während die ohne Joint fällt.
- **e2e 04, 40 und 46** zählen den neuen Typ in ihren Listen mit.
- **21 von 22 Unit-Gegenproben greifen.** Die eine übrige ist gleichwertig: Rapier ignoriert das Entfernen eines Joints, der mit seinem Körper schon weg ist. Die Schutzabfrage bleibt.
- **Alle drei Browser-Gegenproben greifen** (2, 1 und 2 Checks fallen): keine Linie, keine Joints in der Engine, Griff nicht greifbar.
- **Einen Screenshot angesehen:** Icon, Linie, Griff und die Rope-Zeilen im Inspector.
- Die e2e-Summe **1691** ist gerechnet (1687 + 4). Die 1687 hat CI an `e066613` gemessen. Volle Suite und Smokes liefen nicht.
- **Nicht im Browser geprüft** (nur Unit-Tests): Spring, Hinge mit Grenze, Fixed, der Node `Add joint`, ein bewegtes Connected object, Joints in Containern.
- **Gar nicht geprüft:** der veröffentlichte Spiel-Link, und alles nach dem Build.
- **Nicht behandelt:** Winkelgrenzen unter einem gespiegelten Parent zählen im Drehsinn der Engine.
## Nebenwirkung: das `+`-Menü scrollt bei 800 px Höhe
Mit *Joint* hat das `+`-Menü 25 Einträge und passt bei einer Fensterhöhe von 800 px nicht mehr ganz unter seinen Knopf. Der letzte Eintrag (*Vector art*) ist dort nur mit Scrollen zu erreichen. e2e 04 prüfte bisher, dass er ohne Scrollen sichtbar ist, und scrollt jetzt hin.
## Offen für Schritt 2
- Slider (Schiene) mit Weggrenzen.
- Motoren an Hinge und Slider.
Commit: `7dd3ae1`. Wiki: *Physics* (neuer Abschnitt „Joints“), *Concepts*, *Behaviors*, *Architecture*, *Development* (Summe).
Schritt 2 ist gebaut: Slider mit Weggrenzen, Motoren an Hinge und Slider, und ein Node zum Auslesen. Damit ist alles aus dem Issue-Text drin, bis auf eine Bruchkraft, die nie verlangt war.
Entschieden (Rückfragen vom 2026-10-03)
Schiene des Sliders: aus der Drehung des Joints selbst. 0° ist waagrecht, den Joint drehen dreht die Schiene. Die Scene View zeichnet die Achse durch den Joint, mit Strichen an den Grenzen.
Weggrenzen:Limit distance mit Min distance und Max distance, in Welteinheiten ab der Lage beim Start. Standard -100 bis 100, Min ≤ 0 ≤ Max.
Motor:Motor mit Off, Speed oder Target, dazu Motor speed, Motor target und Max force.
Lesen im Graph: ein Node Joint position mit den Ausgängen value und speed. Setzen bleibt bei Properties, und Motor-Änderungen bauen den Joint dabei nicht neu.
Was neu ist
Typ Slider: Das Objekt gleitet entlang der x-Achse des Joints und dreht sich gegen das andere Ende nicht. Ein Punkt wie bei Hinge und Fixed: wo der Joint sitzt.
Joints haben jetzt Scale, Rotation und Mirror X/Y wie ein Sprite, und der Dreh-Knopf der Scene View dreht sie. Wirkung hat das nur beim Slider. Der Joint dreht sich mit dem Objekt, auf dem er sitzt.
Zeilen je nach Typ und Motor-Art:
Limit distance, Min distance, Max distance: nur Slider.
Motor: nur Hinge und Slider.
Motor speed und Max force (0 = no limit): bei Speed und Target.
Motor target: nur bei Target.
Speed dreht bzw. fährt dauernd mit Motor speed (Grad/s bzw. Einheiten/s). Target fährt auf Motor target (Grad bzw. Einheiten ab Start) und hält dort.
Properties auf Motor speed, Motor target oder Max force ändert den laufenden Motor. Eine Tür geht also auf, wenn man 90 ins Ziel schreibt.
Node Joint position:value ist der Winkel (Hinge, zählt über volle Runden weiter) bzw. der Weg (Slider) ab der Lage beim Start, speed wie schnell sich das ändert. Bei Spring, Rope, Fixed, einem Joint, der nichts hält, und im Editor-Run: 0.
Scene View: Die Schiene eines Sliders ist gestrichelt, zwischen den Grenzen mit je einem Strich, ohne Grenzen 60 Einheiten je Seite.
Was ich selbst festgelegt habe
Target fährt höchstens mit Motor speed und bremst auf dem letzten Stück ab. Die Zeile Motor speed gilt deshalb auch bei Target.
Max force zählt wie bei Apply force (Einheiten · kg/s²). Beim Hinge ist es die Kraft 100 Einheiten vom Drehpunkt.
Motor an oder aus baut den Joint neu, die gemessene Lage bleibt dabei erhalten.
Slider zwischen zwei Rigidbodies: Rapier hält die zwei dort auf einem Winkel. Ist das gehaltene Objekt schräg platziert, dreht es sich beim Start auf die Lage des anderen, und die Konsole sagt das einmal. An der Szene oder an einem Objekt ohne Rigidbody bleibt es so gedreht, wie es platziert ist.
Im Play wird die Schiene dort gezeichnet, wo der Joint gerade ist. Sie fährt also mit dem Objekt mit.
Motorstärke: Ein Hinge-Motor ist bei einem kleinen Körper an einem langen Arm schwach. Gemessen: Eine Box von 20 an einem Arm von 100 hebt sich nicht gegen die Schwerkraft und schwingt ohne Schwerkraft auf 105 statt 90 über. Eine Tür von 100 x 20 mit dem Scharnier am Ende erreicht -90 genau und hält es. Mehr Solver-Iterationen helfen, kosten aber in jeder Szene, deshalb habe ich sie nicht gesetzt. Das steht im Wiki.
Under the hood
src/core/properties/joint.ts: sliderAxis, sliderRail und motorSpeed als reine Funktionen.
src/runtime/physics/joints.ts: Prismatic-Joint, Motor je Frame, measure für Winkel und Weg. Rapier 0.20 liest keinen Gelenkwinkel zurück, deshalb wird die Lage aus den zwei Körpern gerechnet.
shownFor im Schema darf eine Liste von Bedingungen sein, die alle gelten müssen.
Neue Unit-Tests: 16 in tests/core/joints.test.ts, einer in tests/viewport/joint-links.test.ts, 3 Katalog-Fälle für Joint position.
Gemessen in den Unit-Tests:
Slider an der Szene hält die Kiste gegen die Schwerkraft quer zur Schiene (unter 0,5 Einheiten).
Slider mit Grenze 50 bleibt zwischen 48 und 52 stehen.
Rad mit 360 Grad/s steht nach 2,5 s bei 880 bis 905 Grad.
Tür mit Ziel -90 ist nach 0,25 s unterwegs (zwischen -20 und -60) und steht nach 2 s bei -90.
Lift mit Ziel -200 hält dort und fährt dann auf ein neu gesetztes Ziel -50.
Max force 1 hebt die Kiste nicht, 100000 schon.
Zwei neue e2e-Checks (41f) mit echter Maus im Inspector: Typ Slider und Motor Speed wählen, die richtigen Zeilen und die Schiene. Im Play fährt der Bob mit 59,1 Einheiten in 1,0 s die Schiene hinauf (Soll 60), seitlich weniger als 0,5.
9 von 9 Unit-Gegenproben greifen: Motor aus, Drehversatz aus, Weggrenzen aus, kein Weiterzählen über volle Runden, Ziel ignoriert, nur die erste Zeilen-Bedingung, kein Hinweis, Motor nicht im Key, Achse ignoriert.
Die e2e-Summe 1701 ist gerechnet (1699 + 2). Volle Suite und Smokes liefen nicht, Browser-Gegenproben und Screenshots auch nicht.
Nicht im Browser geprüft (nur Unit-Tests): Hinge-Motor, Target-Modus, Weggrenzen, Joint position, der Dreh-Knopf am Joint, der Slider zwischen zwei Rigidbodies.
Nicht behandelt: Slider und Winkelgrenzen unter einem gespiegelten Parent.
Commit: f1e3bd8. Wiki 5f53111: Physics (Slider, Motor, Joint position), Behaviors, Concepts, Development (Summe).
Mit Schritt 1 (7dd3ae1) und Schritt 2 ist das Issue erledigt, ich schließe es.
Schritt 2 ist gebaut: Slider mit Weggrenzen, Motoren an Hinge und Slider, und ein Node zum Auslesen. Damit ist alles aus dem Issue-Text drin, bis auf eine Bruchkraft, die nie verlangt war.
## Entschieden (Rückfragen vom 2026-10-03)
1. **Schiene des Sliders:** aus der Drehung des Joints selbst. 0° ist waagrecht, den Joint drehen dreht die Schiene. Die Scene View zeichnet die Achse durch den Joint, mit Strichen an den Grenzen.
2. **Weggrenzen:** `Limit distance` mit *Min distance* und *Max distance*, in Welteinheiten ab der Lage beim Start. Standard -100 bis 100, Min ≤ 0 ≤ Max.
3. **Motor:** `Motor` mit Off, Speed oder Target, dazu *Motor speed*, *Motor target* und *Max force*.
4. **Lesen im Graph:** ein Node `Joint position` mit den Ausgängen `value` und `speed`. Setzen bleibt bei `Properties`, und Motor-Änderungen bauen den Joint dabei nicht neu.
## Was neu ist
- **Typ *Slider*:** Das Objekt gleitet entlang der x-Achse des Joints und dreht sich gegen das andere Ende nicht. Ein Punkt wie bei Hinge und Fixed: wo der Joint sitzt.
- **Joints haben jetzt *Scale*, *Rotation* und *Mirror X/Y*** wie ein Sprite, und der Dreh-Knopf der Scene View dreht sie. Wirkung hat das nur beim Slider. Der Joint dreht sich mit dem Objekt, auf dem er sitzt.
- **Zeilen** je nach Typ und Motor-Art:
- *Limit distance*, *Min distance*, *Max distance*: nur Slider.
- *Motor*: nur Hinge und Slider.
- *Motor speed* und *Max force (0 = no limit)*: bei Speed und Target.
- *Motor target*: nur bei Target.
- **Speed** dreht bzw. fährt dauernd mit *Motor speed* (Grad/s bzw. Einheiten/s). **Target** fährt auf *Motor target* (Grad bzw. Einheiten ab Start) und hält dort.
- **`Properties`** auf *Motor speed*, *Motor target* oder *Max force* ändert den laufenden Motor. Eine Tür geht also auf, wenn man 90 ins Ziel schreibt.
- **Node `Joint position`:** `value` ist der Winkel (Hinge, zählt über volle Runden weiter) bzw. der Weg (Slider) ab der Lage beim Start, `speed` wie schnell sich das ändert. Bei Spring, Rope, Fixed, einem Joint, der nichts hält, und im Editor-Run: 0.
- **Scene View:** Die Schiene eines Sliders ist gestrichelt, zwischen den Grenzen mit je einem Strich, ohne Grenzen 60 Einheiten je Seite.
## Was ich selbst festgelegt habe
- **Target fährt höchstens mit *Motor speed*** und bremst auf dem letzten Stück ab. Die Zeile *Motor speed* gilt deshalb auch bei Target.
- **Max force** zählt wie bei `Apply force` (Einheiten · kg/s²). Beim Hinge ist es die Kraft 100 Einheiten vom Drehpunkt.
- **Motor an oder aus** baut den Joint neu, die gemessene Lage bleibt dabei erhalten.
- **Slider zwischen zwei Rigidbodies:** Rapier hält die zwei dort auf einem Winkel. Ist das gehaltene Objekt schräg platziert, dreht es sich beim Start auf die Lage des anderen, und die Konsole sagt das einmal. An der Szene oder an einem Objekt ohne Rigidbody bleibt es so gedreht, wie es platziert ist.
- **Im Play wird die Schiene dort gezeichnet, wo der Joint gerade ist.** Sie fährt also mit dem Objekt mit.
- **Motorstärke:** Ein Hinge-Motor ist bei einem kleinen Körper an einem langen Arm schwach. Gemessen: Eine Box von 20 an einem Arm von 100 hebt sich nicht gegen die Schwerkraft und schwingt ohne Schwerkraft auf 105 statt 90 über. Eine Tür von 100 x 20 mit dem Scharnier am Ende erreicht -90 genau und hält es. Mehr Solver-Iterationen helfen, kosten aber in jeder Szene, deshalb habe ich sie nicht gesetzt. Das steht im Wiki.
## Under the hood
- `src/core/properties/joint.ts`: `sliderAxis`, `sliderRail` und `motorSpeed` als reine Funktionen.
- `src/runtime/physics/joints.ts`: Prismatic-Joint, Motor je Frame, `measure` für Winkel und Weg. Rapier 0.20 liest keinen Gelenkwinkel zurück, deshalb wird die Lage aus den zwei Körpern gerechnet.
- `shownFor` im Schema darf eine Liste von Bedingungen sein, die alle gelten müssen.
## Tests (auf `melektaus-atx`)
```
Gate → 220 Files, 3436 Tests, CHECK EXIT 0 (3416 + 20)
e2e 41 → ALL PASSED – 27 checks (25 + 2)
```
- **Neue Unit-Tests:** 16 in `tests/core/joints.test.ts`, einer in `tests/viewport/joint-links.test.ts`, 3 Katalog-Fälle für `Joint position`.
- **Gemessen in den Unit-Tests:**
- Slider an der Szene hält die Kiste gegen die Schwerkraft quer zur Schiene (unter 0,5 Einheiten).
- Slider mit Grenze 50 bleibt zwischen 48 und 52 stehen.
- Rad mit 360 Grad/s steht nach 2,5 s bei 880 bis 905 Grad.
- Tür mit Ziel -90 ist nach 0,25 s unterwegs (zwischen -20 und -60) und steht nach 2 s bei -90.
- Lift mit Ziel -200 hält dort und fährt dann auf ein neu gesetztes Ziel -50.
- Max force 1 hebt die Kiste nicht, 100000 schon.
- **Zwei neue e2e-Checks (41f)** mit echter Maus im Inspector: Typ *Slider* und Motor *Speed* wählen, die richtigen Zeilen und die Schiene. Im Play fährt der Bob mit 59,1 Einheiten in 1,0 s die Schiene hinauf (Soll 60), seitlich weniger als 0,5.
- **9 von 9 Unit-Gegenproben greifen:** Motor aus, Drehversatz aus, Weggrenzen aus, kein Weiterzählen über volle Runden, Ziel ignoriert, nur die erste Zeilen-Bedingung, kein Hinweis, Motor nicht im Key, Achse ignoriert.
- Die e2e-Summe **1701** ist gerechnet (1699 + 2). Volle Suite und Smokes liefen nicht, Browser-Gegenproben und Screenshots auch nicht.
- **Nicht im Browser geprüft** (nur Unit-Tests): Hinge-Motor, Target-Modus, Weggrenzen, `Joint position`, der Dreh-Knopf am Joint, der Slider zwischen zwei Rigidbodies.
- **Nicht behandelt:** Slider und Winkelgrenzen unter einem gespiegelten Parent.
Commit: f1e3bd8. Wiki `5f53111`: *Physics* (Slider, Motor, `Joint position`), *Behaviors*, *Concepts*, *Development* (Summe).
Mit Schritt 1 (7dd3ae1) und Schritt 2 ist das Issue erledigt, ich schließe es.
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.
Aus #144 („Später“). Zwei Körper verbinden: Scharnier (Tür, Pendel, Rad), Feder, Seil, feste Verbindung, Schiene.
Befund
revolute,spring,rope,fixed,prismatic(createImpulseJoint(data, body1, body2)), mit Grenzen und Motoren bei revolute/prismatic.objectgibt es für Graph-Ports (kinds.ts), aber nicht als gespeicherte Referenz im Inspector.Zu bauen
Offen (mit Vorschlag)
objectRefper Id), weil ihn später auch andere brauchen (Follow, Spawner-Ziel).#148Schritt 1 ist gebaut: Hinge (mit Winkelgrenze), Spring, Rope und Fixed. Das Issue bleibt offen für Schritt 2 (Slider und Motoren), wie in Antwort 1 gewünscht.
Entschieden (Rückfragen vom 2026-10-02)
Connected anchor X/Yam anderen Objekt (Standard 0,0 = dessen Ursprung, ohne Objekt ein Punkt der Szene).Add joint. Lösen läuft über das vorhandeneDestroyam Joint, einen eigenenBreak jointgibt es nicht.Was neu ist
Add joint: Eingänge joint asset (Vorlage für Typ und Werte), object, connected, x, y. Ausgang ist der neue Joint.Propertiesmit Type Joint ändert die Werte eines platzierten Joints.Was ich selbst festgelegt habe
Length0 nimmt den Abstand, in dem die zwei Enden platziert sind.Destroymit keep children nimmt die Joints mit dem Objekt weg, wie Behaviors und den Rigidbody.Add jointspeichert das Ziel als Verweis per Id, nicht als Laufzeit-Objekt. So zeichnet die Scene View die Linie auch im Play.Under the hood
src/core/model/joint.ts(Schlüssel) undsrc/core/properties/joint.ts(was die Engine liest, ohne Engine prüfbar).src/runtime/physics/joints.ts: ein Engine-Joint je Joint-Objekt. Ein Ende ohne Rigidbody bekommt einen eigenen kinematischen Körper ohne Collider, der dem Objekt folgt.shownFor: eine Zeile nur bei bestimmten Werten eines Enums.src/viewport/jointLinks.ts(Linie, ohne Pixi) undsrc/editor/viewport/jointAnchor.ts(Griff).Tests (auf
melektaus-atx)tests/core/joints.test.ts(Seil, Feder, Pendel, Winkelgrenze, feste Verbindung, das andere Ende, der Node, Container, Einfügen, Löschen), 6 für die Linie der Scene View, 4 Katalog-Fälle des Nodes. Die übrigen 4 entstehen in Tests, die je Asset-Typ einen Fall erzeugen.e066613gemessen. Volle Suite und Smokes liefen nicht.Add joint, ein bewegtes Connected object, Joints in Containern.Nebenwirkung: das
+-Menü scrollt bei 800 px HöheMit Joint hat das
+-Menü 25 Einträge und passt bei einer Fensterhöhe von 800 px nicht mehr ganz unter seinen Knopf. Der letzte Eintrag (Vector art) ist dort nur mit Scrollen zu erreichen. e2e 04 prüfte bisher, dass er ohne Scrollen sichtbar ist, und scrollt jetzt hin.Offen für Schritt 2
Commit:
7dd3ae1. Wiki: Physics (neuer Abschnitt „Joints“), Concepts, Behaviors, Architecture, Development (Summe).Schritt 2 ist gebaut: Slider mit Weggrenzen, Motoren an Hinge und Slider, und ein Node zum Auslesen. Damit ist alles aus dem Issue-Text drin, bis auf eine Bruchkraft, die nie verlangt war.
Entschieden (Rückfragen vom 2026-10-03)
Limit distancemit Min distance und Max distance, in Welteinheiten ab der Lage beim Start. Standard -100 bis 100, Min ≤ 0 ≤ Max.Motormit Off, Speed oder Target, dazu Motor speed, Motor target und Max force.Joint positionmit den Ausgängenvalueundspeed. Setzen bleibt beiProperties, und Motor-Änderungen bauen den Joint dabei nicht neu.Was neu ist
Propertiesauf Motor speed, Motor target oder Max force ändert den laufenden Motor. Eine Tür geht also auf, wenn man 90 ins Ziel schreibt.Joint position:valueist der Winkel (Hinge, zählt über volle Runden weiter) bzw. der Weg (Slider) ab der Lage beim Start,speedwie schnell sich das ändert. Bei Spring, Rope, Fixed, einem Joint, der nichts hält, und im Editor-Run: 0.Was ich selbst festgelegt habe
Apply force(Einheiten · kg/s²). Beim Hinge ist es die Kraft 100 Einheiten vom Drehpunkt.Under the hood
src/core/properties/joint.ts:sliderAxis,sliderRailundmotorSpeedals reine Funktionen.src/runtime/physics/joints.ts: Prismatic-Joint, Motor je Frame,measurefür Winkel und Weg. Rapier 0.20 liest keinen Gelenkwinkel zurück, deshalb wird die Lage aus den zwei Körpern gerechnet.shownForim Schema darf eine Liste von Bedingungen sein, die alle gelten müssen.Tests (auf
melektaus-atx)tests/core/joints.test.ts, einer intests/viewport/joint-links.test.ts, 3 Katalog-Fälle fürJoint position.Joint position, der Dreh-Knopf am Joint, der Slider zwischen zwei Rigidbodies.Commit:
f1e3bd8. Wiki5f53111: Physics (Slider, Motor,Joint position), Behaviors, Concepts, Development (Summe).Mit Schritt 1 (
7dd3ae1) und Schritt 2 ist das Issue erledigt, ich schließe es.