feat(world): Chunk-Streaming mit Collider-Verwaltung und Auffangnetz

wantedChunks() liefert Lade-/Entladelisten mit Hysterese (Laderadius 400,
Entladeradius 600) um die Spielerposition; ChunkStreamer holt Chunks per
fetch, baut sie über buildChunkGeometry() (Task 7) zu Meshes, registriert
die Kollisionsschicht (ground/bridges/buildings) beim Player-Controller
mit asynchronem BVH-Bau und meldet über isReadyAt() wann ein Collider
tatsächlich einsatzbereit ist. Straßen/Sand/Park/Stadtfläche bleiben reine
Auflagen ohne eigene Kollision.

main.js schwebt die Figur kollisionsfrei am Spawn (Vice Beach), bis ihr
Chunk bereit ist oder 15s vergangen sind, und fängt jeden tieferen
Durchfall über ein Auffangnetz ab, das zur letzten sicheren Höhe
zurücksetzt. Der alte Platzhalter-Boden (400x400-Ebene samt Collider)
entfällt zugunsten der echten gestreamten Welt.

Der zweite Testfall aus dem Task-Brief hatte einen Datenfehler: die
"near"-Position {x:1375,z:-1375} traf exakt das Zentrum von Chunk 9_4
(Distanz zu Chunk 5_5 ca. 1030, weit jenseits des Entladeradius 600) und
ließ die eigene Behauptung "not.toContain" fehlschlagen, sobald
streaming.js existierte (RED belegt im Task-8-Report). Position auf
{x:875,z:-1125} korrigiert, exakt 500 Einheiten von Chunk 5_5 entfernt —
außerhalb von 400, innerhalb von 600 — und damit die Hysterese, die der
Testname behauptet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-12 20:18:54 +02:00
1 parent 962bfef778
commit f7041dab91
3 files changed
+187 -9

No files matched your search

+29
View File
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { wantedChunks } from "../src/world/streaming.js";
describe("wantedChunks", () => {
it("lädt die Umgebung des Spielers", () => {
const { add } = wantedChunks({ x: 0, z: -1000 }, { load: 400, unload: 600, active: new Set() });
expect(add.length).toBeGreaterThan(4);
expect(add.length).toBeLessThan(30);
});
it("entlädt erst jenseits des größeren Radius", () => {
// Chunk 5_5 hat sein Zentrum bei (375, -1125) (SLICE.x0 + 5.5*CHUNK_SIZE, ...).
// "near" liegt hier bewusst 500 Einheiten entfernt: außerhalb des Laderadius (400),
// aber innerhalb des Entladeradius (600) — genau die Hysterese, die der Testname
// behauptet. Der ursprüngliche Brief-Wert {x:1375, z:-1375} traf exakt das Zentrum
// von Chunk 9_4 (Distanz zu 5_5: ~1030, weit jenseits von 600) und ließ die eigene
// Behauptung "not.toContain" fehlschlagen — siehe Task-8-Report für den RED-Beleg.
const active = new Set(["5_5"]);
const near = wantedChunks({ x: 875, z: -1125 }, { load: 400, unload: 600, active });
expect(near.remove).not.toContain("5_5");
const far = wantedChunks({ x: -900, z: -2400 }, { load: 400, unload: 600, active });
expect(far.remove).toContain("5_5");
});
it("meldet nichts doppelt an", () => {
const active = new Set(wantedChunks({ x: 0, z: -1000 }, { load: 400, unload: 600, active: new Set() }).add);
expect(wantedChunks({ x: 0, z: -1000 }, { load: 400, unload: 600, active }).add).toHaveLength(0);
});
});