docs: correct the rebuild-cost yardstick in ADR-0004 and the spec
Measured while building #16: at 10 000 enemies the grid rebuild costs 0.057 ms against 0.021 ms for move_enemies — 2.7x more, not less. ADR-0004 §3 counted arithmetic and missed what dominates: move_enemies is a linear table walk, the rebuild writes into ~7000 scattered buckets. The decision does not change. It never rested on the comparison but on the absolute bound of 0.10 ms, and 0.057 ms is 0.34 % of the frame budget; an incremental update would pay the same scattered write on every cell crossing. Revision trigger 1 and the acceptance criterion of spec feature 3 are restated on that bound, which is what the runnable check now asserts. Also records the second deviation the build produced: a cell holds (Entity, Vec2) rather than Entity, because soft separation writes Transform while reading its neighbours' positions.
This commit is contained in:
@@ -68,6 +68,13 @@ System, das einen Durchlauf über dieselbe Gegnermenge macht, kostet höchstens
|
||||
sein) und der Turm steht fest in der Mitte — es gibt keinen Grund für eine Hashmap. Der Zellindex
|
||||
ist zwei Divisionen und zwei `floor`. Gegner außerhalb des Gitters werden in die Randzelle geklemmt.
|
||||
|
||||
**Nachtrag vom 2026-08-11 (#16): je Zelle ein `Vec<(Entity, Vec2)>`, nicht `Vec<Entity>`.** Die
|
||||
Position wird neben der `Entity` mitgeführt. Der Grund ist die weiche 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 das eine `Vec2` je Bucket-Eintrag. Die
|
||||
Entscheidung ändert sich dadurch nicht; die Ablage tut es.
|
||||
|
||||
Das Grid ist **kein** `Component` an der Turm-Entity: Es beschreibt nicht den Turm, sondern die
|
||||
Welt, und es überlebt keinen Run-Wechsel als Zustand — es wird ohnehin jeden Frame neu gebaut.
|
||||
Damit bleibt die Ablageregel aus ADR-0002 §6 unberührt.
|
||||
@@ -118,6 +125,38 @@ schiebt eine `Entity` in einen `Vec`.
|
||||
|
||||
Die 10 ns sind eine großzügige Obergrenze, weil sie den teureren Nachbarn als Maßstab nimmt.
|
||||
|
||||
#### Nachtrag vom 2026-08-11 (nachgemessen in #16): der Maßstab war falsch, die Schranke hält
|
||||
|
||||
Die Rechnung oben ist beim Bau des Grids nachgemessen worden — Release-Profil, `HARD_ENEMY_CAP`
|
||||
Gegner, Median über 20 Läufe je System (`the_rebuild_stays_cheaper_than_move_enemies_at_the_hard_cap`
|
||||
in `src/spatial.rs`):
|
||||
|
||||
| System | gemessen bei 10 000 Gegnern |
|
||||
|---|---|
|
||||
| `move_enemies` | **0,021 ms** |
|
||||
| `rebuild_spatial_grid` | **0,057 ms** |
|
||||
|
||||
**Der Rebuild ist rund 2,7× teurer als `move_enemies`, nicht billiger.** Der Absatz oben zählt
|
||||
Arithmetik — zwei Divisionen und zwei `floor` gegen eine Normalisierung mit Wurzel — und übersieht
|
||||
die Kosten, die hier dominieren: `move_enemies` ist ein **linearer** Lauf über eine Tabelle, der
|
||||
Rebuild schreibt in ~7 000 verstreute `Vec`s und zahlt Cache-Misses, die keine FLOP-Zählung sieht.
|
||||
Der `Vec`-Push, den der Absatz als „Rest der Ersparnis" abtut, **ist** der Kostenpunkt.
|
||||
|
||||
Was das ändert und was nicht:
|
||||
|
||||
- **Die Entscheidung bleibt.** Sie stand nie auf dem Vergleich, sondern auf der absoluten Schranke:
|
||||
0,057 ms sind **0,34 % des Frame-Budgets** und liegen unter den hier veranschlagten ≤0,10 ms. Die
|
||||
Logik gesamt bleibt bei ≤0,18 ms.
|
||||
- **Das inkrementelle Update wird dadurch nicht attraktiver.** Es würde denselben verstreuten
|
||||
Schreibzugriff bezahlen, sobald ein Gegner die Zelle wechselt — und bei radialer Konvergenz
|
||||
wechseln ständig welche —, plus Rückindex und Entfernen aus der Mitte eines `Vec`. Der Preis, der
|
||||
hier wehtut, ist genau der, den es nicht einspart.
|
||||
- **Der Maßstab „billiger als `move_enemies`" ist gestrichen.** Er war eine Bequemlichkeit, kein
|
||||
Argument. Revisions-Auslöser 1 unten ist entsprechend auf die absolute Schranke umgestellt.
|
||||
|
||||
Zu messen ist erneut nach dem Wechsel auf echte Texturen und mit dem HUD — dieselbe Auflage, unter
|
||||
der die Zahlen aus #4 stehen.
|
||||
|
||||
**Was ein inkrementelles Update dagegen einspart, ist nach oben durch dieselben ≤0,10 ms begrenzt —
|
||||
und es spart nicht einmal die, denn den Zellindex muss es genauso je Gegner ausrechnen, um einen
|
||||
Zellwechsel überhaupt zu erkennen.** Der Rest der Ersparnis ist der `Vec`-Push. Dagegen steht: ein
|
||||
@@ -280,10 +319,13 @@ und die Auslöse-Bedingungen, wo sie sichtbarer wäre.
|
||||
|
||||
Diese Entscheidung wird neu getroffen, sobald **eines** der folgenden Ereignisse eintritt:
|
||||
|
||||
1. **Der Rebuild ist nicht mehr der billigere Durchlauf.** Eine Messung mit dem Aufbau aus
|
||||
`examples/perf_spike.rs` bei `HARD_ENEMY_CAP` zeigt `rebuild_spatial_grid` teurer als
|
||||
`move_enemies` im selben Frame. Die Entscheidung gegen das inkrementelle Update steht und fällt
|
||||
mit dieser Annahme; ist sie widerlegt, wird sie neu getroffen — nicht vorher.
|
||||
1. **Der Rebuild sprengt seine absolute Schranke.** Eine Messung bei `HARD_ENEMY_CAP` zeigt
|
||||
`rebuild_spatial_grid` über **0,10 ms**. Die Entscheidung gegen das inkrementelle Update steht und
|
||||
fällt mit dieser Schranke; ist sie gerissen, wird sie neu getroffen — nicht vorher.
|
||||
*Fassung bis 2026-08-11: „teurer als `move_enemies` im selben Frame". Dieser Maßstab ist in #16
|
||||
widerlegt worden (Nachtrag zu §3) — der Rebuild war von Anfang an teurer, ohne dass die
|
||||
Entscheidung dadurch fällig wäre. Er ist durch die Schranke ersetzt, auf der das Argument
|
||||
tatsächlich steht.*
|
||||
2. **Radien-Spreizung.** Ein Konsument braucht einen Abfrageradius kleiner als eine halbe Zelle oder
|
||||
größer als ‹20› Zellkanten. Dann trägt **eine** Auflösung nicht mehr für alle Abfragen, und zwei
|
||||
Auflösungen oder eine hierarchische Struktur sind zu prüfen.
|
||||
|
||||
@@ -141,9 +141,11 @@ Auslöse-System und die Projektil-Kollision bedient.
|
||||
Sprung-zu-Welle auf die höchste im Debug-Panel erreichbare Welle.
|
||||
- Bei 10 000 gleichzeitigen Gegnern bleibt die Frametime unter 16,67 ms (Nachmessung mit dem Aufbau
|
||||
aus `examples/perf_spike.rs`, nach dem Wechsel auf echte Sprites zu wiederholen).
|
||||
- Der Rebuild des `SpatialGrid` kostet bei 10 000 Gegnern **nicht mehr als `move_enemies` im selben
|
||||
Frame** (ADR-0004 §3 — die Entscheidung gegen ein inkrementelles Update steht auf dieser Annahme;
|
||||
reißt sie, ist die ADR fällig, nicht die Zahl).
|
||||
- Der Rebuild des `SpatialGrid` kostet bei 10 000 Gegnern **höchstens 0,10 ms** (ADR-0004 §3 — die
|
||||
Entscheidung gegen ein inkrementelles Update steht auf dieser Schranke; reißt sie, ist die ADR
|
||||
fällig, nicht die Zahl). *Bis 2026-08-11 stand hier „nicht mehr als `move_enemies` im selben
|
||||
Frame"; die Messung in #16 hat diesen Maßstab widerlegt — gemessen 0,057 ms gegen 0,021 ms —, ohne
|
||||
die Schranke zu reißen. Siehe den Nachtrag zu ADR-0004 §3.*
|
||||
- Kein Gegner bewegt sich in einem Frame vom Turm **weg**, solange ihn keine Fähigkeit dorthin
|
||||
drückt — die Separationsklemmung macht das konstruktiv unmöglich, und der Turmring pulsiert
|
||||
sichtbar nicht.
|
||||
|
||||
Reference in New Issue
Block a user