bug fixes
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 3m42s
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 3m42s
This commit is contained in:
@@ -7,11 +7,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle) {
|
func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle) {
|
||||||
|
// State laden
|
||||||
posY := parseOr(vals["pos_y"], PlayerYBase)
|
posY := parseOr(vals["pos_y"], PlayerYBase)
|
||||||
velY := parseOr(vals["vel_y"], 0.0)
|
velY := parseOr(vals["vel_y"], 0.0)
|
||||||
score := int(parseOr(vals["score"], 0))
|
score := int(parseOr(vals["score"], 0))
|
||||||
rngStateVal, _ := strconv.ParseInt(vals["rng_state"], 10, 64)
|
|
||||||
|
|
||||||
|
// NEU: Wir laden die bisher vergangene Zeit (Ticks)
|
||||||
|
ticksAlive := int(parseOr(vals["total_ticks"], 0))
|
||||||
|
|
||||||
|
rngStateVal, _ := strconv.ParseInt(vals["rng_state"], 10, 64)
|
||||||
godLives := int(parseOr(vals["p_god_lives"], 0))
|
godLives := int(parseOr(vals["p_god_lives"], 0))
|
||||||
hasBat := vals["p_has_bat"] == "1"
|
hasBat := vals["p_has_bat"] == "1"
|
||||||
bootTicks := int(parseOr(vals["p_boot_ticks"], 0))
|
bootTicks := int(parseOr(vals["p_boot_ticks"], 0))
|
||||||
@@ -28,9 +32,14 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
playerDead := false
|
playerDead := false
|
||||||
|
|
||||||
for i := 0; i < totalTicks; i++ {
|
for i := 0; i < totalTicks; i++ {
|
||||||
currentSpeed := BaseSpeed + (float64(score) / 1000)
|
// WICHTIG: Wir erhöhen die Zeit
|
||||||
if currentSpeed > 10.0 {
|
ticksAlive++
|
||||||
currentSpeed = 10.0
|
|
||||||
|
// LOGIK FIX: Geschwindigkeit basiert jetzt auf ZEIT (Ticks), nicht Score!
|
||||||
|
// 3000 Ticks = ca. 50 Sekunden. Da wird es schneller.
|
||||||
|
currentSpeed := BaseSpeed + (float64(ticksAlive)/3000.0)*0.5
|
||||||
|
if currentSpeed > 12.0 {
|
||||||
|
currentSpeed = 12.0
|
||||||
}
|
}
|
||||||
|
|
||||||
currentJumpPower := JumpPower
|
currentJumpPower := JumpPower
|
||||||
@@ -39,6 +48,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
bootTicks--
|
bootTicks--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Input Verarbeitung
|
||||||
didJump := false
|
didJump := false
|
||||||
isCrouching := false
|
isCrouching := false
|
||||||
for _, inp := range inputs {
|
for _, inp := range inputs {
|
||||||
@@ -52,6 +62,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Physik
|
||||||
isGrounded := posY >= PlayerYBase-1.0
|
isGrounded := posY >= PlayerYBase-1.0
|
||||||
currentHeight := PlayerHeight
|
currentHeight := PlayerHeight
|
||||||
if isCrouching {
|
if isCrouching {
|
||||||
@@ -77,6 +88,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
hitboxY = posY + (PlayerHeight - currentHeight)
|
hitboxY = posY + (PlayerHeight - currentHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Obstacles
|
||||||
nextObstacles := []ActiveObstacle{}
|
nextObstacles := []ActiveObstacle{}
|
||||||
rightmostX := 0.0
|
rightmostX := 0.0
|
||||||
|
|
||||||
@@ -87,10 +99,10 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Passed Check (Verhindert Hängenbleiben an "toten" Objekten)
|
||||||
paddingX := 10.0
|
paddingX := 10.0
|
||||||
realRightEdge := obs.X + obs.Width - paddingX
|
if obs.X+obs.Width-paddingX < 55.0 {
|
||||||
|
// Schon vorbei -> Keine Kollision mehr prüfen
|
||||||
if realRightEdge < 55.0 {
|
|
||||||
nextObstacles = append(nextObstacles, obs)
|
nextObstacles = append(nextObstacles, obs)
|
||||||
if obs.X+obs.Width > rightmostX {
|
if obs.X+obs.Width > rightmostX {
|
||||||
rightmostX = obs.X + obs.Width
|
rightmostX = obs.X + obs.Width
|
||||||
@@ -105,16 +117,14 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
|
|
||||||
pLeft, pRight := 50.0+paddingX, 50.0+30.0-paddingX
|
pLeft, pRight := 50.0+paddingX, 50.0+30.0-paddingX
|
||||||
pTop, pBottom := hitboxY+paddingY_Top, hitboxY+currentHeight-5.0
|
pTop, pBottom := hitboxY+paddingY_Top, hitboxY+currentHeight-5.0
|
||||||
|
oLeft, oRight := obs.X+paddingX, obs.X+obs.Width-paddingX
|
||||||
oLeft := obs.X + paddingX
|
|
||||||
oRight := obs.X + obs.Width - paddingX + currentSpeed
|
|
||||||
oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-5.0
|
oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-5.0
|
||||||
|
|
||||||
isCollision := pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom
|
isCollision := pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom
|
||||||
|
|
||||||
if isCollision {
|
if isCollision {
|
||||||
if obs.Type == "coin" {
|
if obs.Type == "coin" {
|
||||||
score += 2000
|
score += 2000 // Score Bonus macht das Spiel nicht mehr kaputt!
|
||||||
continue
|
continue
|
||||||
} else if obs.Type == "powerup" {
|
} else if obs.Type == "powerup" {
|
||||||
if obs.ID == "p_god" {
|
if obs.ID == "p_god" {
|
||||||
@@ -147,17 +157,18 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
}
|
}
|
||||||
obstacles = nextObstacles
|
obstacles = nextObstacles
|
||||||
|
|
||||||
|
// Spawning
|
||||||
if rightmostX < GameWidth-10.0 {
|
if rightmostX < GameWidth-10.0 {
|
||||||
rawGap := 400.0 + rng.NextRange(0, 500)
|
gap := float64(int(400.0 + rng.NextRange(0, 500)))
|
||||||
gap := float64(int(rawGap))
|
|
||||||
spawnX := rightmostX + gap
|
spawnX := rightmostX + gap
|
||||||
if spawnX < GameWidth {
|
if spawnX < GameWidth {
|
||||||
spawnX = GameWidth
|
spawnX = GameWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
isBossPhase := (score % 1500) > 1200
|
// LOGIK FIX: Boss Phase basiert auf ZEIT (Ticks)
|
||||||
var possibleDefs []ObstacleDef
|
isBossPhase := (ticksAlive % 1500) > 1200
|
||||||
|
|
||||||
|
var possibleDefs []ObstacleDef
|
||||||
for _, d := range defaultConfig.Obstacles {
|
for _, d := range defaultConfig.Obstacles {
|
||||||
if isBossPhase {
|
if isBossPhase {
|
||||||
if d.ID == "principal" || d.ID == "trashcan" {
|
if d.ID == "principal" || d.ID == "trashcan" {
|
||||||
@@ -167,7 +178,8 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
if d.ID == "principal" {
|
if d.ID == "principal" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if d.ID == "eraser" && score < 500 {
|
// Eraser kommt ab Tick 3000 (ca. 50 sekunden)
|
||||||
|
if d.ID == "eraser" && ticksAlive < 3000 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
possibleDefs = append(possibleDefs, d)
|
possibleDefs = append(possibleDefs, d)
|
||||||
@@ -175,7 +187,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
}
|
}
|
||||||
|
|
||||||
def := rng.PickDef(possibleDefs)
|
def := rng.PickDef(possibleDefs)
|
||||||
|
|
||||||
if def != nil && def.CanTalk {
|
if def != nil && def.CanTalk {
|
||||||
if rng.NextFloat() > 0.7 {
|
if rng.NextFloat() > 0.7 {
|
||||||
rng.NextFloat()
|
rng.NextFloat()
|
||||||
@@ -186,23 +197,17 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
if def.Type == "powerup" && rng.NextFloat() > 0.1 {
|
if def.Type == "powerup" && rng.NextFloat() > 0.1 {
|
||||||
def = nil
|
def = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if def != nil {
|
if def != nil {
|
||||||
spawnY := GroundY - def.Height - def.YOffset
|
spawnY := GroundY - def.Height - def.YOffset
|
||||||
obstacles = append(obstacles, ActiveObstacle{
|
obstacles = append(obstacles, ActiveObstacle{
|
||||||
ID: def.ID,
|
ID: def.ID, Type: def.Type, X: spawnX, Y: spawnY, Width: def.Width, Height: def.Height,
|
||||||
Type: def.Type,
|
|
||||||
X: spawnX,
|
|
||||||
Y: spawnY,
|
|
||||||
Width: def.Width,
|
|
||||||
Height: def.Height,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !playerDead {
|
if !playerDead {
|
||||||
score++
|
score++ // Basis-Score läuft normal weiter
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -216,6 +221,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
|||||||
|
|
||||||
rdb.HSet(ctx, "session:"+sessionID, map[string]interface{}{
|
rdb.HSet(ctx, "session:"+sessionID, map[string]interface{}{
|
||||||
"score": score,
|
"score": score,
|
||||||
|
"total_ticks": ticksAlive, // NEU: Speichern
|
||||||
"pos_y": fmt.Sprintf("%f", posY),
|
"pos_y": fmt.Sprintf("%f", posY),
|
||||||
"vel_y": fmt.Sprintf("%f", velY),
|
"vel_y": fmt.Sprintf("%f", velY),
|
||||||
"rng_state": rng.State,
|
"rng_state": rng.State,
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ const CHUNK_SIZE = 60;
|
|||||||
const TARGET_FPS = 60;
|
const TARGET_FPS = 60;
|
||||||
const MS_PER_TICK = 1000 / TARGET_FPS;
|
const MS_PER_TICK = 1000 / TARGET_FPS;
|
||||||
|
|
||||||
|
const DEBUG_SYNC = true;
|
||||||
|
const SYNC_TOLERANCE = 5.0;
|
||||||
|
|
||||||
// RNG Klasse
|
// RNG Klasse
|
||||||
class PseudoRNG {
|
class PseudoRNG {
|
||||||
constructor(seed) {
|
constructor(seed) {
|
||||||
|
|||||||
@@ -1,70 +1,84 @@
|
|||||||
function updateGameLogic() {
|
function updateGameLogic() {
|
||||||
// 1. Speed Berechnung (Sync mit Server!)
|
// 1. Input Logging (Ducken)
|
||||||
let currentSpeed = BASE_SPEED + (score / 1000);
|
if (isCrouching) {
|
||||||
if (currentSpeed > 10.0) currentSpeed = 10.0;
|
inputLog.push({ t: currentTick - lastSentTick, act: "DUCK" });
|
||||||
|
|
||||||
|
|
||||||
// 2. Input & Sprung
|
|
||||||
if (isCrouching) inputLog.push({ t: currentTick - lastSentTick, act: "DUCK" });
|
|
||||||
|
|
||||||
const originalHeight = 50; const crouchHeight = 25;
|
|
||||||
player.h = isCrouching ? crouchHeight : originalHeight;
|
|
||||||
let drawY = isCrouching ? player.y + (originalHeight - crouchHeight) : player.y;
|
|
||||||
|
|
||||||
// Jump Power Check
|
|
||||||
let jumpP = JUMP_POWER;
|
|
||||||
if (bootTicks > 0) {
|
|
||||||
jumpP = HIGH_JUMP_POWER;
|
|
||||||
bootTicks--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Physik
|
// 2. Geschwindigkeit (Basiert auf ZEIT/Ticks, nicht Score!)
|
||||||
|
// Formel: Start bei 5, erhöht sich alle 3000 Ticks (ca. 50 Sek) um 0.5
|
||||||
|
let currentSpeed = 5 + (currentTick / 3000.0) * 0.5;
|
||||||
|
if (currentSpeed > 12.0) currentSpeed = 12.0; // Max Speed Cap
|
||||||
|
|
||||||
|
// 3. Spieler Physik & Größe
|
||||||
|
const originalHeight = 50;
|
||||||
|
const crouchHeight = 25;
|
||||||
|
|
||||||
|
player.h = isCrouching ? crouchHeight : originalHeight;
|
||||||
|
|
||||||
|
// Visuelle Korrektur Y (damit Füße am Boden bleiben)
|
||||||
|
let drawY = isCrouching ? player.y + (originalHeight - crouchHeight) : player.y;
|
||||||
|
|
||||||
|
// Schwerkraft
|
||||||
player.vy += GRAVITY;
|
player.vy += GRAVITY;
|
||||||
if (isCrouching && !player.grounded) player.vy += 2.0;
|
if (isCrouching && !player.grounded) player.vy += 2.0; // Schneller fallen (Fast Fall)
|
||||||
|
|
||||||
player.y += player.vy;
|
player.y += player.vy;
|
||||||
|
|
||||||
|
// Boden-Kollision
|
||||||
if (player.y + originalHeight >= GROUND_Y) {
|
if (player.y + originalHeight >= GROUND_Y) {
|
||||||
player.y = GROUND_Y - originalHeight; player.vy = 0; player.grounded = true;
|
player.y = GROUND_Y - originalHeight;
|
||||||
} else { player.grounded = false; }
|
player.vy = 0;
|
||||||
|
player.grounded = true;
|
||||||
|
} else {
|
||||||
|
player.grounded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Hindernisse Bewegen & Kollision
|
||||||
// 3. Obstacles
|
|
||||||
let nextObstacles = [];
|
let nextObstacles = [];
|
||||||
let rightmostX = 0;
|
let rightmostX = 0;
|
||||||
|
|
||||||
for (let obs of obstacles) {
|
for (let obs of obstacles) {
|
||||||
obs.x -= currentSpeed;
|
obs.x -= currentSpeed;
|
||||||
|
|
||||||
|
// Hitbox für aktuellen Frame
|
||||||
const playerHitbox = { x: player.x, y: drawY, w: player.w, h: player.h };
|
const playerHitbox = { x: player.x, y: drawY, w: player.w, h: player.h };
|
||||||
|
|
||||||
if (checkCollision(playerHitbox, obs)) {
|
if (checkCollision(playerHitbox, obs)) {
|
||||||
// TYPE CHECK
|
// A. MÜNZE (+2000 Punkte)
|
||||||
if (obs.def.type === "coin") {
|
if (obs.def.type === "coin") {
|
||||||
score += 2000;
|
score += 2000;
|
||||||
continue;
|
continue; // Entfernen (Eingesammelt)
|
||||||
}
|
}
|
||||||
|
// B. POWERUP (Aktivieren)
|
||||||
else if (obs.def.type === "powerup") {
|
else if (obs.def.type === "powerup") {
|
||||||
if (obs.def.id === "p_god") godModeLives = 3;
|
if (obs.def.id === "p_god") godModeLives = 3;
|
||||||
if (obs.def.id === "p_bat") hasBat = true;
|
if (obs.def.id === "p_bat") hasBat = true;
|
||||||
if (obs.def.id === "p_boot") bootTicks = 600;
|
if (obs.def.id === "p_boot") bootTicks = 600; // ca. 10 Sekunden
|
||||||
continue;
|
continue; // Entfernen (Eingesammelt)
|
||||||
}
|
}
|
||||||
|
// C. GEGNER / HINDERNIS
|
||||||
else {
|
else {
|
||||||
// HINDERNIS
|
// Schläger vs Lehrer
|
||||||
if (hasBat && obs.def.type === "teacher") {
|
if (hasBat && obs.def.type === "teacher") {
|
||||||
hasBat = false;
|
hasBat = false; // Schläger kaputt
|
||||||
continue; // Zerstört!
|
continue; // Lehrer weg
|
||||||
}
|
}
|
||||||
|
// Godmode vs Alles
|
||||||
if (godModeLives > 0) {
|
if (godModeLives > 0) {
|
||||||
godModeLives--;
|
godModeLives--; // Ein Leben weg
|
||||||
continue; // Überlebt!
|
continue; // Hindernis ignoriert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tod
|
||||||
player.color = "darkred";
|
player.color = "darkred";
|
||||||
if (!isGameOver) { sendChunk(); gameOver("Kollision"); }
|
if (!isGameOver) {
|
||||||
|
sendChunk();
|
||||||
|
gameOver("Kollision");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Objekt behalten wenn noch im Bild
|
||||||
if (obs.x + obs.def.width > -100) {
|
if (obs.x + obs.def.width > -100) {
|
||||||
nextObstacles.push(obs);
|
nextObstacles.push(obs);
|
||||||
if (obs.x + obs.def.width > rightmostX) rightmostX = obs.x + obs.def.width;
|
if (obs.x + obs.def.width > rightmostX) rightmostX = obs.x + obs.def.width;
|
||||||
@@ -72,42 +86,52 @@ function updateGameLogic() {
|
|||||||
}
|
}
|
||||||
obstacles = nextObstacles;
|
obstacles = nextObstacles;
|
||||||
|
|
||||||
// 4. Spawning (Sync mit Go!)
|
// 5. Spawning (Basiert auf ZEIT/Ticks)
|
||||||
if (rightmostX < GAME_WIDTH - 10 && gameConfig) {
|
if (rightmostX < GAME_WIDTH - 10 && gameConfig) {
|
||||||
const gap = Math.floor(400 + rng.nextRange(0, 500));
|
const gap = Math.floor(400 + rng.nextRange(0, 500));
|
||||||
let spawnX = rightmostX + gap; if (spawnX < GAME_WIDTH) spawnX = GAME_WIDTH;
|
let spawnX = rightmostX + gap;
|
||||||
|
if (spawnX < GAME_WIDTH) spawnX = GAME_WIDTH;
|
||||||
|
|
||||||
const isBossPhase = (score % 1500) > 1200;
|
// Boss Phase abhängig von Zeit (nicht Score, wegen Münzen!)
|
||||||
|
const isBossPhase = (currentTick % 1500) > 1200;
|
||||||
|
|
||||||
let possibleObs = [];
|
let possibleObs = [];
|
||||||
gameConfig.obstacles.forEach(def => {
|
gameConfig.obstacles.forEach(def => {
|
||||||
if (isBossPhase) {
|
if (isBossPhase) {
|
||||||
|
// Boss Phase: Nur Principal und Trashcan
|
||||||
if (def.id === "principal" || def.id === "trashcan") possibleObs.push(def);
|
if (def.id === "principal" || def.id === "trashcan") possibleObs.push(def);
|
||||||
} else {
|
} else {
|
||||||
|
// Normal Phase
|
||||||
if (def.id === "principal") return;
|
if (def.id === "principal") return;
|
||||||
if (def.id === "eraser" && score < 500) return;
|
// Eraser erst ab Tick 3000 (ca. 50 Sek)
|
||||||
|
if (def.id === "eraser" && currentTick < 3000) return;
|
||||||
possibleObs.push(def);
|
possibleObs.push(def);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Zufälliges Objekt wählen
|
||||||
let def = rng.pick(possibleObs);
|
let def = rng.pick(possibleObs);
|
||||||
|
|
||||||
// Speech Sync
|
// RNG Sync: Sprechblasen
|
||||||
let speech = null;
|
let speech = null;
|
||||||
if (def && def.canTalk) {
|
if (def && def.canTalk) {
|
||||||
|
// WICHTIG: Reihenfolge muss 1:1 wie in Go sein
|
||||||
if (rng.nextFloat() > 0.7) speech = rng.pick(def.speechLines);
|
if (rng.nextFloat() > 0.7) speech = rng.pick(def.speechLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Powerup Rarity Sync (Muss exakt wie Go sein: 10% Chance)
|
// RNG Sync: Powerup Seltenheit (nur 10% Chance)
|
||||||
if (def && def.type === "powerup") {
|
if (def && def.type === "powerup") {
|
||||||
if (rng.nextFloat() > 0.1) def = null;
|
if (rng.nextFloat() > 0.1) def = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hinzufügen
|
||||||
if (def) {
|
if (def) {
|
||||||
const yOffset = def.yOffset || 0;
|
const yOffset = def.yOffset || 0;
|
||||||
obstacles.push({
|
obstacles.push({
|
||||||
x: spawnX, y: GROUND_Y - def.height - yOffset,
|
x: spawnX,
|
||||||
def: def, speech: speech
|
y: GROUND_Y - def.height - yOffset,
|
||||||
|
def: def,
|
||||||
|
speech: speech
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ async function sendChunk() {
|
|||||||
const ticksToSend = currentTick - lastSentTick;
|
const ticksToSend = currentTick - lastSentTick;
|
||||||
if (ticksToSend <= 0) return;
|
if (ticksToSend <= 0) return;
|
||||||
|
|
||||||
|
|
||||||
|
const snapshotobstacles = JSON.parse(JSON.stringify(obstacles));
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
sessionId: sessionID,
|
sessionId: sessionID,
|
||||||
inputs: [...inputLog],
|
inputs: [...inputLog],
|
||||||
@@ -20,15 +23,29 @@ async function sendChunk() {
|
|||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.serverObs) serverObstacles = data.serverObs;
|
// Update für visuelles Debugging
|
||||||
|
if (data.serverObs) {
|
||||||
|
serverObstacles = data.serverObs;
|
||||||
|
|
||||||
|
// --- NEU: DEBUG MODUS VERGLEICH ---
|
||||||
|
if (typeof DEBUG_SYNC !== 'undefined' && DEBUG_SYNC) {
|
||||||
|
compareState(snapshotobstacles, data.serverObs);
|
||||||
|
}
|
||||||
|
// ----------------------------------
|
||||||
|
}
|
||||||
|
|
||||||
if (data.status === "dead") {
|
if (data.status === "dead") {
|
||||||
console.error("SERVER TOT", data);
|
console.error("💀 SERVER KILL", data);
|
||||||
gameOver("Vom Server gestoppt");
|
gameOver("Vom Server gestoppt");
|
||||||
} else {
|
} else {
|
||||||
const sScore = data.verifiedScore;
|
const sScore = data.verifiedScore;
|
||||||
if (Math.abs(score - sScore) > 200) score = sScore;
|
// Score Korrektur
|
||||||
|
if (Math.abs(score - sScore) > 200) {
|
||||||
|
console.warn(`⚠️ SCORE DRIFT: Client=${score} Server=${sScore}`);
|
||||||
|
score = sScore;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Netzwerkfehler:", e);
|
console.error("Netzwerkfehler:", e);
|
||||||
}
|
}
|
||||||
@@ -89,4 +106,76 @@ async function loadStartScreenLeaderboard() {
|
|||||||
});
|
});
|
||||||
listEl.innerHTML = html;
|
listEl.innerHTML = html;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareState(clientObs, serverObs) {
|
||||||
|
// 1. Anzahl prüfen
|
||||||
|
if (clientObs.length !== serverObs.length) {
|
||||||
|
console.error(`🚨 ANZAHL MISMATCH! Client: ${clientObs.length}, Server: ${serverObs.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const report = [];
|
||||||
|
const maxLen = Math.max(clientObs.length, serverObs.length);
|
||||||
|
let hasMajorDrift = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < maxLen; i++) {
|
||||||
|
const cli = clientObs[i];
|
||||||
|
const srv = serverObs[i];
|
||||||
|
|
||||||
|
let drift = 0;
|
||||||
|
let status = "✅ OK";
|
||||||
|
|
||||||
|
// Client Objekt vorbereiten
|
||||||
|
let cID = "---";
|
||||||
|
let cX = 0;
|
||||||
|
if (cli) {
|
||||||
|
cID = cli.def.id; // Struktur beachten: cli.def.id
|
||||||
|
cX = cli.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server Objekt vorbereiten
|
||||||
|
let sID = "---";
|
||||||
|
let sX = 0;
|
||||||
|
if (srv) {
|
||||||
|
sID = srv.id; // Struktur vom Server: srv.id
|
||||||
|
sX = srv.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vergleich
|
||||||
|
if (cli && srv) {
|
||||||
|
// IDs unterschiedlich? (z.B. Tisch vs Lehrer)
|
||||||
|
if (cID !== sID) {
|
||||||
|
status = "❌ ID ERROR";
|
||||||
|
hasMajorDrift = true;
|
||||||
|
} else {
|
||||||
|
drift = cX - sX;
|
||||||
|
if (Math.abs(drift) > SYNC_TOLERANCE) {
|
||||||
|
status = "⚠️ DRIFT";
|
||||||
|
hasMajorDrift = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status = "❌ MISSING";
|
||||||
|
hasMajorDrift = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In Tabelle eintragen
|
||||||
|
report.push({
|
||||||
|
Index: i,
|
||||||
|
Status: status,
|
||||||
|
"C-ID": cID,
|
||||||
|
"S-ID": sID,
|
||||||
|
"C-Pos": cX.toFixed(1),
|
||||||
|
"S-Pos": sX.toFixed(1),
|
||||||
|
"Drift (px)": drift.toFixed(2)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nur loggen, wenn Fehler da sind oder alle 5 Sekunden (Tick 300)
|
||||||
|
if (hasMajorDrift || currentTick % 300 === 0) {
|
||||||
|
if (hasMajorDrift) console.warn("--- SYNC PROBLEME GEFUNDEN ---");
|
||||||
|
else console.log("--- Sync Check (Routine) ---");
|
||||||
|
|
||||||
|
console.table(report); // Das erstellt eine super lesbare Tabelle im Browser
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user