feat: Figurenauswahl, Autos, Debug-Panel und Namensnennung

Task 11: Figuren (Mannequin/Superhero Male/Female) mit auf ual.glb
retargeteten Animationen, Frisuren, vier SUVs auf angehefteten Chunks,
ein lil-gui-Debug-Panel (Figur/Frisur/Höhe/Hitbox/Chunk-Grenzen/
Kartenbild/Nebel/Wahrzeichen-Sprung) und die verpflichtende
Namensnennung fürs HUD (Kartenkacheln, Wahrzeichen, SUV, Texturen,
Palme, Figuren — jede Quelle mit ihrer eigenen Lizenz).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-13 03:04:49 +02:00
1 parent 02d0f36580
commit d30aae68a1
42 files changed
+13143 -4

No files matched your search

+27 -2
View File
@@ -36,7 +36,7 @@ const emptyChunk = { chunk: [4, 6], origin: [0, -1000], land: [], surfaces: [],
// Zentrum von Chunk 4_6 in three-Koordinaten: SLICE.x0 + 4.5*CHUNK_SIZE, -(SLICE.y1 - 6.5*CHUNK_SIZE).
const emptyChunkCenter = { x: 125, z: -875 };
function makeStreamer(chunkData, props) {
function makeStreamer(chunkData, props, pinned) {
vi.stubGlobal("fetch", vi.fn(async () => ({ ok: true, json: async () => chunkData })));
const player = {
addCollider: vi.fn(() => ({ id: 42 })),
@@ -47,7 +47,7 @@ function makeStreamer(chunkData, props) {
collisionWorld: { get: vi.fn(() => ({ ready: false })) },
};
const scene = { add: vi.fn(), remove: vi.fn() };
return { streamer: new ChunkStreamer({ scene, player, materials: {}, props }), player, scene };
return { streamer: new ChunkStreamer({ scene, player, materials: {}, props, pinned }), player, scene };
}
// #load() ist privat und läuft nebenläufig; zwei Mikrotask-Runden reichen, um
@@ -127,6 +127,31 @@ describe("ChunkStreamer", () => {
expect(ownDispose).toHaveBeenCalledTimes(1);
expect(sharedDispose).not.toHaveBeenCalled();
});
// Task 11, Schritt 5: die vier Fahrzeug-Chunks werden festgehalten, weil ein Fahrzeug
// ohne Boden ins Nichts fällt — #unload muss sie überspringen, statt sie beim
// gewöhnlichen Entfernen-jenseits-des-Radius-Durchlauf trotzdem abzubauen. Mit echter
// Landgeometrie (nicht emptyChunk), damit ein echtes Collider-Handle entsteht und die
// removeCollider-Zusicherung unten etwas prüft, statt nach Ruling 51 trivial wahr zu sein.
it("entlädt einen angehefteten Chunk nicht, auch jenseits des Entladeradius (Task 11)", async () => {
const landChunk = {
chunk: [4, 6], origin: [0, -1000],
land: [[[[0, 0], [50, 0], [50, 50], [0, 50]]]],
surfaces: [], roads: [], buildings: [],
};
const { streamer, player } = makeStreamer(landChunk, undefined, ["4_6"]);
streamer.update(emptyChunkCenter);
await flush();
expect(streamer.active.has("4_6")).toBe(true);
expect(player.addCollider).toHaveBeenCalledTimes(1); // sonst prüft removeCollider unten nichts
// Weit weg -> jenseits des Entladeradius (600); ohne pinned wäre "4_6" nun weg
// (siehe Test oben, gleiches Setup ohne pinned).
streamer.update({ x: emptyChunkCenter.x + 5000, z: emptyChunkCenter.z });
expect(streamer.active.has("4_6")).toBe(true);
expect(player.removeCollider).not.toHaveBeenCalled();
});
});
// Fix-Runde 2, Befund C / Ruling 64: der Zweig `if (this.props)` in #load hatte keine