fix(showroom): Funde der Schlussdurchsicht auf der Testseite behoben

Die Spielfigur wirft jetzt einen Schatten (player.init durchlief applyCharacter
nie); drei stille Fehlerpfade meckern jetzt statt roh abzubrechen (fehlender
Clip fuer die Fingerhaltung, Waffe ohne Griff, mit neuem Test); die
Waffenauswahl im Panel zeigt Beschriftungen statt IDs; der Waffen-Umbau prueft
jetzt die Meshzahl je Datei; stageTransform ist aus main.js nach lineup.js
gezogen und mit dem im Browser gemessenen Fall getestet; slotPosition nimmt
DUMMIES als Argument statt es aus dem Modul zu lesen; dazu Kommentar- und
Entwurfskorrekturen (Docstring, Beckenspur-Test, Frustum-Culling, Port/Kamera/
VIEW_DISTANCE im Entwurf).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5 committed 2026-09-18 03:19:42 +02:00
1 parent c8f1832eae
commit a0cd95cceb
9 files changed
+97 -18

No files matched your search

+20
View File
@@ -58,9 +58,17 @@ describe("createDummy", () => {
right.name = "hand_r";
const left = new Bone();
left.name = "hand_l";
// Ziele der Fingerspuren aus `fingers` unten (index_01_r, thumb_03_l) — ohne sie meldet three.js beim
// Binden für jeden Testlauf "No target node found for track".
const indexR = new Bone();
indexR.name = "index_01_r";
const thumbL = new Bone();
thumbL.name = "thumb_03_l";
root.add(pelvis);
pelvis.add(right);
pelvis.add(left);
right.add(indexR);
left.add(thumbL);
return root;
}
const clip = new AnimationClip("take", 1, [
@@ -94,8 +102,20 @@ describe("createDummy", () => {
const root = new Group();
const pelvis = new Bone();
pelvis.name = "pelvis";
const indexR = new Bone();
indexR.name = "index_01_r";
const thumbL = new Bone();
thumbL.name = "thumb_03_l";
root.add(pelvis);
pelvis.add(indexR);
pelvis.add(thumbL);
expect(() => createDummy({ template: root, clip, fingers, weapon, hand: "r", grip: WEAPON_GRIP }))
.toThrow(/Dummy ohne Knochen hand_r/);
});
// Bruch: Waffe ohne Griff — applyGrip bekäme kein Objekt und bräche mit einem rohen TypeError ab.
it("meldet eine Waffe ohne Griff", () => {
expect(() => createDummy({ template: template(), clip, fingers, weapon, hand: "r", grip: null }))
.toThrow(/Waffe ohne Griff/);
});
});
+23 -2
View File
@@ -6,7 +6,7 @@ import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
import { getBounds } from "@gltf-transform/functions";
import { MeshoptDecoder } from "meshoptimizer";
import { TEXTURE_SIZE, WEAPON_SOURCES } from "../tools/assets/weapons.mjs";
import { DUMMIES, labelLines, slotPosition, WEAPONS } from "../src/showroom/lineup.js";
import { DUMMIES, labelLines, slotPosition, stageTransform, WEAPONS } from "../src/showroom/lineup.js";
describe.each(WEAPON_SOURCES.map((source) => [source.id, source]))("%s.glb", (id, source) => {
const file = fileURLToPath(new URL(`../public/models/weapons/${id}.glb`, import.meta.url));
@@ -102,7 +102,9 @@ describe("weapons-mocap.glb", () => {
}
});
// Bruch: Drift nicht abgezogen — der Dummy wanderte von seinem Platz.
// Bruch: Drift nicht abgezogen — der Dummy wanderte von seinem Platz. Geprüft werden die lokalen
// Komponenten x und y, weil die Beckenspur im Raum von root liegt (−90° um X) und die Höhe dort auf der
// lokalen z-Achse sitzt, nicht auf y.
it("beginnt jede Beckenbahn waagrecht bei 0", () => {
for (const animation of animations) {
const translation = animation.listChannels().find((channel) => channel.getTargetPath() === "translation");
@@ -187,4 +189,23 @@ describe("Aufstellung", () => {
expect(labelLines(DUMMIES[0])).toEqual(["Pistole nachladen · Take 1", "Pistole"]);
expect(labelLines(DUMMIES.at(-1))).toEqual(["Bogenschütze · Take 1", "ohne Waffe"]);
});
// Bruch: falsches Vorzeichen bei Position oder Drehwinkel — die Aufstellung stünde seitlich versetzt, oder
// die Dummies schauten an der Figur vorbei statt sie anzusehen.
it("stellt die Aufstellung bei Blick nach +Z 8 × Maßstab vor die Figur und dreht sie um 180°", () => {
const { x, z, rotationY } = stageTransform({ x: 0, z: 0 }, { x: 0, z: 1 }, 1);
expect(x).toBeCloseTo(0, 10);
expect(z).toBeCloseTo(8, 10);
// Math.atan2(-0, -1) liefert −π statt π (Vorzeichen der Null) — dieselbe Halbdrehung, deshalb Betrag.
expect(Math.abs(rotationY)).toBeCloseTo(Math.PI, 10);
});
// Im Browser gemessener Fall (task-8-browsercheck.md): Figur bei (0|0), Maßstab 0,4921, Ergebnis
// (−3,94|0) und 90°.
it("stellt die Aufstellung bei Blick nach −X 8 × Maßstab vor die Figur und dreht sie um 90°", () => {
const { x, z, rotationY } = stageTransform({ x: 0, z: 0 }, { x: -1, z: 0 }, 0.4921);
expect(x).toBeCloseTo(-3.94, 2);
expect(z).toBeCloseTo(0, 10);
expect(rotationY).toBeCloseTo(Math.PI / 2, 10);
});
});