S3: Gegner, Spatial Grid und weiche Separation #16

Closed
opened 2026-08-10 13:14:34 +00:00 by RDietermann · 1 comment
Owner

Part of #13
Blocked by: #14, #15

Gegner erscheinen am Rand, laufen radial auf den Turm zu — und das Spatial Grid aus ADR-0004 entsteht hier, weil es ab jetzt drei Konsumenten bedient.

Inhalt

  • Gegner spawnen auf der Arena-Peripherie und bewegen sich mit konstantem Tempo auf die Mitte zu.
  • Ein Enemy trägt HP. Ein Archetyp genügt im Skelett; die drei bis vier aus der Kalibrierung kommen später.
  • Spatial Grid nach ADR-0004: Rebuild pro Frame, Zellgröße am Separationsradius. Das Grid ist geteilte Infrastruktur — hier entsteht es, S4 nutzt es für Kollision und Zielauswahl.
  • Weiche Separation als geklemmter Positionsversatz. Die Klemmung ist die Antwort auf den Turmring, wo die Dichte am höchsten ist und der Rest-Vektor nach außen zeigt.
  • Eine Soll-Population, die das Feld auffüllt. Der Spawner ist cap-getrieben („fülle bis zum Soll auf"), nicht plan-getrieben — siehe die Cap-Folgen an #1.

Akzeptanz

  • Bei der Soll-Population laufen Gegner sichtbar radial zusammen, ohne sich zu einem Punkt zu verdichten oder am Turmring zu pulsieren.
  • Das Grid liefert eine Nachbarschaftsabfrage, die S4 ohne Änderung benutzen kann.
  • Die Frametime bleibt bei der geplanten Obergrenze im Rahmen dessen, was examples/perf_spike.rs gemessen hat — mit dem Grid-Aufschlag aus ADR-0004 von ≤ 0,10 ms bei 10 000 Gegnern. Nachmessen, nicht annehmen.

Nicht in diesem Ticket: Wellen-Takt, Bosse, Gegner-Archetypen, Angriff auf den Turm.

Part of #13 Blocked by: #14, #15 Gegner erscheinen am Rand, laufen radial auf den Turm zu — und das **Spatial Grid** aus ADR-0004 entsteht hier, weil es ab jetzt drei Konsumenten bedient. **Inhalt** - Gegner spawnen auf der Arena-Peripherie und bewegen sich mit konstantem Tempo auf die Mitte zu. - Ein `Enemy` trägt HP. Ein Archetyp genügt im Skelett; die drei bis vier aus der Kalibrierung kommen später. - **Spatial Grid nach ADR-0004**: Rebuild pro Frame, Zellgröße am Separationsradius. Das Grid ist geteilte Infrastruktur — hier entsteht es, S4 nutzt es für Kollision und Zielauswahl. - **Weiche Separation** als geklemmter Positionsversatz. Die Klemmung ist die Antwort auf den Turmring, wo die Dichte am höchsten ist und der Rest-Vektor nach außen zeigt. - Eine Soll-Population, die das Feld auffüllt. Der Spawner ist **cap-getrieben** („fülle bis zum Soll auf"), nicht plan-getrieben — siehe die Cap-Folgen an #1. **Akzeptanz** - Bei der Soll-Population laufen Gegner sichtbar radial zusammen, ohne sich zu einem Punkt zu verdichten oder am Turmring zu pulsieren. - Das Grid liefert eine Nachbarschaftsabfrage, die S4 ohne Änderung benutzen kann. - Die Frametime bleibt bei der geplanten Obergrenze im Rahmen dessen, was `examples/perf_spike.rs` gemessen hat — mit dem Grid-Aufschlag aus ADR-0004 von ≤ 0,10 ms bei 10 000 Gegnern. Nachmessen, nicht annehmen. **Nicht in diesem Ticket**: Wellen-Takt, Bosse, Gegner-Archetypen, Angriff auf den Turm.
RDietermann added the wayfinder:task label 2026-08-10 13:14:34 +00:00
Author
Owner

Gebaut auf slice/skeleton (0f93a93, f1b8586).

Inhalt

  • src/enemy.rs — cap-getriebener Spawner, radiale Bewegung mit Stopp an der Angriffsreichweite, weiche Separation. Ein Archetyp, Health als Component ohne Leser (#17).
  • src/spatial.rs — Uniform Grid als Resource, Rebuild pro Frame, Zellgröße = max(Separationsradius, Trefferradius). Eine geteilte Abfrage for_each_in_radius, Narrow Phase inklusive.
  • Frame-Ordnung nach ADR-0004 §6 explizit in main.rs verdrahtet, nicht aus der Registrierungsreihenfolge geerbt.
  • Acht neue Balance-Werte, alle Platzhalter außer HARD_ENEMY_CAP.

Akzeptanz

  • Ring visuell abgenommen — läuft radial zusammen, verdichtet sich nicht auf einen Punkt, pulsiert nicht.
  • Nachbarschaftsabfrage steht und ist die, die S4 unverändert benutzt.
  • Frametime nachgemessen, siehe Fund 1.

Zwei Funde gegen ADR-0004 — ADR geändert, nicht Code daran vorbeigebaut

1. Der Maßstab in §3 war falsch, die Schranke hält

Gemessen im Release-Profil bei HARD_ENEMY_CAP, Median über 20 Läufe je System:

System 10 000 Gegner
move_enemies 0,021 ms
rebuild_spatial_grid 0,057 ms

Der Rebuild ist rund 2,7x teurer als move_enemies, nicht billiger. §3 zählt Arithmetik — zwei Divisionen und zwei floor gegen eine Normalisierung mit Wurzel — und übersieht, was hier dominiert: move_enemies ist ein linearer Lauf über eine Tabelle, der Rebuild schreibt in ~7 000 verstreute Vecs und zahlt Cache-Misses. Der Vec-Push, den §3 als „Rest der Ersparnis" abtut, ist der Kostenpunkt.

Die Entscheidung bleibt trotzdem stehen: Sie stand nie auf dem Vergleich, sondern auf der absoluten Schranke von 0,10 ms. 0,057 ms sind 0,34 % des Frame-Budgets. Und das inkrementelle Update wird dadurch nicht attraktiver — es bezahlt denselben verstreuten Schreibzugriff bei jedem Zellwechsel, und bei radialer Konvergenz wechseln ständig welche.

Geändert: Nachtrag zu ADR-0004 §3, Revisions-Auslöser 1 auf die absolute Schranke umgestellt (alte Fassung als solche vermerkt), dieselbe Korrektur an der Akzeptanz von Spec-Feature 3. Der Auslöser ist jetzt als laufender Check hinterlegt — the_rebuild_stays_within_its_budget_at_the_hard_cap, cargo test --release -- --ignored --nocapture.

Nachzumessen nach dem Wechsel auf echte Texturen und mit HUD, wie alle Zahlen aus #4.

2. §1 Ablage: Vec<(Entity, Vec2)> statt Vec<Entity>

Die Position wird neben der Entity im Bucket geführt. Grund ist die Separation aus §5: sie schreibt Transform und liest dabei die Transforms ihrer Nachbarn. Mit bloßen Entitys müsste sie dieselbe Gegner-Query gleichzeitig lesend und schreibend halten — in Bevy nicht ausleihbar, und die Auswege (ParamSet, ein zweiter Puffer-Durchlauf) kosten mehr als ein Vec2 je Eintrag. Ablage geändert, Entscheidung unberührt. Nachtrag in §1.


Offen, gehört nicht hierher: ‹k›=8, ‹f›=0,5, Separationsradius 14 und Soll-Population 400 sind Startwerte. Sie findet nur Spielen, und der Beleg ist der Blick auf den Turmring — steht so in ADR-0004 und in der Spec unter „Offen".

Gebaut auf `slice/skeleton` (0f93a93, f1b8586). **Inhalt** - `src/enemy.rs` — cap-getriebener Spawner, radiale Bewegung mit Stopp an der Angriffsreichweite, weiche Separation. Ein Archetyp, `Health` als Component ohne Leser (#17). - `src/spatial.rs` — Uniform Grid als `Resource`, Rebuild pro Frame, Zellgröße = `max(Separationsradius, Trefferradius)`. Eine geteilte Abfrage `for_each_in_radius`, Narrow Phase inklusive. - Frame-Ordnung nach ADR-0004 §6 explizit in `main.rs` verdrahtet, nicht aus der Registrierungsreihenfolge geerbt. - Acht neue `Balance`-Werte, alle Platzhalter außer `HARD_ENEMY_CAP`. **Akzeptanz** - Ring visuell abgenommen — läuft radial zusammen, verdichtet sich nicht auf einen Punkt, pulsiert nicht. - Nachbarschaftsabfrage steht und ist die, die S4 unverändert benutzt. - Frametime nachgemessen, siehe Fund 1. --- ## Zwei Funde gegen ADR-0004 — ADR geändert, nicht Code daran vorbeigebaut ### 1. Der Maßstab in §3 war falsch, die Schranke hält Gemessen im Release-Profil bei `HARD_ENEMY_CAP`, Median über 20 Läufe je System: | System | 10 000 Gegner | |---|---| | `move_enemies` | 0,021 ms | | `rebuild_spatial_grid` | **0,057 ms** | **Der Rebuild ist rund 2,7x teurer als `move_enemies`, nicht billiger.** §3 zählt Arithmetik — zwei Divisionen und zwei `floor` gegen eine Normalisierung mit Wurzel — und übersieht, was hier dominiert: `move_enemies` ist ein linearer Lauf über eine Tabelle, der Rebuild schreibt in ~7 000 verstreute `Vec`s und zahlt Cache-Misses. Der `Vec`-Push, den §3 als „Rest der Ersparnis" abtut, **ist** der Kostenpunkt. Die Entscheidung bleibt trotzdem stehen: Sie stand nie auf dem Vergleich, sondern auf der absoluten Schranke von 0,10 ms. 0,057 ms sind 0,34 % des Frame-Budgets. Und das inkrementelle Update wird dadurch nicht attraktiver — es bezahlt denselben verstreuten Schreibzugriff bei jedem Zellwechsel, und bei radialer Konvergenz wechseln ständig welche. Geändert: Nachtrag zu ADR-0004 §3, Revisions-Auslöser 1 auf die absolute Schranke umgestellt (alte Fassung als solche vermerkt), dieselbe Korrektur an der Akzeptanz von Spec-Feature 3. Der Auslöser ist jetzt als laufender Check hinterlegt — `the_rebuild_stays_within_its_budget_at_the_hard_cap`, `cargo test --release -- --ignored --nocapture`. Nachzumessen nach dem Wechsel auf echte Texturen und mit HUD, wie alle Zahlen aus #4. ### 2. §1 Ablage: `Vec<(Entity, Vec2)>` statt `Vec<Entity>` Die Position wird neben der `Entity` im Bucket geführt. Grund ist die Separation aus §5: sie schreibt `Transform` und liest dabei die `Transform`s ihrer Nachbarn. Mit bloßen `Entity`s müsste sie dieselbe Gegner-Query gleichzeitig lesend und schreibend halten — in Bevy nicht ausleihbar, und die Auswege (`ParamSet`, ein zweiter Puffer-Durchlauf) kosten mehr als ein `Vec2` je Eintrag. Ablage geändert, Entscheidung unberührt. Nachtrag in §1. --- **Offen, gehört nicht hierher**: ‹k›=8, ‹f›=0,5, Separationsradius 14 und Soll-Population 400 sind Startwerte. Sie findet nur Spielen, und der Beleg ist der Blick auf den Turmring — steht so in ADR-0004 und in der Spec unter „Offen".
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RDietermann/InfiniTower#16