fix(world): Chunk-Streaming, Auffangnetz und Schatten folgen dem Fahrzeug

playerCapsule.position ist beim Fahren nicht die Weltposition, sondern der lokale
Sitzplatz-Offset (syncMountedPlayer hängt die Kapsel an vehicleGroup und überschreibt
ihre .position mit driverSeatPosition * scale). main.js trieb Streaming, isReadyAt,
das Auffangnetz, lastSafe und den Schattenwurf trotzdem ausnahmslos damit an — die
Welt hörte beim Fahren auf nachzuladen.

src/player.js bündelt "wo ist der Spieler gerade" in playerPosition()/
playerIsGrounded(): Fahrzeugposition (vehicleGroup.position) und Radkontakt, wenn
eines aktiv ist, sonst Kapsel und das vorhandene getIsOnGround(). Zusätzlich über
getControllerMode() abgesichert, nicht nur getActiveVehicle() — Letzteres wird beim
Aussteigen vom Controller nie zurückgesetzt (nur destroy() tut das), im Browser
nachgewiesen: nach einem erzwungenen Ausstieg lieferte getActiveVehicle() weiterhin
das verlassene Fahrzeug, obwohl controllerMode schon wieder 0 war.

Im Browser mit von Hand angetriebenem tick()/player.update() verifiziert (die
automatisierte Vorschau drosselt requestAnimationFrame auf ~2 Hz): Fahrzeug 227
Einheiten bewegt, playerPosition() folgte exakt, vier neue Chunks luden nach
(Netzwerk-Log bestätigt), sichtbar andere Umgebung nach der Fahrt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-13 05:13:54 +02:00
1 parent e179794967
commit b273bb09cc
3 files changed
+193 -6

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
/**
* Einzige Quelle für "wo ist der Spieler gerade" (Befund A, Fix-Runde 3). main.js trieb
* Streaming, isReadyAt, Auffangnetz, lastSafe und Schattenwurf an sechs Stellen ausnahmslos
* mit player.playerCapsule.position an - das ist falsch, sobald ein Fahrzeug aktiv ist:
* PlayerController.syncMountedPlayer() hängt die Kapsel beim Einsteigen als Kind an
* vehicle.vehicleGroup und überschreibt cap.position mit driverSeatPosition * scale, einer
* festen, kleinen lokalen Zahl nahe dem Ursprung - NICHT der Weltposition. playerCapsule.
* position bleibt also beim Fahren nicht etwa nur stehen, es springt auf einen bedeutungslosen
* Sitzplatz-Offset. Jede Stelle, die "wo ist der Spieler" braucht, muss über playerPosition()
* laufen, sonst hört das Streaming beim Fahren auf nachzuladen.
*/
/**
* getActiveVehicle() allein reicht nicht: VehicleSystem löscht this.active beim Aussteigen
* NICHT (weder exit() noch das stopActive(), das reset() bei aktivem Fahrzeug aufruft, setzen
* es zurück - nur destroy() tut das, beim kompletten Abbau aller Fahrzeuge). getActiveVehicle()
* bleibt also auf dem zuletzt benutzten Fahrzeug stehen, lange nachdem man ausgestiegen ist.
* getControllerMode() === 1 ist das einzige verlässliche "fährt gerade wirklich"-Signal (von
* enter()/exit() korrekt gesetzt) - per Hand im Browser nachgewiesen: nach reset() bei aktivem
* Fahrzeug lieferte getActiveVehicle() weiterhin das verlassene Fahrzeug, obwohl die Kapsel
* schon zurück auf dem Spawnpunkt stand und controllerMode schon wieder 0 war.
*/
function activeVehicleOrNull(player) {
return player.getControllerMode() === 1 ? player.getActiveVehicle() : null;
}
export function playerPosition(player) {
const vehicle = activeVehicleOrNull(player);
return vehicle ? vehicle.vehicleGroup.position : player.playerCapsule.position;
}
/**
* Echtes Bodensignal für lastSafe, fahrzeugbewusst (Ruling 52 gilt auch im Auto). Zu Fuß
* das vorhandene, raycast-gestützte getIsOnGround(). Im Fahrzeug NICHT dasselbe
* getIsOnGround(): controllerMode 1 (Fahrzeug) läuft nie durch CharacterMovement, das
* einzige, was playerIsOnGround setzt - der Wert bliebe beim Fahren auf dem Zustand von
* kurz vor dem Einsteigen eingefroren (nahezu immer "true", man steigt ja stehend ein) und
* würde lastSafe jeden Frame unbesehen überschreiben, auch wenn das Fahrzeug gerade über
* einer Klippe oder einer noch nicht geladenen Lücke schwebt - exakt das Verhalten, das
* Ruling 52 ("an ein echtes Bodensignal binden statt an die Höhe") verhindern sollte.
* Stattdessen derselbe Radkontakt-Test, den VehicleSystem.finishPhysics() intern für
* "hasWheelContact" benutzt: mindestens ein Rad mit Bodenkontakt.
*/
export function playerIsGrounded(player) {
const vehicle = activeVehicleOrNull(player);
if (!vehicle) return player.getIsOnGround();
const wheels = vehicle.vehicleController;
for (let i = 0; i < wheels.numWheels(); i++) {
if (wheels.wheelIsInContact(i)) return true;
}
return false;
}