Aus #144 („Später“). Collider sollen sich gezielt ignorieren können, z. B. Spieler-Schüsse treffen keine Spieler, Gegner laufen durch Gegner.
Befund
Heute kollidiert jeder Collider mit jedem. Filtern kann man nur über Trigger (keine Blockade) oder im Graph nach dem Event. Es gibt keine Ebenen, keine Masken und auch keine Tags am Objekt.
Rapier hat dafür InteractionGroups: 16 Bit Mitgliedschaft + 16 Bit Filter pro Collider, getrennt für Erkennen (setCollisionGroups) und Blockieren (setSolverGroups).
Move und der Editor-Run laufen über unsere eigene Geometrie in Solids, Events ohne Dynamic Body über die eigene Überlappungsprüfung. Die Regel muss also an drei Stellen gleich wirken: Rapier, Solids, Event-Paarsuche.
Zu bauen
Projektweite Liste benannter Ebenen (max. 16), in den Projekt-Einstellungen.
Am Collider: Layer (eine Ebene) und Collides with (Mehrfachauswahl). Vorgabe: Ebene „Default“, kollidiert mit allen — bestehende Projekte ändern sich nicht.
Tilemap: eigene Ebene pro Tilemap.
Eine gemeinsame Funktion layersMeet(a, b), die Rapier-Gruppen, Solids und die Event-Paarsuche benutzen.
Tests: Paar filtert in beiden Systemen gleich; Dynamic Body fällt durch eine ausgeschlossene Ebene; Events bleiben aus.
Offen (mit Vorschlag)
Symmetrisch oder einseitig? Vorschlag: symmetrisch (A trifft B nur, wenn beide einander zulassen) — so rechnet Rapier, und es gibt keine Paare, die nur halb kollidieren.
Erkennen vs. Blockieren getrennt einstellbar? Vorschlag: nein, eine Einstellung für beides. Wer nur erkennen will, nimmt Trigger.
Ebenen im Graph ändern (Node „Set layer“)? Vorschlag: ja, als eigener Node, aber erst nach dem Grundbau.
Aus #144 („Später“). Collider sollen sich gezielt ignorieren können, z. B. Spieler-Schüsse treffen keine Spieler, Gegner laufen durch Gegner.
## Befund
- **Heute kollidiert jeder Collider mit jedem.** Filtern kann man nur über `Trigger` (keine Blockade) oder im Graph nach dem Event. Es gibt keine Ebenen, keine Masken und auch keine Tags am Objekt.
- Rapier hat dafür `InteractionGroups`: 16 Bit Mitgliedschaft + 16 Bit Filter pro Collider, getrennt für Erkennen (`setCollisionGroups`) und Blockieren (`setSolverGroups`).
- `Move` und der Editor-Run laufen über unsere eigene Geometrie in `Solids`, Events ohne Dynamic Body über die eigene Überlappungsprüfung. Die Regel muss also an **drei** Stellen gleich wirken: Rapier, `Solids`, Event-Paarsuche.
## Zu bauen
- Projektweite Liste benannter Ebenen (max. 16), in den Projekt-Einstellungen.
- Am Collider: **Layer** (eine Ebene) und **Collides with** (Mehrfachauswahl). Vorgabe: Ebene „Default“, kollidiert mit allen — bestehende Projekte ändern sich nicht.
- Tilemap: eigene Ebene pro Tilemap.
- Eine gemeinsame Funktion `layersMeet(a, b)`, die Rapier-Gruppen, `Solids` und die Event-Paarsuche benutzen.
- Tests: Paar filtert in beiden Systemen gleich; Dynamic Body fällt durch eine ausgeschlossene Ebene; Events bleiben aus.
## Offen (mit Vorschlag)
1. **Symmetrisch oder einseitig?** Vorschlag: symmetrisch (A trifft B nur, wenn beide einander zulassen) — so rechnet Rapier, und es gibt keine Paare, die nur halb kollidieren.
2. **Erkennen vs. Blockieren getrennt einstellbar?** Vorschlag: nein, eine Einstellung für beides. Wer nur erkennen will, nimmt `Trigger`.
3. **Ebenen im Graph ändern (Node „Set layer“)?** Vorschlag: ja, als eigener Node, aber erst nach dem Grundbau.
Gebaut und gepusht: 4f55f85 (dazu defd4a8, siehe unten), Wiki 0b7f0c2.
Was jetzt da ist
Ein Collider trägt eine Ebene (Layer) und eine Maske der Ebenen, die er trifft (Collides with). Ein Paar trifft sich nur, wenn beide Masken die Ebene des anderen nennen — symmetrisch, wie in den Rückfragen entschieden, und eine Einstellung für Erkennen und Blockieren zugleich.
Die Regel liegt als reines Modul src/core/model/collisionLayers.ts (layerBit, layersMeet, die Namen, mit denen ein Projekt startet). Die drei Stellen, die sich einig sein müssen, lesen dieselbe Funktion:
CollisionSystem — der Lauf im Editor,
solids.ts — der Filter pro Paar im Sweep,
PhysicsSystem — bei Rapier wird die Maske zu den Interaktionsgruppen des Colliders.
Projektdatei: neues Feld collisionLayers: ["Default"] (Default eine Ebene, kein formatVersion-Bump). Schema: zwei neue Feldarten — layer als Auswahl der Projektebenen, layerMask als Menü mit Häkchen, das beim Anklicken offen bleibt (Menu.ts kennt jetzt Zeilen, die nicht schließen).
Bearbeitet werden die Namen über „Collision layers…" im Project-Menü der Toolbar, nicht über einen neuen Project-Tab. Das habe ich selbst entschieden, weil die Rückfrage dazu ins Timeout lief — es ist der kleinste Eingriff; ein Project-Tab bleibt möglich, sobald es mehr Projekteinstellungen gibt. Der Dialog hält bis zu 16 Namen, „Add layer" hängt Layer <n> an, „Apply" schreibt alle als einen Undo-Schritt (SetCollisionLayersCommand).
Ein Set layer-Node ist wie besprochen nicht dabei, der kommt später.
Eine Falle, die ein Test gefangen hat
Number(null) ist 0 — eine collidesWith: null in einer Projektdatei hätte damit stillschweigend „trifft nichts" bedeutet. Die Maske geht deshalb durch eine Prüfung, die alles ablehnt, was keine endliche Zahl ist.
9 neue e2e-Checks in Modul 8: Menüeintrag, Dialog, „Add layer", „Apply" samt Undo, beide Inspector-Zeilen, das Häkchen-Menü, und zum Schluss läuft der Mover geradewegs durch die Kiste, an der er in 8c noch geblockt hat, sobald ihre Ebenen sich nicht mehr treffen.
21 neue Unit-Tests zur Regel, zum Schema und zum Command.
Zwei Gegenproben mit eingebautem Fehler, beide greifen: einmal der Rapier-Pfad, einmal Solids.
Dialog und Häkchen-Menü im Screenshot angesehen.
Check-Summe 1138 in README und Wiki, lokal gemessen.
Doku
Wiki: Physics und Colliders (Ebene, Maske, wo die Namen bearbeitet werden), API (das Projektfeld), Architecture (collisionLayers, CollisionLayersDialog), Concepts und Tilemaps (die zwei neuen Inspector-Zeilen), Development (Check-Summe).
Zwei Nachträge
defd4a8: Der CI-Lauf zum vorigen Commit 5ba796a (#119) war rot — Details stehen dort.
Der CI-Lauf zu diesem Stand läuft noch; er bestätigt dann auch die 1138.
Gebaut und gepusht: **`4f55f85`** (dazu `defd4a8`, siehe unten), Wiki **`0b7f0c2`**.
## Was jetzt da ist
Ein Collider trägt eine **Ebene** (`Layer`) und eine **Maske** der Ebenen, die er trifft (`Collides with`). Ein Paar trifft sich nur, wenn **beide** Masken die Ebene des anderen nennen — symmetrisch, wie in den Rückfragen entschieden, und eine Einstellung für Erkennen und Blockieren zugleich.
Die Regel liegt als reines Modul `src/core/model/collisionLayers.ts` (`layerBit`, `layersMeet`, die Namen, mit denen ein Projekt startet). Die drei Stellen, die sich einig sein müssen, lesen dieselbe Funktion:
- `CollisionSystem` — der Lauf im Editor,
- `solids.ts` — der Filter pro Paar im Sweep,
- `PhysicsSystem` — bei Rapier wird die Maske zu den Interaktionsgruppen des Colliders.
**Projektdatei:** neues Feld `collisionLayers: ["Default"]` (Default eine Ebene, kein `formatVersion`-Bump). **Schema:** zwei neue Feldarten — `layer` als Auswahl der Projektebenen, `layerMask` als Menü mit Häkchen, das beim Anklicken offen bleibt (`Menu.ts` kennt jetzt Zeilen, die nicht schließen).
**Bearbeitet werden die Namen über „Collision layers…" im Project-Menü der Toolbar**, nicht über einen neuen Project-Tab. Das habe ich selbst entschieden, weil die Rückfrage dazu ins Timeout lief — es ist der kleinste Eingriff; ein Project-Tab bleibt möglich, sobald es mehr Projekteinstellungen gibt. Der Dialog hält bis zu 16 Namen, „Add layer" hängt `Layer <n>` an, „Apply" schreibt alle als **einen** Undo-Schritt (`SetCollisionLayersCommand`).
Ein `Set layer`-Node ist wie besprochen **nicht** dabei, der kommt später.
## Eine Falle, die ein Test gefangen hat
`Number(null)` ist `0` — eine `collidesWith: null` in einer Projektdatei hätte damit stillschweigend „trifft nichts" bedeutet. Die Maske geht deshalb durch eine Prüfung, die alles ablehnt, was keine endliche Zahl ist.
## Tests
```
./check.sh → CHECK EXIT 0 (119 Files, 1739 Tests)
npm run build → BUILD EXIT 0
node tools/e2e.mjs (alle Module) → ALL PASSED – 1138 checks
```
- **9 neue e2e-Checks in Modul 8:** Menüeintrag, Dialog, „Add layer", „Apply" samt Undo, beide Inspector-Zeilen, das Häkchen-Menü, und zum Schluss läuft der Mover geradewegs durch die Kiste, an der er in 8c noch geblockt hat, sobald ihre Ebenen sich nicht mehr treffen.
- **21 neue Unit-Tests** zur Regel, zum Schema und zum Command.
- **Zwei Gegenproben mit eingebautem Fehler**, beide greifen: einmal der Rapier-Pfad, einmal `Solids`.
- Dialog und Häkchen-Menü im Screenshot angesehen.
- **Check-Summe 1138** in README und Wiki, lokal gemessen.
## Doku
Wiki: Physics und Colliders (Ebene, Maske, wo die Namen bearbeitet werden), API (das Projektfeld), Architecture (`collisionLayers`, `CollisionLayersDialog`), Concepts und Tilemaps (die zwei neuen Inspector-Zeilen), Development (Check-Summe).
## Zwei Nachträge
- **`defd4a8`:** Der CI-Lauf zum vorigen Commit `5ba796a` (#119) war rot — Details stehen dort.
- Der CI-Lauf zu diesem Stand läuft noch; er bestätigt dann auch die 1138.
Nachtrag: die beiden CI-Läufe nach diesem Issue waren rot — beide Male am Rand, nicht an der Regel.
Lauf 112 (4f55f85, Task 176/177): Gate grün, die Editor-Suite grün mit ALL PASSED – 1138 checks, der Actor-Smoke aber mit einem FAIL:
FAIL walking diagonally covers the same distance as walking straight – {"straight":40,"diagonal":58}
58 ist rund 40·√2, die Diagonale wird also nicht normalisiert gemessen. Lokal ist derselbe Smoke grün (21 Checks). Ich habe den Check nicht angefasst — er gehört nicht zu diesem Issue, und ob er wirklich flattert oder etwas Echtes zeigt, ist noch nicht gemessen. Wenn er wiederkommt, braucht er ein eigenes Issue.
Lauf 113 (defd4a8, Task 178/179): Gate grün, die Suite abgebrochen — kein FAIL, sondern eine Exception in genau dem Block, den dieses Issue angelegt hat:
TypeError: Cannot read properties of null (reading 'includes')
at colliders (tools/e2e/08-colliders.mjs:142:37)
Ursache: clickText('Project') hat in CI das Project-Menü nicht geöffnet, danach gab __gm.menuRows()null zurück und .includes(...) lief darauf. Lokal ging der Klick immer auf. Der Stand ist inzwischen überholt — mit #183 öffnet das Modul das Menü über clickToolbar('menu') und liest die Zeilen erst danach; ob das in CI hält, zeigt der Lauf zu da35f10.
Was beide gemeinsam haben: die Regel selbst (Ebenen und Masken) war in beiden Läufen grün.
**Nachtrag: die beiden CI-Läufe nach diesem Issue waren rot — beide Male am Rand, nicht an der Regel.**
**Lauf 112 (`4f55f85`, Task 176/177):** Gate grün, die Editor-Suite grün mit `ALL PASSED – 1138 checks`, der **Actor-Smoke** aber mit einem FAIL:
```
FAIL walking diagonally covers the same distance as walking straight – {"straight":40,"diagonal":58}
```
58 ist rund 40·√2, die Diagonale wird also nicht normalisiert gemessen. Lokal ist derselbe Smoke grün (21 Checks). Ich habe den Check **nicht** angefasst — er gehört nicht zu diesem Issue, und ob er wirklich flattert oder etwas Echtes zeigt, ist noch nicht gemessen. Wenn er wiederkommt, braucht er ein eigenes Issue.
**Lauf 113 (`defd4a8`, Task 178/179):** Gate grün, die Suite **abgebrochen** — kein FAIL, sondern eine Exception in genau dem Block, den dieses Issue angelegt hat:
```
TypeError: Cannot read properties of null (reading 'includes')
at colliders (tools/e2e/08-colliders.mjs:142:37)
```
Ursache: `clickText('Project')` hat in CI das Project-Menü nicht geöffnet, danach gab `__gm.menuRows()` `null` zurück und `.includes(...)` lief darauf. Lokal ging der Klick immer auf. Der Stand ist inzwischen überholt — mit #183 öffnet das Modul das Menü über `clickToolbar('menu')` und liest die Zeilen erst danach; ob das in CI hält, zeigt der Lauf zu `da35f10`.
Was beide gemeinsam haben: **die Regel selbst (Ebenen und Masken) war in beiden Läufen grün.**
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“). Collider sollen sich gezielt ignorieren können, z. B. Spieler-Schüsse treffen keine Spieler, Gegner laufen durch Gegner.
Befund
Trigger(keine Blockade) oder im Graph nach dem Event. Es gibt keine Ebenen, keine Masken und auch keine Tags am Objekt.InteractionGroups: 16 Bit Mitgliedschaft + 16 Bit Filter pro Collider, getrennt für Erkennen (setCollisionGroups) und Blockieren (setSolverGroups).Moveund der Editor-Run laufen über unsere eigene Geometrie inSolids, Events ohne Dynamic Body über die eigene Überlappungsprüfung. Die Regel muss also an drei Stellen gleich wirken: Rapier,Solids, Event-Paarsuche.Zu bauen
layersMeet(a, b), die Rapier-Gruppen,Solidsund die Event-Paarsuche benutzen.Offen (mit Vorschlag)
Trigger.vorschläge passen
Gebaut und gepusht:
4f55f85(dazudefd4a8, siehe unten), Wiki0b7f0c2.Was jetzt da ist
Ein Collider trägt eine Ebene (
Layer) und eine Maske der Ebenen, die er trifft (Collides with). Ein Paar trifft sich nur, wenn beide Masken die Ebene des anderen nennen — symmetrisch, wie in den Rückfragen entschieden, und eine Einstellung für Erkennen und Blockieren zugleich.Die Regel liegt als reines Modul
src/core/model/collisionLayers.ts(layerBit,layersMeet, die Namen, mit denen ein Projekt startet). Die drei Stellen, die sich einig sein müssen, lesen dieselbe Funktion:CollisionSystem— der Lauf im Editor,solids.ts— der Filter pro Paar im Sweep,PhysicsSystem— bei Rapier wird die Maske zu den Interaktionsgruppen des Colliders.Projektdatei: neues Feld
collisionLayers: ["Default"](Default eine Ebene, keinformatVersion-Bump). Schema: zwei neue Feldarten —layerals Auswahl der Projektebenen,layerMaskals Menü mit Häkchen, das beim Anklicken offen bleibt (Menu.tskennt jetzt Zeilen, die nicht schließen).Bearbeitet werden die Namen über „Collision layers…" im Project-Menü der Toolbar, nicht über einen neuen Project-Tab. Das habe ich selbst entschieden, weil die Rückfrage dazu ins Timeout lief — es ist der kleinste Eingriff; ein Project-Tab bleibt möglich, sobald es mehr Projekteinstellungen gibt. Der Dialog hält bis zu 16 Namen, „Add layer" hängt
Layer <n>an, „Apply" schreibt alle als einen Undo-Schritt (SetCollisionLayersCommand).Ein
Set layer-Node ist wie besprochen nicht dabei, der kommt später.Eine Falle, die ein Test gefangen hat
Number(null)ist0— einecollidesWith: nullin einer Projektdatei hätte damit stillschweigend „trifft nichts" bedeutet. Die Maske geht deshalb durch eine Prüfung, die alles ablehnt, was keine endliche Zahl ist.Tests
Solids.Doku
Wiki: Physics und Colliders (Ebene, Maske, wo die Namen bearbeitet werden), API (das Projektfeld), Architecture (
collisionLayers,CollisionLayersDialog), Concepts und Tilemaps (die zwei neuen Inspector-Zeilen), Development (Check-Summe).Zwei Nachträge
defd4a8: Der CI-Lauf zum vorigen Commit5ba796a(#119) war rot — Details stehen dort.Nachtrag: die beiden CI-Läufe nach diesem Issue waren rot — beide Male am Rand, nicht an der Regel.
Lauf 112 (
4f55f85, Task 176/177): Gate grün, die Editor-Suite grün mitALL PASSED – 1138 checks, der Actor-Smoke aber mit einem FAIL:58 ist rund 40·√2, die Diagonale wird also nicht normalisiert gemessen. Lokal ist derselbe Smoke grün (21 Checks). Ich habe den Check nicht angefasst — er gehört nicht zu diesem Issue, und ob er wirklich flattert oder etwas Echtes zeigt, ist noch nicht gemessen. Wenn er wiederkommt, braucht er ein eigenes Issue.
Lauf 113 (
defd4a8, Task 178/179): Gate grün, die Suite abgebrochen — kein FAIL, sondern eine Exception in genau dem Block, den dieses Issue angelegt hat:Ursache:
clickText('Project')hat in CI das Project-Menü nicht geöffnet, danach gab__gm.menuRows()nullzurück und.includes(...)lief darauf. Lokal ging der Klick immer auf. Der Stand ist inzwischen überholt — mit #183 öffnet das Modul das Menü überclickToolbar('menu')und liest die Zeilen erst danach; ob das in CI hält, zeigt der Lauf zuda35f10.Was beide gemeinsam haben: die Regel selbst (Ebenen und Masken) war in beiden Läufen grün.