change game to be server host on mg 2 3 4 5 6 7

This commit is contained in:
2026-06-28 05:20:00 +00:00
parent d599c5c72d
commit 6bc2ccea19
12 changed files with 51713 additions and 86 deletions
+13 -13
View File
@@ -51,9 +51,9 @@
"providerUserId": "p_1775109142385_wq7wfy1p32j",
"notes": "auto: player-coins",
"blocked": false,
"coins": 2322,
"coins": 2382,
"createdAt": "2026-04-02T05:52:21+00:00",
"updatedAt": "2026-06-27T11:14:02+00:00",
"updatedAt": "2026-06-27T18:23:36+00:00",
"daily": {
"anchorMs": 1781197200000,
"claimedDays": [
@@ -67,10 +67,10 @@
],
"lockUntilMs": 0
},
"score": 248,
"score": 308,
"scoreByCase": {
"1": 26,
"10": 616,
"10": 676,
"8": 296,
"11": 20,
"13": 324,
@@ -459,9 +459,9 @@
"providerUserId": "p_1781851275186_szcmp5pu3s",
"notes": "auto: player-coins",
"blocked": false,
"coins": 212,
"coins": 242,
"createdAt": "2026-06-19T06:41:15+00:00",
"updatedAt": "2026-06-24T05:16:27+00:00",
"updatedAt": "2026-06-27T19:28:03+00:00",
"daily": {
"anchorMs": 1781802000000,
"claimedDays": [
@@ -475,9 +475,9 @@
],
"lockUntilMs": 0
},
"score": 192,
"score": 222,
"scoreByCase": {
"10": 28,
"10": 58,
"13": 36,
"12": 82,
"4": 28,
@@ -485,7 +485,7 @@
},
"lbName": "MONE",
"achievements": {
"d1_minigame_solver": 19,
"d1_minigame_solver": 20,
"a1_first_deduction": 1,
"a5_truth_hunter": 1
}
@@ -663,15 +663,15 @@
"providerUserId": "p_1782016990238_0u37bv7pxlxs",
"notes": "auto: player-coins",
"blocked": false,
"coins": 572,
"coins": 592,
"createdAt": "2026-06-21T04:43:11+00:00",
"updatedAt": "2026-06-27T11:14:02+00:00",
"updatedAt": "2026-06-27T18:51:26+00:00",
"lobbyColorThemeIndex": 5,
"lobbySkinToneIndex": 1,
"score": 532,
"score": 552,
"scoreByCase": {
"13": 182,
"10": 248,
"10": 268,
"6": 8,
"8": 20,
"2": 46,
+12 -7
View File
@@ -1,10 +1,15 @@
{
"gauntletTickMs": 220,
"gauntletJumpTicks": 16,
"gauntletJumpTicks": 6,
"gauntletTimeLimitSec": 0,
"gauntletLaneImageUrls": [],
"gauntletLaserTopUrl": "",
"gauntletLaserBottomUrl": "",
"gauntletLaneImageUrls": [
"/Game/img/gauntlet-assets/gauntlet-077c293962b8fe07.png",
"/Game/img/gauntlet-assets/gauntlet-67a585790f67fe67.png",
"/Game/img/gauntlet-assets/gauntlet-c4d4482e2a60eac9.png",
"/Game/img/gauntlet-assets/gauntlet-6aa244ab734b12e4.png"
],
"gauntletLaserTopUrl": "/Game/img/gauntlet-assets/gauntlet-dd51baed17270995.png",
"gauntletLaserBottomUrl": "/Game/img/gauntlet-assets/gauntlet-dd51baed17270995.png",
"gauntletLaserLineUrl": "",
"gauntletLaserFillColor": "rgba(140,230,255,0.42)",
"gauntletLaserStrokeColor": "rgba(255,220,255,0.9)",
@@ -90,15 +95,15 @@
"balloonBossPlayerBalloonFallbackUrl": "/Game/img/MegaVirus/Artboard%209.png",
"balloonBossBalloonsPerPlayer": 3,
"forcedMinigameKeys": [
"quiz",
"gauntlet",
"balloon_boss"
"space_shooter",
"stack"
],
"testSpecialCardByMap": {},
"troublesomeForceOffer": false,
"specialQuizIconExpireSec": 20,
"pickVoteSec": 25,
"trialVoteSec": 30,
"lobbyDebugHud": true,
"lobbyDebugHud": false,
"lobbyDebugHudNames": []
}
+10 -3
View File
@@ -1975,7 +1975,14 @@ body.room-lobby--quiz-active .lobby-b-extra-row {
font-size: 0.8rem;
}
/* Stack Tower play — กัน #game-canvas ถูกกฎอื่นเป็น contain แล้วแถบดำรอบแคนวาส */
html.play-stack-tower-pixel-canvas #play-canvas-stage #game-canvas {
object-fit: cover !important;
/* Stack Tower play — เฟรมกล้องต้อง "คงที่ทุก resolution"
cover สเกลตามด้านที่ใหญ่กว่า → จอกว้าง/เตี้ย ต่างกัน = เฟรม/ซูมเปลี่ยน (กล้องเพี้ยนเวลาเปลี่ยน resolution + บล็อกห้อย/จุดวางหลุดเฟรม)
contain = เห็นทั้งฉาก 16:9 (บล็อกห้อย+จุดวาง+หอ) เหมือนกันเป๊ะทุกจอ; แถบ letterbox กลืนสีฉาก (#0a0e22) ไม่ดูจอเสีย */
html.play-stack-tower-pixel-canvas #play-canvas-stage.play-canvas-stage {
background: #0a0e22 !important;
}
html.play-stack-tower-pixel-canvas #play-canvas-stage #game-canvas {
object-fit: contain !important;
object-position: center center !important;
background: #0a0e22 !important;
}
+507 -36
View File
@@ -321,7 +321,19 @@
(เดิมทุกเครื่องคิดชนให้ทุกยานด้วยตำแหน่งดาวเคราะห์ local ที่ต่างกัน → ตัดสินไม่ตรงกัน) */
others.forEach(function (o, id) {
if (!o) return;
if (isPreviewBotId(id) && isMePlayHost()) tryHit(o, o.spaceShooterCx, o.spaceShooterCy);
if (!(isPreviewBotId(id) && isMePlayHost())) return;
if (spaceShooterServerAuthActive()) {
/* server-auth (MG6 เกมจริง): host คิดการชนบอท (asteroid deterministic + ตำแหน่งบอทจาก server)
แล้ว "รายงาน" จำนวนชนให้ server ตัดสินตายจริง — ไม่ตัดสินตายเอง local (กันสถานะตายขัดกัน) */
const h0 = Math.max(0, Number(o.spaceShooterHits) || 0);
tryHit(o, o.spaceShooterCx, o.spaceShooterCy);
const h1 = Math.max(0, Number(o.spaceShooterHits) || 0);
if (h1 > h0 && socket) {
try { socket.emit('space-shooter-bot-hit', { id, hits: h1 }); } catch (e) { /* ignore */ }
}
} else {
tryHit(o, o.spaceShooterCx, o.spaceShooterCy);
}
});
/* เพิ่งตาย → broadcast ทันที ไม่รอ throttle 90ms ให้ทุกเครื่องเห็นพร้อมกัน */
if (!wasElim && me.spaceShooterEliminated && socket && myId != null) {
@@ -679,6 +691,9 @@
let stackPreviewTurnOrder = null;
let stackPreviewTurnIndex = 0;
let stackPreviewBotThinkUntil = 0;
/* MG3 server-authoritative: เปิดเมื่อได้รับ stack-state ที่ srv:true → client เลิก host-sim
(ลำดับตา/แกว่ง/ดรอปบอท/จบเกม = server เป็นเจ้าของ; ผู้เล่นส่งเจตนา stack-drop-req) */
let stackServerAuthPlay = false;
/** ลำดับตา MG3 = ผู้เล่นจริงทุกคน + บอท เรียงตาม id แบบคงที่ → ทุกเครื่องได้ลำดับ/ที่นั่งเดียวกัน (shared tower)
(เดิม [human=ตัวเอง seat1 + bots] ต่อเครื่อง → แต่ละเครื่องมองตัวเองเป็นคนเดียว เลยแยกตึกกัน) */
@@ -687,6 +702,7 @@
stackPreviewTurnOrder = null;
return;
}
if (stackServerAuthPlay) return; /* server-auth: ลำดับตามาจาก stack-state เท่านั้น */
if (!detectiveCaseFillBots) {
/* editor preview (คนเดียว): คงพฤติกรรมเดิม [human ตัวเอง seat1 + บอท] */
const bots = [...others.keys()].filter(isPreviewBotId).sort();
@@ -738,6 +754,7 @@
/** host broadcast ลำดับตา + index ปัจจุบัน → non-host ใช้ตรงกัน (single source of truth) */
function broadcastStackStatePlay() {
if (stackServerAuthPlay) return; /* server-auth: server เป็นเจ้าของ stack-state */
if (!detectiveCaseFillBots || !isMePlayHost() || !socket || myId == null || !stackTurnOrderReadyPlay()) return;
try {
socket.emit('stack-state', {
@@ -812,6 +829,7 @@
function advanceStackPreviewTurn() {
if (!playBotsEnabled() || !stackTurnOrderReadyPlay()) return;
if (stackServerAuthPlay) return; /* server-auth: idx มาจาก server (stack-state) เท่านั้น — กัน double-advance */
/* เล่นจริง: index คุมโดย host เท่านั้น (non-host รับจาก stack-state) กันลำดับตาเหลื่อมกัน */
if (detectiveCaseFillBots && !isMePlayHost()) return;
stackPreviewTurnIndex = (stackPreviewTurnIndex + 1) % stackPreviewTurnOrder.length;
@@ -1572,6 +1590,9 @@
gauntletCrownRunwaySnapScrollToFinishStopPlay();
gauntletCrownRunwayWalkActive = true;
gauntletCrownRunwayWalkStartMs = Date.now();
/* G3: เข้าเฟสเดินเข้าเส้นชัย → เคลียร์ท่ากระโดด (กันค้างลอยกลางอากาศ — ต้องเดินเข้าเส้น ไม่ใช่กระโดดค้าง) */
meGauntletJumpTicks = 0; meGauntletJumpVis = 0;
others.forEach((o) => { if (o) { o.gauntletJumpTicks = 0; o.gauntletJumpVis = 0; } });
/* แจ้ง server ครั้งเดียว: เข้าโซนเส้นชัย → หยุด obstacle + collision (กันโดนชน/-10 ตอนกำลังเดินเข้าเส้น)
server จะรอ grace (GAUNTLET_FINISH_WALK_MS) ให้เดินครบก่อนค่อยจบ */
if (!gauntletFinishPhaseSignaled) {
@@ -1792,7 +1813,9 @@
ctx.save();
ctx.setTransform(zDraw, 0, 0, zDraw, canvasW / 2 - camX * zDraw, canvasH / 2 - camY * zDraw);
ctx.beginPath();
ctx.rect(0, 0, mapWpx, mapHpx);
/* แนวนอน: คลิปเต็มความกว้างที่มองเห็น (worldMinX..worldMaxX) แทนตัดที่ความกว้างแมพ (mapWpx)
→ จอกว้างทุก resolution รันเวย์โชว์ยาวเต็ม ไม่เหลือขอบดำซ้าย-ขวา · แนวตั้งคงคลิป=ความสูงแมพ (ไม่กระทบเส้นชัย) */
ctx.rect(worldMinX, 0, cwR, mapHpx);
ctx.clip();
ctx.translate(worldMinX, 0);
if (gauntletCrownRunwayBgMapEligiblePlay()) {
@@ -3575,7 +3598,7 @@
let meGauntletJumpVis = 0;
/** ซิงก์จากเซิร์ฟเวอร์ (gauntlet-sync / GET /api/game-timing) */
let gauntletRuntimeTickMs = 220;
let gauntletRuntimeJumpTicks = 16;
let gauntletRuntimeJumpTicks = 6;
/** Stack: รอบสวิงต่อวินาที — จาก GET /api/game-timing · ค่าน้อย = สวิงช้า */
let playStackSwingHz = 0.55;
/** Stack: ความกว้างบล็อก (tile) — null = คำนวณจากโซนลงบนแผนที่ */
@@ -4019,6 +4042,9 @@
let jumpSurviveJumpQueued = false;
let jumpSurviveOnGround = false;
let jumpSurviveLastEmitT = 0;
let jumpSurviveServerAuthPlay = false; /* true เมื่อรับ jump-survive-bot-move/ended ครั้งแรก — server เป็นเจ้าของบอท/รอบ */
let spaceShooterServerAuthPlay = false; /* MG6: true เมื่อรับ space-shooter-bot-move — server เป็นเจ้าของบอท/รอบ */
let balloonBossServerAuthPlay = false; /* MG7: true เมื่อรับ balloon-boss-bot-move — server เป็นเจ้าของบอท/รอบ/HP บอส */
/** mnptfts2: howto → countdown → live → ended (คะแนน 100 ผู้รอดสุดท้าย) */
let jumpSurviveMissionPhase = null;
let jumpSurviveGameEnded = false;
@@ -5727,6 +5753,11 @@
*/
function applyGauntletPreviewSpawnLayout(onlyBots) {
if (!mapData || mapData.gameType !== 'gauntlet') return;
/* เกมจริง (ห้องจริงทุกแบบ: คนล้วน/มีบอท): server เป็นเจ้าของตำแหน่ง/เลนของทุกตัวผ่าน gauntlet-sync
→ ห้าม client จัดเลนเอง เพราะ layout นี้สแนป me ไป pos[0] (เลนบนสุด, จุดสตาร์ท) เสมอ
ทำให้ตอน host หลุด me (host ใหม่) ไปทับบอท takeover ที่กินเลนบนสุด
เฉพาะ editor preview (preview=1, ไม่มี server ขับ) เท่านั้นที่ client จัดเลนเอง */
if (!previewMode) return;
const realIds = [...others.keys()].filter((id) => !isPreviewBotId(id)).sort();
const botIds = [...others.keys()].filter(isPreviewBotId).sort();
const humanCount = 1 + realIds.length;
@@ -6520,7 +6551,7 @@
/* #1 map fit: gauntlet เกมจริง — fit "ความสูงแมป (ลู่ทั้งหมด)" ให้พอดีจอ resolution-adaptive
(เดิมใช้ zoom ดิบ ไม่ปรับตาม resolution = ไม่พอดี) · fit สูงอย่างเดียว เพราะ runway เลื่อนแนวนอน */
const mhPx = h * tileSize;
const fitZ = (canvas.height / mhPx) * 0.96;
const fitZ = (canvas.height / mhPx) * 1.0; /* เต็มจอบน-ล่าง (เดิม 0.96 = มีขอบดำบน-ล่าง) */
if (Number.isFinite(fitZ) && fitZ > 0) zDraw = fitZ;
}
if (isStackTowerEmbedZoomLockedPlay()) {
@@ -6605,6 +6636,7 @@
const GAUNTLET_CROWN_TAIL_OFFSCREEN_MARGIN_PX = 40;
function maybeEliminateGauntletPreviewBotsTailOffCameraPlay(camX, camY, zDrawVal) {
if (!playBotsEnabled() || !isMePlayHost() || !isGauntletCrownHeistMapPlay() || !mapData) return;
if (detectiveCaseFillBots) return; /* MG2 เกมจริง: server ตัดสินการตาย/คัดออกของบอทเอง */
if (gauntletCrownPregamePhase !== 'live') return;
const halfW = canvas.width / (2 * zDrawVal);
const worldMinX = camX - halfW;
@@ -6620,6 +6652,18 @@
function isStack() { return mapData && mapData.gameType === 'stack'; }
function isJumpSurvive() { return mapData && mapData.gameType === 'jump_survive'; }
/* MG5 server-auth: server เป็นเจ้าของบอท/รอบ → host เลิก sim บอทเอง, ทุกเครื่องรับตำแหน่งบอทจาก server */
function jumpSurviveServerAuthActive() {
return isJumpSurvive() && (jumpSurviveServerAuthPlay || isDetectiveMinigamePlay());
}
/* MG6 server-auth: server เป็นเจ้าของบอท/รอบ → host เลิก sim บอท, ทุกเครื่องรับตำแหน่งบอทจาก server */
function spaceShooterServerAuthActive() {
return isSpaceShooter() && (spaceShooterServerAuthPlay || isDetectiveMinigamePlay());
}
/* MG7 server-auth: server เป็นเจ้าของบอท/รอบ/HP บอส → host เลิก sim บอท, ทุกเครื่องรับตำแหน่ง/ดาเมจบอทจาก server */
function balloonBossServerAuthActive() {
return isBalloonBoss() && (balloonBossServerAuthPlay || isDetectiveMinigamePlay());
}
function currentPlayMapId() {
const q = (playSessionMapId || '').trim();
@@ -7722,6 +7766,7 @@
if (!isStackTowerMissionUiMapPlay()) return;
if (stackTowerMissionPhase !== 'live') return;
if (stackTowerMissionEndedOnce) return;
if (stackServerAuthPlay) return; /* server-auth: server ตัดสินจบ + ส่ง stack-over */
const pct = stackTowerDecryptPctPlay();
const elapsed = stackTowerMissionElapsedSecPlay();
const timeLim = stackTowerMissionTimeLimitSecPlay();
@@ -8996,6 +9041,13 @@
function isBalloonBoss() { return mapData && mapData.gameType === 'balloon_boss'; }
function isQuizCarry() { return mapData && mapData.gameType === 'quiz_carry'; }
/* MG4 server-authoritative: เกมจริง (detective) เท่านั้น — server เป็นเจ้าของ คำถาม/เฟส/หยิบ-ส่ง/คะแนน/บอท
editor preview (เล่นเดี่ยว) ยังเป็น client-sim เดิม (gate เดียวกับ server: space.detectiveMinigameActive) */
let quizCarryServerAuthPlay = false; /* ตั้ง true เมื่อรับ quiz-carry-phase (srv) ครั้งแรก — กัน edge ก่อน flag */
function quizCarryServerAuthActive() {
return isQuizCarry() && (quizCarryServerAuthPlay || isDetectiveMinigamePlay());
}
/** พรีวิว editor + ภารกิจสืบสวน mnorwqx1 — flow เต็ม: HOWTO/READY, นับ 3-2-1, ครบ carrySessionLength, สรุปภารกิจ */
function quizCarryEmbedMissionFlowActive() {
if (!isQuizCarry()) return false;
@@ -9755,6 +9807,11 @@
function broadcastFillBotState() {
if (!playBotsEnabled() || !isMePlayHost() || !mapData) return;
if (isQuiz() && detectiveCaseFillBots) return; /* quiz เกมจริง: server เป็นเจ้าของบอท (quiz-bot-move) → ห้าม host ส่งทับ = บอทวาป/ผีหลุด area */
if (isGauntlet() && detectiveCaseFillBots) return; /* MG2 เกมจริง: server เป็นเจ้าของบอท (gauntlet-sync) → ห้าม host ส่งทับ */
if (quizCarryServerAuthActive()) return; /* MG4 เกมจริง: server เป็นเจ้าของบอท (quiz-carry-bot-move) → ห้าม host ส่งทับ */
if (jumpSurviveServerAuthActive()) return; /* MG5 เกมจริง: server เป็นเจ้าของบอท (jump-survive-bot-move) → ห้าม host ส่งทับ */
if (spaceShooterServerAuthActive()) return; /* MG6 เกมจริง: server เป็นเจ้าของบอท (space-shooter-bot-move) → ห้าม host ส่งทับ */
if (balloonBossServerAuthActive()) return; /* MG7 เกมจริง: server เป็นเจ้าของบอท (balloon-boss-bot-move) → ห้าม host ส่งทับ */
const arr = [];
others.forEach((o, id) => {
if (!isPreviewBotId(id) || !o) return;
@@ -10131,6 +10188,7 @@
}
function tickQuizCarryRoundTimers() {
if (quizCarryServerAuthActive()) return; /* server คุมเวลาตอบ/จบรอบ */
if (quizCarrySessionEnded) return;
if (quizCarryPregameActive) return;
if (!isQuizCarry() || !mapData || !quizCarryCurrent) return;
@@ -10302,6 +10360,7 @@
}
function quizCarryPickNextQuestion() {
if (quizCarryServerAuthActive()) return; /* server เป็นเจ้าของคำถาม (quiz-carry-phase) */
if (quizCarrySessionEnded) return;
if (quizCarryPregameActive) return;
if (!quizCarryPool.length) {
@@ -10350,6 +10409,7 @@
/** หลังจบข้อหนึ่งข้อ (ถูกหรือหมดเวลา): พรีวิวนับรอบจนครบ carrySessionLength แล้วจบเซสชัน */
function quizCarryAfterRoundResolved() {
if (!isQuizCarry()) return;
if (quizCarryServerAuthActive()) return; /* server คุมการจบรอบ/เลื่อนข้อ (quiz-carry-result/ended) */
if (isDetectiveMinigamePlay() && !quizCarryEmbedMissionFlowActive()) {
finishDetectiveMinigameAndReturnLobby();
return;
@@ -10952,6 +11012,24 @@
if (quizCarryPregameActive) return false;
if (quizCarrySessionEnded) return false;
if (!isQuizCarry() || !mapData || !quizCarryCurrent) return false;
/* MG4 server-auth: คนจริงกด F/GRAB → ส่ง intent ให้ server ตัดสิน (server เป็นเจ้าของ held/คะแนน/จบรอบ)
บอทถูก guard ปิด local-sim แล้ว จึงไม่เข้ามาทางนี้ */
if (quizCarryServerAuthActive()) {
if (actorId === myId && !opts.silent && socket && socket.connected) {
/* feedback ถูก/ผิด ที่โต๊ะทันที — server เป็นเจ้าของ held/คะแนน/จบรอบ แต่ client รู้ held + correctIndex อยู่แล้ว
(เดิม server-auth ส่งแต่ intent ไม่โชว์อะไร → ผู้เล่นไม่เห็นว่าตอบถูก/ผิด โดยเฉพาะตอบผิด server ไม่แจ้งกลับ) */
try {
const held = me.quizCarryHeld;
if (held != null && quizCarryCurrent && quizCarryOptionsPickableNow() && quizCarryCanSubmitAnswerAt(mapData, ox, oy)) {
const ok = held === quizCarryCurrent.correctIndex;
showQuizMapQuestionCarryFeedback(ok);
showQuizCarryToast(ok ? ('ถูกต้อง +' + QUIZ_CARRY_POINTS_PER_CORRECT + ' แต้ม') : 'ผิด — คืนป้ายที่โซนตัวเลือกแล้ว กด F หยิบใหม่ได้', ok);
}
} catch (e) { /* ignore */ }
socket.emit('quiz-carry-grab');
}
return false;
}
const md = mapData;
const footprint = quizTilesFootprintPlay(ox, oy);
const ent = actorId === myId ? me : others.get(actorId);
@@ -11452,6 +11530,7 @@
function resetQuizCarryPlayState() {
cancelQuizCarrySessionCompleteResultToSummary();
quizCarryServerAuthPlay = false; /* MG4: รีเซ็ต gate server-auth (ตั้งใหม่เมื่อ quiz-carry-phase แรกมา) */
const qcmo = document.getElementById('quiz-carry-mission-overlay');
if (qcmo) qcmo.classList.add('is-hidden');
quizCarryPregameActive = false;
@@ -11784,6 +11863,7 @@
}
function stepQuizCarryPreviewBots() {
if (quizCarryServerAuthActive()) return; /* server ขับบอท (quiz-carry-bot-move) */
if (quizCarrySessionEnded) return;
if (quizCarryPregameActive) return;
if (!playBotsEnabled() || !isQuizCarry() || !mapData) return;
@@ -11966,6 +12046,7 @@
stackMini = null;
stackTowerPost50AnimStartMs = null;
stackTowerHeartMinusFx = null;
stackServerAuthPlay = false; /* re-arm: server จะเปิดใหม่ผ่าน stack-state(srv) เมื่อรอบใหม่เริ่ม */
if (!mapData || !isStack()) return;
const w = mapData.width || 20, h = mapData.height || 15;
const land = getStackAreaBoundsPlay(mapData.stackLandArea, w, h);
@@ -12315,7 +12396,12 @@
const actorHeavy = !!m.pendingDropHeavy;
const actorTheme = m.pendingDropTheme || null; /* remote drop = สีจาก host; local = null (resolve เอง) */
const tw = m.widthTiles * tileSize * getStackTowerBlockWorldScalePlay();
const swingXW = (m.topCenterX + m.swingAmp * Math.sin(m.phase)) * tileSize;
/* server-auth: ใช้ offset ปล่อยจริงจาก server (landOffsetTiles) เป็นจุดเริ่มร่วง → ตรงกับจุดลง = บล็อกร่วงตรง ไม่ไถลออกจากเชือก
(เดิมเริ่มจาก m.phase ฝั่ง client ที่อาจคลาดจาก server → บล็อกเริ่มคนละจุดกับที่ลง) */
const swingReleaseOff = (stackServerAuthPlay && Number.isFinite(hit.landOffsetTiles))
? hit.landOffsetTiles
: (m.swingAmp * Math.sin(m.phase));
const swingXW = (m.topCenterX + swingReleaseOff) * tileSize;
const lh = m.layerWorldH || Math.max(14, tileSize * 0.3);
const swingLift = getStackTowerSwingLiftWorldPx(m.layers.length, lh);
const swingAttachY = m.swingWorldY - swingLift;
@@ -12376,6 +12462,13 @@
/** @returns {boolean} เริ่มแอนิเมชันร่วงแล้วหรือไม่ */
function tryHumanStackDrop() {
if (!stackHumanDropGateOkPlay()) return false;
if (stackServerAuthPlay) {
/* server-auth: ส่งเจตนากดวาง → server คำนวณ ณ เฟสปัจจุบัน แล้ว emit stack-drop กลับมาทุกเครื่อง (รวมเรา) */
try { socket.emit('stack-drop-req'); } catch (e) { /* ignore */ }
stackMini.lastDropAt = Date.now(); /* กัน double-tap (server มี busyUntil อยู่แล้ว) */
if (specialQuizIcon) sendSpecialQuizCollide();
return true;
}
ensureStackNextDropVisual();
const hit = computeStackDropResult(stackMini);
if (!startStackFallAnimation(hit, playBotsEnabled() ? { human: true, id: myId != null ? String(myId) : null } : null)) return false;
@@ -12408,6 +12501,7 @@
/** Stack preview: สลับตา — เฉพาะบอทที่ถึงตาถึงจะจำลองปล่อยหนึ่งครั้งแล้วเลื่อนตา */
function stepPreviewStackTurnBased(nowMs) {
if (stackServerAuthPlay) return; /* server-auth: บอทถูกขับโดย server (รับผลผ่าน stack-drop) */
if (!playBotsEnabled() || !isStack() || !stackMini || stackFall || stackMini.settling) return;
/* เล่นจริง: host เท่านั้นจำลองบอท (แล้ว broadcast) — non-host รอรับ stack-drop; editor preview (previewFillBots) จำลองเองได้ */
if (detectiveCaseFillBots && !isMePlayHost()) return;
@@ -12450,6 +12544,8 @@
stackMini.pendingDropHeavy = !!item.heavy;
stackMini.pendingDropTheme = item.theme || null; /* สีจาก host สำหรับบล็อกนี้ (remote drop) */
stackMini.widthTiles = stackBlockWidthTilesForPlay(stackMini.initialWidthTiles, !!item.heavy);
/* snap เฟสเชือกให้ตรงจุดปล่อยของ server → เชือก+บล็อกที่ร่วงตรงกัน (กันบล็อกไม่ตรงเชือก) */
if (Number.isFinite(item.phase)) stackMini.phase = item.phase;
return startStackFallAnimation(item.hit, item.actor || { human: true });
}
@@ -12459,7 +12555,7 @@
const dt = Math.min(0.06, (now - lastStackTickMs) / 1000);
lastStackTickMs = now;
/* เล่นจริง: host ส่งลำดับตา+index เป็นระยะ → non-host adopt (ครอบ late-join: client โหลดช้า host จะรวมเข้า order ให้) */
if (detectiveCaseFillBots && isMePlayHost()) {
if (detectiveCaseFillBots && isMePlayHost() && !stackServerAuthPlay) {
/* rebuild เมื่อ (ก) ยังไม่มีคิว หรือ (ข) คน/บอท เปลี่ยน (เช่น client เข้าทีหลัง = late-join) → ให้ทุกคนได้ seat + สี */
if (!stackTurnOrderReadyPlay() || stackOrderNeedsRebuildPlay()) rebuildStackPreviewTurnOrder();
const ts = Date.now();
@@ -12538,12 +12634,17 @@
/* เล่นจริง: แกว่งเฉพาะตาคนที่คุม (เรา หรือ host คุมบอท) แล้ว broadcast เฟสให้คนอื่น mirror;
editor preview / เล่นเดี่ยว: แกว่งเองตลอดเหมือนเดิม */
let advancePhase = true;
if (detectiveCaseFillBots) {
let emitSwing = false;
if (stackServerAuthPlay) {
advancePhase = true; /* server-auth: เดินเฟส local ให้ลื่น → reconcile ตอนรับ stack-swing */
emitSwing = false; /* แต่ไม่ emit — server เป็นเจ้าของเฟส */
} else if (detectiveCaseFillBots) {
advancePhase = isMyStackTurn() || (isMePlayHost() && isStackCurrentBotTurnPlay());
emitSwing = advancePhase;
}
if (advancePhase) {
stackMini.phase += stackMini.phaseSpeed * dt * Math.PI * 2;
if (detectiveCaseFillBots && socket && myId != null) {
if (emitSwing && detectiveCaseFillBots && socket && myId != null) {
const tnow = Date.now();
if (tnow - stackLastSwingEmit > 50) {
stackLastSwingEmit = tnow;
@@ -12765,6 +12866,14 @@
if (Object.prototype.hasOwnProperty.call(payload, 'gauntletLiveStartMs')) {
const _ls = Number(payload.gauntletLiveStartMs);
gauntletCrownRunwayServerLiveStartMs = (Number.isFinite(_ls) && _ls > 0) ? _ls : 0; /* G1: anchor scroll จาก server (เดียวกันทุกเครื่อง) */
/* G2: รับ liveStartMs จริง (อาจ "อนาคต" = pre-run hold) → re-anchor ทับค่า fallback ที่ snap ล็อกไว้ตอน howto
(ไม่แตะระหว่างเฟสเส้นชัย/เดินเข้าเส้น เพื่อไม่ดึง scroll กลับ) */
if (gauntletCrownRunwayServerLiveStartMs > 0
&& gauntletCrownRunwaySpawnScrollSnapped
&& !gauntletCrownRunwayBgFinishLatched
&& !gauntletCrownRunwayWalkActive) {
gauntletCrownRunwayLiveAnchorServerMs = gauntletCrownRunwayServerLiveStartMs;
}
}
if (Object.prototype.hasOwnProperty.call(payload, 'gauntletEndsAt')) {
if (payload.gauntletEndsAt == null) gauntletEndsAtMs = null;
@@ -13042,6 +13151,7 @@
/* อาร์คกระโดดนุ่มแบบ design (rn-jump): sine โค้งสมมาตร ease-in-out peak กลางอากาศ
เดิม: ขาขึ้น quadratic / ขาลง linear (อสมมาตร peak ~72% เวลา → ขาลงกระตุก) */
const p = Math.max(0, Math.min(1, 1 - t)); /* 0 = เริ่มกระโดด, 1 = แตะพื้น */
/* อาร์ค sine นุ่มสมมาตร — ขึ้น-ลงลื่นไม่กระตุก (เวลากระโดดสั้นลงแล้ว = ไม่ลอยค้าง, sine นุ่ม = ไม่วาปลง) */
return Math.sin(Math.PI * p);
}
@@ -13278,6 +13388,7 @@
function applyGauntletPreviewBotsAfterSync() {
if (!playBotsEnabled() || !isMePlayHost() || !mapData || mapData.gameType !== 'gauntlet') return;
if (detectiveCaseFillBots) return; /* MG2 เกมจริง: server เดินบอทเอง (host หลุดก็ไม่หยุด) */
if (isGauntletCrownHeistMapPlay() && isGauntletCrownPregameBlockingPlay()) return;
if (isGauntletCrownHeistMapPlay() && gauntletCrownRunwayStripFrozenPlay()) return;
const w = mapData.width || 20;
@@ -13292,6 +13403,7 @@
/** แจ้งเซิร์ฟเวอร์แถว y ที่มีคุณ+บอททดสอบ — lane spawn ใช้เฉพาะแถวที่มี peer; บอทไม่ใช่ peer */
function emitGauntletPreviewRowsToServer() {
if (!playBotsEnabled() || !mapData || mapData.gameType !== 'gauntlet') return;
if (detectiveCaseFillBots) return; /* MG2 เกมจริง: server รู้แถวเลนของบอทเอง (gr.bots) */
if (isGauntletCrownHeistMapPlay() && isGauntletCrownPregameBlockingPlay()) return;
const h = mapData.height || 15;
const ysSet = new Set();
@@ -13943,8 +14055,11 @@
const totalSum = ranked.reduce(function (s, r) { return s + r.finalScore; }, 0);
const n = ranked.length || 1;
const averageScore = Math.floor(totalSum / n);
const grade = averageScore >= 80 ? 'A' : averageScore >= 60 ? 'B' : 'C';
const rewardCard = gauntletCrownRollRewardCardLocal(grade);
const survivorCount = baseRows.filter(function (r) { return !r.eliminated; }).length;
/* เกรด F = ไม่มีผู้รอด (ตรงกับ local builder + server) → ไม่มีการ์ดรางวัล */
let grade = averageScore >= 80 ? 'A' : averageScore >= 60 ? 'B' : 'C';
if (survivorCount <= 0) grade = 'F';
const rewardCard = grade === 'F' ? null : gauntletCrownRollRewardCardLocal(grade);
return {
ranked: ranked,
totalSum: totalSum,
@@ -13952,6 +14067,8 @@
grade: grade,
rewardCard: rewardCard,
totalParts: ranked.map(function (r) { return r.finalScore; }),
participantCount: baseRows.length,
survivorCount: survivorCount,
};
}
@@ -16863,6 +16980,7 @@
}
function stepSpaceShooterPreviewBots(dt) {
if (spaceShooterServerAuthActive()) return; /* server-auth: บอทถูกขับโดย server (รับผ่าน space-shooter-bot-move) */
if (spaceShooterGameEnded || !playBotsEnabled() || !isMePlayHost() || !mapData || mapData.gameType !== 'space_shooter') return;
if (isSpaceShooterMissionUiMapPlay() && spaceShooterMissionPhase !== 'live') return;
const ts = tileSize;
@@ -16985,7 +17103,15 @@
}
others.forEach((o, id) => {
if (isPreviewBotId(id)) return;
if (isPreviewBotId(id)) {
/* บอท server-auth: lerp ยานเข้าหาเป้า (ตำแหน่งจาก server) ทุกเฟรม ~60fps → ลื่น ไม่กระตุก */
if (spaceShooterServerAuthActive() && !o.spaceShooterEliminated) {
if (Number.isFinite(o.spaceShooterTargetCx) && Number.isFinite(o.spaceShooterCx)) o.spaceShooterCx += (o.spaceShooterTargetCx - o.spaceShooterCx) * 0.32;
if (Number.isFinite(o.spaceShooterTargetCy) && Number.isFinite(o.spaceShooterCy)) o.spaceShooterCy += (o.spaceShooterTargetCy - o.spaceShooterCy) * 0.32;
try { syncSpaceShooterFootFromShipCenter(o); o.tx = o.x; o.ty = o.y; } catch (e) { /* ignore */ }
}
return;
}
/* lerp แรงขึ้น (0.45) → ยานผู้เล่นคนอื่นตามตำแหน่งจริงทันขึ้น ลดอาการ "ไม่ตรง/ยืด" */
if (o.tx != null) o.x += (o.tx - o.x) * 0.45;
if (o.ty != null) o.y += (o.ty - o.y) * 0.45;
@@ -17570,7 +17696,8 @@
/** รวมดาเมจที่ทำกับบอส (ไม่เท่ากับคะแนน — คะแนน +10 ต่อครั้ง, HP บอส -2 ต่อครั้ง)
non-host: ใช้ค่าที่ host เป็นเจ้าของ (single source of truth) ถ้าได้รับแล้ว — HP บอสตรงกันทุกเครื่อง */
function balloonBossTeamBossDamagePlay() {
if (!isMePlayHost() && balloonBossHostBossDmg != null) {
/* server-auth: ดาเมจบอท (balloon-boss-bot-move) + คน (user-move) sync ครบทุกเครื่อง → รวม local เองได้ (host-independent) */
if (!balloonBossServerAuthActive() && !isMePlayHost() && balloonBossHostBossDmg != null) {
return Math.max(0, balloonBossHostBossDmg | 0);
}
let s = Math.max(0, me.balloonBossBossDmg | 0);
@@ -17709,6 +17836,7 @@
}
function stepBalloonBossPreviewBots(dt, mw, mh, bossCx, bossCy, ts) {
if (balloonBossServerAuthActive()) return; /* server-auth: บอทถูกขับโดย server (รับผ่าน balloon-boss-bot-move) */
if (!playBotsEnabled() || !isMePlayHost() || !mapData || mapData.gameType !== 'balloon_boss') return;
[...others.keys()].filter(isPreviewBotId).forEach((bid) => {
const o = others.get(bid);
@@ -17805,7 +17933,7 @@
const allRefsEarly = buildBalloonBossParticipantRefsPlay();
/* จบเกมแบบ host-authoritative: host ตัดสิน + broadcast, ทุกเครื่องจบพร้อมกัน
(เดิมแต่ละเครื่องตัดสินเองจาก teamBossDmg/elimination ที่สะสมไม่พร้อมกัน → game over ไม่เท่ากัน) */
if (isMePlayHost()) {
if (isMePlayHost() && !balloonBossServerAuthActive()) {
if (remSec != null && remSec <= 0) {
if (teamBossDmg >= maxHp) { triggerBalloonBossOverPlay('victory'); return; }
/* หมดเวลาแต่บอสยังไม่ตาย → ลูกโป่งของคนที่ยังเหลือ "แตกหมด" ตายทั้งหมด (แพ้) */
@@ -17929,7 +18057,7 @@
applyBalloonBossHitToEntity(me);
}
}
if (!hit && isMePlayHost()) {
if (!hit && isMePlayHost() && !balloonBossServerAuthActive()) {
aliveRefs.forEach((entry) => {
if (hit || !isPreviewBotId(entry.id)) return;
const ent = entry.ref;
@@ -17962,7 +18090,7 @@
if (String(entry.id) === String(b.ownerId)) continue;
/* ลูกโป่งของตัวเอง = เจ้าตัวตัดสิน · บอท = host ตัดสินคนเดียว (non-host เอาตาย/ลูกโป่งจาก fill-bot-state)
เดิม non-host คิด damage บอทเองจากกระสุนที่ relay มา → บอทตายคนละจังหวะ 2 จอ */
const canBalloonHit = (ent === me) || (isPreviewBotId(entry.id) && isMePlayHost());
const canBalloonHit = (ent === me) || (isPreviewBotId(entry.id) && isMePlayHost() && !balloonBossServerAuthActive());
if (!canBalloonHit) continue;
const cc = balloonBossBalloonClusterWorldPlay(ent, bossC.cx, bossC.cy);
const pdx = b.x - cc.cx, pdy = b.y - cc.cy;
@@ -18012,7 +18140,7 @@
balloonBossEliminated: !!me.balloonBossEliminated,
});
}
} else if (isPreviewBotId(b.ownerId) && isMePlayHost()) {
} else if (isPreviewBotId(b.ownerId) && isMePlayHost() && !balloonBossServerAuthActive()) {
const o = others.get(b.ownerId);
if (o) {
o.balloonBossScore = Math.max(0, (o.balloonBossScore | 0) + addScore);
@@ -18119,7 +18247,7 @@
}
/* MG7 host-authoritative boss HP: host broadcast ดาเมจรวม + สถานะผู้เล่นคนอื่น ~7Hz
→ ทุกเครื่องแสดง HP บอส/ลูกโป่ง/การตาย ตรงกับ host (single source of truth) */
if (isMePlayHost() && socket && myId != null && tEmit - balloonBossLastSyncEmit > 140) {
if (isMePlayHost() && !balloonBossServerAuthActive() && socket && myId != null && tEmit - balloonBossLastSyncEmit > 140) {
balloonBossLastSyncEmit = tEmit;
const syncRefs = buildBalloonBossParticipantRefsPlay();
socket.emit('balloon-boss-sync', {
@@ -19199,6 +19327,7 @@
}
function stepJumpSurvivePreviewBots(dt, halfViewH) {
if (jumpSurviveServerAuthActive()) return; /* server-auth: บอทถูกขับโดย server (รับผ่าน jump-survive-bot-move) */
if (!playBotsEnabled() || !isMePlayHost() || !mapData || !isJumpSurvive()) return;
const md = mapData;
const { cw, ch } = getCharacterFootprintWH(md);
@@ -19319,8 +19448,11 @@
/* ผู้เล่นจริงระยะไกล (ไม่ใช่บอท): เลอร์ปตำแหน่งตาม move ที่รับมา (o.tx/o.ty)
เดิม jump_survive ไม่ได้เลอร์ป others เลย → host/เพื่อนค้างที่จุดเกิดขณะกล้องเลื่อนขึ้น = ตัวละครหายจากจอ
(เกมอื่นทำที่ tick กลาง 23235 แต่ jump_survive return ก่อนถึงตรงนั้น จึงต้องทำในนี้เอง) */
const jsSrvAuth = jumpSurviveServerAuthActive();
others.forEach((o, id) => {
if (isPreviewBotId(id) || !o) return;
if (!o) return;
/* ผู้เล่นจริงระยะไกล → lerp ตาม move; บอท → ปกติ host sim เอง (ข้าม) แต่ server-auth: บอทขับโดย server → lerp ด้วย */
if (isPreviewBotId(id) && !jsSrvAuth) return;
if (o.tx != null) o.x += (o.tx - o.x) * 0.28;
if (o.ty != null) o.y += (o.ty - o.y) * 0.28;
});
@@ -20355,6 +20487,121 @@
if (isMePlayHost() || !isQuizCarry() || !quizCarryEmbedMissionFlowActive()) return;
showQuizCarrySessionCompleteOverlay();
});
/* ===== MG4 server-authoritative (เกมจริง detective): adopt server state ===== */
/** แปลง epoch ฝั่ง server → epoch ท้องถิ่น (ชดเชย clock offset) */
function quizCarryServerEpochToLocal(se) {
const n = Number(se);
if (!Number.isFinite(n) || n <= 0) return 0;
return n - (serverNow() - Date.now());
}
function applyQuizCarryPhaseFromServer(p) {
if (!p || !isQuizCarry()) return;
quizCarryServerAuthPlay = true;
quizCarryPregameActive = false;
quizCarrySessionEnded = false;
quizCarryAnswerTimeupAwaitNext = false;
quizCarryEmbedPendingQuestion = null;
quizCarryEmbedCountdownStartAt = 0;
quizCarryEmbedCountdownEndAt = 0;
quizCarryEmbedPreOptionCountdownStartAt = 0;
quizCarryEmbedPreOptionCountdownEndAt = 0;
try { hideQuizCarryEmbedCountdownOverlay(); } catch (e) { /* ignore */ }
try { hideQuizCarryTimeupOnDeskLayer(); } catch (e) { /* ignore */ }
const q = {
text: String(p.text || ''),
choices: Array.isArray(p.choices) ? p.choices.slice() : [],
correctIndex: Number.isFinite(Number(p.correctIndex)) ? Math.floor(Number(p.correctIndex)) : 0,
};
if (Array.isArray(p.choiceImageUrls)) q.choiceImageUrls = p.choiceImageUrls.slice();
quizCarryCurrent = q;
preloadQuizCarryChoiceImages(q);
playQuizText = formatQuizCarryQuestionHud(q);
const qEl = document.getElementById('quiz-game-question');
if (qEl) qEl.textContent = playQuizText;
clearQuizMapQuestionCarryFeedback();
if (p.phase === 'answer') {
quizCarryOptionRevealAt = quizCarryServerEpochToLocal(p.optionRevealAt) || Date.now();
quizCarryAnswerCloseAt = quizCarryServerEpochToLocal(p.endsAt);
} else {
/* read: optionRevealAt อยู่อนาคต → ยังหยิบไม่ได้ จนกว่าเฟส answer */
quizCarryOptionRevealAt = quizCarryServerEpochToLocal(p.optionRevealAt);
quizCarryAnswerCloseAt = 0;
}
me.quizCarryHeld = null;
others.forEach((o) => { if (o) { o.quizCarryHeld = null; o.botPath = []; } });
try { renderPlayQuizScoreboard(playLiveQuizScores); } catch (e) { /* ignore */ }
try { syncPlayQuizMapPanel(); } catch (e) { /* ignore */ }
try { updateQuizCarryCarryPhaseHud(); } catch (e) { /* ignore */ }
try { syncQuizCarryGrabButton(); } catch (e) { /* ignore */ }
}
socket.on('quiz-carry-phase', (p) => {
if (!p || !p.srv || !isQuizCarry()) return;
applyQuizCarryPhaseFromServer(p);
});
/* บอท carry: server เป็นเจ้าของการเดิน/หยิบ (BFS) → รับตำแหน่ง+held แล้ว lerp ใน tick (o.tx/o.ty) */
socket.on('quiz-carry-bot-move', (d) => {
if (!d || !Array.isArray(d.bots) || !isQuizCarry()) return;
quizCarryServerAuthPlay = true;
d.bots.forEach((rb) => {
if (!rb || typeof rb.id !== 'string') return;
const o = others.get(rb.id);
if (!o) return; /* บอท avatar ถูกสร้างโดย rebalancePreviewBots (id ตรงกับ server) */
if (Number.isFinite(rb.x)) { const big = Number.isFinite(o.x) && Math.abs(rb.x - o.x) > 3; o.tx = rb.x; if (big || !Number.isFinite(o.x)) o.x = rb.x; }
if (Number.isFinite(rb.y)) { const big = Number.isFinite(o.y) && Math.abs(rb.y - o.y) > 3; o.ty = rb.y; if (big || !Number.isFinite(o.y)) o.y = rb.y; }
if (rb.direction) o.direction = rb.direction;
o.botIsWalking = !!rb.isWalking;
o.quizCarryHeld = (rb.held == null) ? null : Math.floor(Number(rb.held));
});
});
/* held ของคนจริง (หยิบ/ส่ง/วาง) จาก server */
socket.on('quiz-carry-held', (d) => {
if (!d || d.id == null || !isQuizCarry()) return;
const raw = d.held;
const held = (raw == null) ? null : Math.floor(Number(raw));
const val = (held == null || !Number.isFinite(held)) ? null : held;
if (String(d.id) === String(myId)) me.quizCarryHeld = val;
else { const o = others.get(d.id) || others.get(String(d.id)); if (o) o.quizCarryHeld = val; }
try { syncQuizCarryGrabButton(); } catch (e) { /* ignore */ }
});
/* จบรอบ: server แจ้งผล (ใครถูก/correctIndex/คะแนน) → merge + เคลียร์ held */
socket.on('quiz-carry-result', (d) => {
if (!d || !isQuizCarry()) return;
quizCarryServerAuthPlay = true;
if (d.scores && typeof d.scores === 'object') {
playLiveQuizScores = {};
for (const id in d.scores) playLiveQuizScores[id] = Math.max(0, Number(d.scores[id]) || 0);
}
me.quizCarryHeld = null;
others.forEach((o) => { if (o) { o.quizCarryHeld = null; o.botPath = []; } });
if (d.winnerId != null && String(d.winnerId) === String(myId)) {
try { showQuizMapQuestionCarryFeedback(true); } catch (e) { /* ignore */ }
}
try { renderPlayQuizScoreboard(playLiveQuizScores); } catch (e) { /* ignore */ }
try { syncQuizCarryGrabButton(); } catch (e) { /* ignore */ }
});
/* จบเซสชัน: server สรุปคะแนน แล้วพากลับ LobbyB (detective) — ไม่ต้องคุม UI เอง */
socket.on('quiz-carry-ended', (d) => {
if (!isQuizCarry()) return;
quizCarryServerAuthPlay = true;
quizCarrySessionEnded = true;
if (d && d.scores && typeof d.scores === 'object') {
playLiveQuizScores = {};
for (const id in d.scores) playLiveQuizScores[id] = Math.max(0, Number(d.scores[id]) || 0);
try { renderPlayQuizScoreboard(playLiveQuizScores); } catch (e) { /* ignore */ }
}
});
/* disconnect takeover: คนหลุดกลางเกม → บอท server แทน (เก็บ avatar เดิม + ติดป้าย "(บอท)") — เหมือน MG1 */
socket.on('quiz-carry-player-to-bot', (d) => {
if (!d || d.id == null || !isQuizCarry()) return;
const o = others.get(d.id) || others.get(String(d.id));
if (!o) return;
o.isQuizCarryTakeoverBot = true;
o.tx = o.x; o.ty = o.y;
let nn = (o.nickname != null && String(o.nickname).trim()) ? String(o.nickname).trim() : (d.nickname ? String(d.nickname).trim() : 'ผู้เล่น');
if (nn.indexOf('(บอท)') < 0) nn = nn + ' (บอท)';
o.nickname = nn;
});
socket.on('gauntlet-crown-lobby-sync', (d) => {
if (!d || typeof d.readyMap !== 'object') return;
gauntletCrownLobbyReadyMap = { ...d.readyMap };
@@ -20415,6 +20662,16 @@
if (!data || !Array.isArray(data.bots)) return;
/* quiz เกมจริง: server เป็นเจ้าของบอท (quiz-bot-move) + roster (play-roster) → ไม่รับ fill-bot-state ทับ (กันบอทวาป/ผีหลุด) */
if (isQuiz() && detectiveCaseFillBots) return;
/* MG2 เกมจริง: server เป็นเจ้าของบอท (gauntlet-sync) → ไม่รับ fill-bot-state ทับ */
if (isGauntlet() && detectiveCaseFillBots) return;
/* MG4 เกมจริง: server เป็นเจ้าของบอท (quiz-carry-bot-move) → ไม่รับ fill-bot-state ทับ */
if (quizCarryServerAuthActive()) return;
/* MG5 เกมจริง: server เป็นเจ้าของบอท (jump-survive-bot-move) → ไม่รับ fill-bot-state ทับ (กันบอทวาป) */
if (jumpSurviveServerAuthActive()) return;
/* MG6 เกมจริง: server เป็นเจ้าของบอท (space-shooter-bot-move) → ไม่รับ fill-bot-state ทับ */
if (spaceShooterServerAuthActive()) return;
/* MG7 เกมจริง: server เป็นเจ้าของบอท (balloon-boss-bot-move) → ไม่รับ fill-bot-state ทับ */
if (balloonBossServerAuthActive()) return;
/* force รายชื่อ "คนจริง" ให้ตรง host — ลบผี (peer ที่ host ไม่มี แต่ค้างบน client จาก reconnect/snapshot)
id-based (ไม่ชนชื่อ default). debounce 3 ครั้งติด กัน peer ที่ host ยังไม่เห็นชั่วคราวถูกลบผิด (self-heal) */
if (Array.isArray(data.humans)) {
@@ -20515,7 +20772,7 @@
/* MG3 stack (shared tower): รับผลการวางจากเจ้าของตา → เข้าคิว แล้วทยอย apply ทีละอัน (กันทิ้งดรอปตอนบล็อกกำลังร่วง = ตึก/% ต่างกัน) */
socket.on('stack-drop', (d) => {
if (!d || !mapData || !isStack() || !stackMini || !detectiveCaseFillBots) return;
if (!d || !mapData || !isStack() || !stackMini || !(detectiveCaseFillBots || stackServerAuthPlay)) return;
const hit = {
miss: !!d.miss, perfect: !!d.perfect, pts: Number(d.pts) || 0, progressDelta: Number(d.progressDelta) || 0,
landOffsetTiles: Number(d.landOffsetTiles) || 0,
@@ -20527,6 +20784,7 @@
stackRemoteDropQueue.push({
hit: hit, seat: Number(d.seat) || 1, heavy: !!d.heavy,
theme: parsePlayLobbyThemeIndex(d.theme) || null, /* สีจาก host → freeze บนบล็อกที่วาง (ตรงทุกจอ) */
phase: Number(d.phase), /* เฟส ณ ปล่อยจาก server → snap เชือกให้ตรงบล็อก */
/* เก็บ id ของคนวาง (ไม่ใช่แค่ human: true) → คะแนนเข้าคนที่วางจริง ไม่เข้า me ทุกครั้ง */
actor: d.isBot ? { botId: String(d.actorId || '') } : { human: true, id: d.actorId != null ? String(d.actorId) : null },
});
@@ -20534,22 +20792,46 @@
/* MG3 stack (shared tower): รับเฟสแกว่งจากเจ้าของตา → mirror บล็อกที่กำลังแกว่ง (เห็นเหมือนกันจนกว่าจะถึงตาตัวเอง) */
socket.on('stack-swing', (d) => {
if (!d || !mapData || !isStack() || !stackMini || !detectiveCaseFillBots) return;
if (isMyStackTurn()) return; /* ตาเรา → คุมเฟสเอง */
if (isMePlayHost() && isStackCurrentBotTurnPlay()) return; /* host คุมบอทเอง */
if (!d || !mapData || !isStack() || !stackMini || !(detectiveCaseFillBots || stackServerAuthPlay)) return;
if (!stackServerAuthPlay) {
if (isMyStackTurn()) return; /* ตาเรา → คุมเฟสเอง */
if (isMePlayHost() && isStackCurrentBotTurnPlay()) return; /* host คุมบอทเอง */
}
const sp = Number(d.speed);
if (Number.isFinite(sp) && sp > 0) stackMini.phaseSpeed = sp; /* ใช้ความเร็วแกว่งจาก server → local advance ตรงกัน */
/* server-auth: ยึด swingAmp+topCenterX ตามโมเดล server เป๊ะ → บล็อกที่ห้อย (sin*amp) ตรงกับ landOffset ตอนปล่อย
(เดิม client คำนวณ swingAmp เองมี "rel boost" ที่ server ไม่มี → แกว่งกว้างกว่า บล็อกเลยวาปตอนวาง) */
if (stackServerAuthPlay) {
const sa = Number(d.swingAmp);
if (Number.isFinite(sa) && sa > 0 && !stackFall && !stackMini.settling) stackMini.swingAmp = sa;
const tcx = Number(d.topCenterX);
if (Number.isFinite(tcx) && !stackFall && !stackMini.settling) stackMini.topCenterX = tcx;
}
const ph = Number(d.phase);
if (Number.isFinite(ph)) stackMini.phase = ph;
if (Number.isFinite(ph)) {
if (stackServerAuthPlay && !stackFall && !stackMini.settling) {
/* server-auth: ปล่อยเฟส local เดินเองที่ความเร็ว server (ลื่น 60fps) — "ไม่" ดึงกลับเข้าหา sample ที่มาช้า
เพราะ sample จาก server เป็นค่าของอดีต (network latency) → local เดินนำหน้าเสมอ การ chase กลับ = บล็อกสะดุด/ถอยหลังทุก ~60ms (อาการกระตุก)
จึง snap เฉพาะตอน drift ใหญ่จริง (เปลี่ยนตา/ดรอป/หลุด sync) นอกนั้นเดิน local ล้วน */
let diff = ph - stackMini.phase;
diff = ((diff + Math.PI) % (Math.PI * 2) + Math.PI * 2) % (Math.PI * 2) - Math.PI;
if (Math.abs(diff) > Math.PI * 0.6) stackMini.phase = ph;
} else {
stackMini.phase = ph;
}
}
});
/* MG3 stack — non-host รับลำดับตา+index จาก host (single source of truth) → เลิกแยกตึก/ตาเหลื่อม */
socket.on('stack-state', (d) => {
if (!d || !mapData || !isStack() || !detectiveCaseFillBots) return;
if (d && d.srv) stackServerAuthPlay = true; /* สัญญาณ: server เป็นเจ้าของเกม stack รอบนี้ */
if (!d || !mapData || !isStack() || !(detectiveCaseFillBots || stackServerAuthPlay)) return;
/* ได้รับ stack-state = server relay จาก "host จริง" (socket.to ไม่ส่งกลับผู้ส่ง) → เรา "ไม่ใช่ host"
แก้ playHostId ที่ผิด (dual-host: เข้าใจผิดว่าเป็น host แล้ว sim เอง = ลำดับ/สี block หลุดกับ host) */
if (d.hostId != null && myId != null && String(d.hostId) !== String(myId)) {
if (String(playHostId) !== String(d.hostId)) playHostId = d.hostId;
}
if (isMePlayHost()) return;
if (isMePlayHost() && !stackServerAuthPlay) return; /* server-auth: host ก็ adopt order จาก server */
if (Array.isArray(d.order)) {
stackPreviewTurnOrder = d.order.map((e) => ({
kind: e.kind, seat: e.seat,
@@ -20584,7 +20866,7 @@
/* MG3 stack — host ตัดสินจบรอบ → non-host จบพร้อมกันด้วย outcome เดียวกัน (กัน "จบไม่พร้อมกัน") */
socket.on('stack-over', (d) => {
if (!mapData || !isStack() || !detectiveCaseFillBots) return;
if (!mapData || !isStack() || !(detectiveCaseFillBots || stackServerAuthPlay)) return;
const outcome = (d && typeof d.outcome === 'string') ? d.outcome : 'time_up';
finishStackTowerMissionLocalPlay(outcome);
});
@@ -20593,6 +20875,7 @@
fast-forward ตำแหน่งตามเวลาที่ผ่านไปจาก t0 (กระสุนผู้เล่นไม่มีแรงโน้มถ่วง → ตำแหน่ง = spawn + v*elapsed เป๊ะทุกเครื่อง) */
socket.on('balloon-boss-shot', (d) => {
if (!d || !mapData || mapData.gameType !== 'balloon_boss') return;
if (specialQuizFreeze) return; /* คำถามพิเศษ → ทิ้งกระสุนที่เข้ามา (กันค้างสะสมแล้วไหลรัวหลังตอบ) */
if (d.ownerId != null && myId != null && String(d.ownerId) === String(myId)) return; /* ของตัวเองเพิ่มไปแล้ว */
const sx = Number(d.sx), sy = Number(d.sy), vx = Number(d.vx), vy = Number(d.vy);
if (![sx, sy, vx, vy].every(Number.isFinite)) return;
@@ -20645,6 +20928,7 @@
/* MG6 space_shooter — รับกระสุนผู้เล่น/บอทคนอื่น (ตรงดิ่งขึ้น vy คงที่) → ตำแหน่ง = y + vy*elapsed เป๊ะทุกเครื่อง */
socket.on('space-shooter-shot', (d) => {
if (!d || !mapData || mapData.gameType !== 'space_shooter') return;
if (specialQuizFreeze) return; /* คำถามพิเศษ → ทิ้งกระสุนที่เข้ามา (กันค้างสะสมแล้วไหลรัวหลังตอบ) */
if (d.ownerId != null && myId != null && String(d.ownerId) === String(myId)) return;
const x = Number(d.x), y = Number(d.y), vy = Number(d.vy);
if (![x, y, vy].every(Number.isFinite)) return;
@@ -20840,7 +21124,7 @@
[...others.keys()].forEach((id) => {
if (isPreviewBotId(id)) return;
const oo = others.get(id);
if (oo && oo.isQuizTakeoverBot) return; /* บอทที่แปลงจากคนหลุด — ไม่อยู่ใน peers แล้ว แต่ห้าม prune */
if (oo && (oo.isQuizTakeoverBot || oo.isGauntletTakeoverBot)) return; /* บอทที่แปลงจากคนหลุด — ไม่อยู่ใน peers แล้ว แต่ห้าม prune */
if (myId != null && String(id) === String(myId)) return;
if (!live.has(String(id))) { others.delete(id); removed++; }
});
@@ -20861,6 +21145,141 @@
}
});
/* คนหลุด MG2 → server แปลงเป็นบอท "ในที่เดิม" (สี/ตำแหน่ง/คะแนนเดิม) → client เก็บ avatar ไว้ (ไม่ลบ) แล้วให้ server ขับผ่าน gauntlet-sync */
socket.on('gauntlet-player-to-bot', (d) => {
if (!d || d.id == null) return;
const o = others.get(d.id) || others.get(String(d.id));
if (o) {
o.isGauntletTakeoverBot = true;
o.tx = o.x; o.ty = o.y; /* หยุดนิ่งจนกว่า gauntlet-sync ตัวแรกจะขับ */
if (o.nickname && String(o.nickname).indexOf('(บอท)') < 0) o.nickname = String(o.nickname).trim() + ' (บอท)';
}
});
/* MG3 stack — ผู้เล่นหลุดกลางรอบ → server เปลี่ยน seat เป็นบอท (เก็บ avatar เดิม) → mark + ติดป้าย */
socket.on('stack-player-to-bot', (d) => {
if (!d || d.id == null) return;
const o = others.get(d.id) || others.get(String(d.id));
if (o) {
o.isStackTakeoverBot = true;
if (o.nickname && String(o.nickname).indexOf('(บอท)') < 0) o.nickname = String(o.nickname).trim() + ' (บอท)';
}
});
/* MG5 jump_survive — บอท server-authoritative: รับตำแหน่ง/ทิศ/anim/ตาย แล้ว lerp ใน jumpSurviveTickFrame (o.tx/o.ty) */
socket.on('jump-survive-bot-move', (d) => {
if (!d || !Array.isArray(d.bots) || !isJumpSurvive()) return;
jumpSurviveServerAuthPlay = true; /* สัญญาณ: server เป็นเจ้าของบอท/รอบรอบนี้ */
if (isJumpSurviveMissionPregameBlockingPlay()) return; /* ยังนับถอยหลัง 3-2-1 → อย่าเพิ่งขยับบอท (กันวาปก่อน "1") */
d.bots.forEach((rb) => {
if (!rb || typeof rb.id !== 'string') return;
const o = others.get(rb.id);
if (!o) return; /* avatar บอทยังไม่ถูกสร้างฝั่ง client — รอ rebalancePreviewBots */
/* lerp ปกติ; แต่ delta ใหญ่ (>3 tile = spawn เริ่ม/หลุด sync) → snap ทันที กันบอทไถลข้ามจอ */
if (Number.isFinite(rb.x)) { const big = !Number.isFinite(o.x) || Math.abs(rb.x - o.x) > 3; o.tx = rb.x; if (big) o.x = rb.x; }
if (Number.isFinite(rb.y)) { const big = !Number.isFinite(o.y) || Math.abs(rb.y - o.y) > 3; o.ty = rb.y; if (big) o.y = rb.y; }
if (rb.dir) o.direction = rb.dir;
o.botIsWalking = !!rb.walking;
o.jumpSurviveRemoteWalking = !!rb.walking;
if (rb.elim && !o.jumpSurviveEliminated) {
o.jumpSurviveEliminated = true;
if (Number.isFinite(rb.x)) o.x = rb.x;
if (Number.isFinite(rb.y)) o.y = rb.y; /* ตาย → snap ไม่ต้อง lerp ต่อ */
}
});
});
/* MG5 — คนหลุด → server แปลงเป็นบอท "ในที่เดิม" → client คง avatar + ติดป้าย (บอท) แล้วให้ server ขับผ่าน jump-survive-bot-move */
socket.on('jump-survive-player-to-bot', (d) => {
if (!d || d.id == null) return;
jumpSurviveServerAuthPlay = true;
const o = others.get(d.id) || others.get(String(d.id));
if (o) {
o.isJumpSurviveTakeoverBot = true;
o.tx = o.x; o.ty = o.y; /* นิ่งจนกว่า bot-move ตัวแรกจะขับ */
if (o.nickname && String(o.nickname).indexOf('(บอท)') < 0) o.nickname = String(o.nickname).trim() + ' (บอท)';
}
});
/* MG5 — server เป็นเจ้าของจบรอบ (หมดเวลา/ตายหมด) → ทุกเครื่องจบพร้อมกัน (host หลุดก็จบได้) */
socket.on('jump-survive-ended', (d) => {
if (!isJumpSurvive()) return;
jumpSurviveServerAuthPlay = true;
const reason = (d && d.reason === 'all_dead') ? 'all_dead' : 'time_up';
try { if (typeof endJumpSurviveMissionRound === 'function') endJumpSurviveMissionRound(reason); } catch (e) { /* ignore */ }
});
/* MG6 space_shooter — บอท server-authoritative: รับตำแหน่งยาน (cx/cy world px)/คะแนน/ตาย → ตั้งเป้าแล้ว lerp ใน tick (กันกระตุก) */
socket.on('space-shooter-bot-move', (d) => {
if (!d || !Array.isArray(d.bots) || !isSpaceShooter()) return;
spaceShooterServerAuthPlay = true; /* สัญญาณ: server เป็นเจ้าของบอท/รอบรอบนี้ */
d.bots.forEach((rb) => {
if (!rb || typeof rb.id !== 'string') return;
const o = others.get(rb.id);
if (!o) return; /* avatar บอทยังไม่ถูกสร้าง — รอ rebalancePreviewBots */
/* ตั้ง "เป้าหมาย" แล้ว lerp ใน spaceShooterTickFrame (เดิม snap ทุก ~60ms → กระตุก); delta ใหญ่ค่อย snap */
if (Number.isFinite(rb.cx)) { const big = !Number.isFinite(o.spaceShooterCx) || Math.abs(rb.cx - o.spaceShooterCx) > 140; o.spaceShooterTargetCx = rb.cx; if (big) o.spaceShooterCx = rb.cx; }
if (Number.isFinite(rb.cy)) { const big = !Number.isFinite(o.spaceShooterCy) || Math.abs(rb.cy - o.spaceShooterCy) > 140; o.spaceShooterTargetCy = rb.cy; if (big) o.spaceShooterCy = rb.cy; }
o.direction = rb.dir || 'up';
if (rb.score != null) o.spaceShooterScore = Math.max(0, rb.score | 0);
/* จำนวนครั้งที่บอทโดนอุกาบาต (server รวมจาก host) → วาด overlay รอยร้าวบนยานบอท เหมือนคนเล่น */
if (Number.isFinite(rb.hits)) o.spaceShooterHits = Math.max(0, rb.hits | 0);
if (rb.elim && !o.spaceShooterEliminated) {
o.spaceShooterEliminated = true;
o.spaceShooterTargetCx = null; o.spaceShooterTargetCy = null;
/* บอทโดนอุกาบาตตาย → โชว์ระเบิด + OUT ให้ทุกเครื่องเห็นพร้อมกัน (ตำแหน่งจาก server) */
try {
if (Number.isFinite(o.spaceShooterCx) && Number.isFinite(o.spaceShooterCy)) {
spaceShooterSpawnAsteroidExplosion(o.spaceShooterCx, o.spaceShooterCy, 26);
spaceShooterPopups.push({ x: o.spaceShooterCx, y: o.spaceShooterCy - 50, text: 'OUT', until: Date.now() + 1400 });
}
} catch (e) { /* ignore */ }
}
try { syncSpaceShooterFootFromShipCenter(o); o.tx = o.x; o.ty = o.y; } catch (e) { /* ignore */ }
});
});
/* MG6 — คนหลุด → server แปลงเป็นบอท "ในที่เดิม" → client คง avatar + ติดป้าย (บอท) แล้วให้ server ขับผ่าน space-shooter-bot-move */
socket.on('space-shooter-player-to-bot', (d) => {
if (!d || d.id == null) return;
spaceShooterServerAuthPlay = true;
const o = others.get(d.id) || others.get(String(d.id));
if (o) {
o.isSpaceShooterTakeoverBot = true;
if (o.nickname && String(o.nickname).indexOf('(บอท)') < 0) o.nickname = String(o.nickname).trim() + ' (บอท)';
}
});
/* MG7 balloon_boss — บอท server-authoritative: รับตำแหน่ง (cx/cy world px)/คะแนน/ดาเมจบอส/ลูกโป่ง/ตาย → สแนปวาดตรงๆ */
socket.on('balloon-boss-bot-move', (d) => {
if (!d || !Array.isArray(d.bots) || !isBalloonBoss()) return;
balloonBossServerAuthPlay = true; /* สัญญาณ: server เป็นเจ้าของบอท/รอบ/HP บอสรอบนี้ */
d.bots.forEach((rb) => {
if (!rb || typeof rb.id !== 'string') return;
const o = others.get(rb.id);
if (!o) return; /* avatar บอทยังไม่ถูกสร้าง — รอ rebalancePreviewBots */
if (Number.isFinite(rb.cx)) o.balloonBossCx = rb.cx;
if (Number.isFinite(rb.cy)) o.balloonBossCy = rb.cy;
o.direction = 'down';
if (rb.score != null) o.balloonBossScore = Math.max(0, rb.score | 0);
if (rb.dmg != null) o.balloonBossBossDmg = Math.max(0, rb.dmg | 0);
if (rb.balloons != null) o.balloonBossBalloons = Math.max(0, rb.balloons | 0);
if (rb.elim) o.balloonBossEliminated = true;
try { syncBalloonBossFootFromCenter(o); o.tx = o.x; o.ty = o.y; } catch (e) { /* ignore */ }
});
});
/* MG7 — คนหลุด → server แปลงเป็นบอท "ในที่เดิม" → client คง avatar + ติดป้าย (บอท) แล้วให้ server ขับผ่าน balloon-boss-bot-move */
socket.on('balloon-boss-player-to-bot', (d) => {
if (!d || d.id == null) return;
balloonBossServerAuthPlay = true;
const o = others.get(d.id) || others.get(String(d.id));
if (o) {
o.isBalloonBossTakeoverBot = true;
if (o.nickname && String(o.nickname).indexOf('(บอท)') < 0) o.nickname = String(o.nickname).trim() + ' (บอท)';
}
});
/* บอท MG1 quiz: server เป็นเจ้าของการเดิน (host หลุดก็ไม่หยุด) → รับตำแหน่ง แล้ว lerp ใน tick (o.tx/o.ty)
รับทุก quiz (ไม่ผูก detectiveCaseFillBots) → รองรับ takeover bot แม้เริ่มเกมไม่มีบอท */
socket.on('quiz-bot-move', (d) => {
@@ -21423,6 +21842,44 @@
}
me.gauntletEliminated = !!p.gauntletEliminated;
clampGauntletCrownHeistEntityXInPlacePlay(me);
} else if (isPreviewBotId(p.id) && p.isBot && isGauntlet() && detectiveCaseFillBots
&& !(playBannedBotSlot >= 0 && pid === (PREVIEW_BOT_PREFIX + playBannedBotSlot))) {
/* บอท MG2 server-authoritative — รับตำแหน่ง/คะแนน/elim จาก server (entity/สี client สร้างจาก id เดียวกัน)
ข้ามบอทที่ถูกแบนรอบนี้ (กันบอทแบนฟื้นจาก sync) */
let o = others.get(p.id) ?? others.get(pid);
const px = Number(p.x);
const py = Number(p.y);
if (!o && Number.isFinite(px) && Number.isFinite(py)) {
o = {
x: px, y: py, tx: px, ty: py,
direction: p.direction || 'right',
nickname: 'บอท',
characterId: null,
spawnJoinOrder: 999,
playTint: playTintForPreviewBotId(p.id) || resolvePlayTintForPeer(p.id),
botTier: 'avg',
gauntletJumpTicks: 0, gauntletJumpVis: 0, gauntletScore: 0,
gauntletEliminated: !!p.gauntletEliminated,
_isServerGauntletBot: true,
};
others.set(p.id, o);
}
if (o) {
o._serverConfirmedAt = Date.now();
const prevSc = Math.max(0, Number(o.gauntletScore) || 0);
if (Number.isFinite(px)) o.tx = px;
if (Number.isFinite(py)) o.ty = py;
if (p.direction) o.direction = p.direction;
const jt = Number(p.gauntletJumpTicks);
o.gauntletJumpTicks = Number.isFinite(jt) ? jt : 0;
if (o.gauntletJumpVis == null) o.gauntletJumpVis = o.gauntletJumpTicks;
const sc = Number(p.gauntletScore);
const newSc = Number.isFinite(sc) ? Math.max(0, sc) : 0;
o.gauntletScore = newSc;
if (isGauntletCrownHeistMapPlay() && prevSc - newSc >= 10) o.gauntletCrownPenaltyFxUntil = Date.now() + 1100;
o.gauntletEliminated = !!p.gauntletEliminated;
clampGauntletCrownHeistEntityXInPlacePlay(o);
}
} else if (!isPreviewBotId(p.id)) {
let o = others.get(p.id) ?? others.get(pid);
const px = Number(p.x);
@@ -21446,6 +21903,7 @@
spaceShooterScore: 0, balloonBossScore: 0, balloonBossBossDmg: 0,
balloonBossBalloons: balloonBossBalloonsStartPlay(), balloonBossEliminated: false,
quizCarryHeld: null, bannedSpectator: false,
isGauntletTakeoverBot: !!p.isTakeover,
_notInHostRoster: 0,
};
others.set(p.id, o);
@@ -21454,6 +21912,11 @@
if (o) {
o._serverConfirmedAt = Date.now();
o._notInHostRoster = 0;
/* บอท takeover (คนหลุด): server ส่งชื่อ+"(บอท)" มาด้วย → คงชื่อ/สถานะบอทไว้ทุก sync (กันชื่อกลับเป็นค่าเดิม/ผู้เล่น) */
if (p.isTakeover) {
o.isGauntletTakeoverBot = true;
if (p.nickname && o.nickname !== p.nickname) o.nickname = p.nickname;
}
const prevSc = Math.max(0, Number(o.gauntletScore) || 0);
if (Number.isFinite(px)) o.tx = px;
if (Number.isFinite(py)) o.ty = py;
@@ -21484,7 +21947,7 @@
[...others.keys()].forEach((id) => {
if (isPreviewBotId(id)) return;
const oo = others.get(id);
if (!oo || oo.isQuizTakeoverBot) return;
if (!oo || oo.isQuizTakeoverBot || oo.isGauntletTakeoverBot) return;
if (myId != null && String(id) === String(myId)) return;
if (syncLive.has(String(id))) { oo._notInGSync = 0; return; }
oo._notInGSync = (oo._notInGSync || 0) + 1;
@@ -25734,11 +26197,18 @@
const stepGauntletJumpVis = (vis, tgt) => {
const t = Number(tgt) || 0;
let v = Number(vis) || 0;
const d = t - v;
const k = d < 0 ? 0.68 : 0.4;
return v + d * k;
/* air = ticks เหลือ (เริ่มกระโดด = สูงสุด แล้วนับถอยหลังลง 0); height = sin arc
ขาขึ้น/เริ่มกระโดด (t >= v): snap ทันที — กัน "เด้งปลอม" ตอน vis ไล่ขึ้นจาก 0→ticks (เคยลอยค้าง/วาป)
ขาลง (นับถอยหลัง t < v): glide นุ่มระหว่าง tick + snap เมื่อใกล้ถึง — กันลอยค้าง asymptote/วาปลง */
if (t >= v) return t;
v = v + (t - v) * 0.34;
if (v - t < 0.12) v = t;
return v;
};
meGauntletJumpVis = stepGauntletJumpVis(meGauntletJumpVis, meGauntletJumpTicks);
/* เฟสเดินเข้าเส้นชัย/freeze → บังคับไม่มีท่ากระโดด (ทุกคนเดินเข้าเส้น ไม่ค้างลอย) */
const gauntletFinishWalkPose = gauntletCrownRunwayWalkActive || gauntletCrownRunwayBgFinishLatched;
if (gauntletFinishWalkPose) { meGauntletJumpTicks = 0; meGauntletJumpVis = 0; }
else meGauntletJumpVis = stepGauntletJumpVis(meGauntletJumpVis, meGauntletJumpTicks);
const mp = lerpGauntletEntityPos(me.x, me.y, me.tx, me.ty);
me.x = mp.nx;
me.y = mp.ny;
@@ -25746,6 +26216,7 @@
const op = lerpGauntletEntityPos(o.x, o.y, o.tx, o.ty);
o.x = op.nx;
o.y = op.ny;
if (gauntletFinishWalkPose) { o.gauntletJumpTicks = 0; o.gauntletJumpVis = 0; return; }
if (o.gauntletJumpVis == null) o.gauntletJumpVis = o.gauntletJumpTicks || 0;
o.gauntletJumpVis = stepGauntletJumpVis(o.gauntletJumpVis, o.gauntletJumpTicks || 0);
});
@@ -25764,12 +26235,12 @@
/* บอท preview ขยับที่ o.x/o.y โดยตรง — ห้าม LERP กับ tx/ty เพราะ stepPreviewBots เคยตั้ง tx=ก่อนเดิน ทำให้โดนดึงถอย ~20%/เฟรม กระตุกและแทบไม่ไป */
others.forEach((o, id) => {
/* host เดินบอทเองตรงๆ (o.x/o.y) → ข้าม lerp; แต่ client อื่นต้อง lerp ตามตำแหน่งที่ host ส่งมา (o.tx/o.ty)
ยกเว้น quiz เกมจริง: server เดินบอท → ทุกคน (รวม host) lerp ตาม server */
if (playBotsEnabled() && isPreviewBotId(id) && isMePlayHost() && !(isQuiz() && detectiveCaseFillBots)) return;
ยกเว้น server-auth (MG1 quiz / MG4 quiz_carry): server เดินบอท → ทุกคน (รวม host) lerp ตาม server */
if (playBotsEnabled() && isPreviewBotId(id) && isMePlayHost() && !(isQuiz() && detectiveCaseFillBots) && !quizCarryServerAuthActive()) return;
/* MG4 (quiz_carry) + MG1 (quiz mng8a80o) บอทเดินต่อเนื่องเร็ว → non-host lerp 0.2 จะตามหลัง host ~1-2 ช่อง ("เดินไม่ตรงกัน")
เร่ง lerp เฉพาะบอท quiz/quiz_carry ให้ตามตำแหน่ง host ทันขึ้น (ไม่แตะเกมอื่น/ผู้เล่นจริง) */
/* บอท MG1 server-driven: server ส่งทุก 60ms → lerp นุ่ม 0.22 (เดิม 0.5 = ถึงเป้าใน 1-2 เฟรมแล้วค้าง = กระตุก) */
const serverDrivenQuizBot = isPreviewBotId(id) && isQuiz() && detectiveCaseFillBots;
/* บอท MG1/MG4 server-driven: server ส่งทุก 60ms → lerp นุ่ม 0.22 (เดิม 0.5 = ถึงเป้าใน 1-2 เฟรมแล้วค้าง = กระตุก) */
const serverDrivenQuizBot = isPreviewBotId(id) && ((isQuiz() && detectiveCaseFillBots) || (isQuizCarry() && quizCarryServerAuthActive()));
const lerpF = serverDrivenQuizBot ? 0.22 : ((isPreviewBotId(id) && (isQuizCarry() || isQuiz())) ? 0.5 : LERP);
const lerpFN = 1 - Math.pow(1 - lerpF, playDtMul); /* frame-rate-normalized → นุ่มเท่ากันทุก FPS */
if (o.tx != null) o.x += (o.tx - o.x) * lerpFN;
File diff suppressed because it is too large Load Diff
+8 -5
View File
@@ -20,7 +20,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;800&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/style.css?v=20260506-stack-tower-cover">
<link rel="stylesheet" href="css/style.css?v=20260628-stack-tower-contain">
<!-- Minigame-2 HOW TO PLAY mock (01-howto) — layer positions / popup / status / btn -->
<link rel="stylesheet" href="/onlydesign/Cut%20[Justice%20Game]_full1.1/Minigame-2/assets/css/game-hud.css?v=0.006130015">
<link rel="stylesheet" href="/onlydesign/Cut%20[Justice%20Game]_full1.1/Minigame-2/assets/css/howto.css?v=0.006130015">
@@ -186,10 +186,13 @@
image-rendering: pixelated;
image-rendering: crisp-edges;
}
/* Stack Tower (mnn93hpi): แคนวาสบัฟเฟอร์ 16:9 — object-fit: cover เติม stage ไม่ให้แถบดำ (contain เคยทำขอบว่าง) */
/* Stack Tower (mnn93hpi): แคนวาสบัฟเฟอร์ 16:9 — ใช้ contain เพื่อให้เฟรมกล้อง "คงที่ทุก resolution"
(cover สเกลตามด้านใหญ่กว่า → เปลี่ยน resolution = เฟรม/ซูมเพี้ยน + บล็อกห้อย/จุดวางหลุดเฟรม)
แถบ letterbox กลืนสีฉาก (#0a0e22) จึงไม่ดูเป็นขอบดำจอเสีย */
html.play-stack-tower-pixel-canvas #play-canvas-stage.play-canvas-stage {
position: relative;
min-height: 0;
background: #0a0e22;
}
html.play-stack-tower-pixel-canvas #play-canvas-stage #game-canvas {
position: absolute;
@@ -198,9 +201,9 @@
height: 100% !important;
max-width: none;
max-height: none;
/* contain ทำให้บัฟเฟอร์ 16:9 ถูกย่อกลาง stage — ขอบบน/ล่างหรือซ้าย/ขวาว่างดำเหมือนจอเสีย; cover เติมเต็ม stage (อาจ crop ขอบเล็กน้อย) */
object-fit: cover;
object-fit: contain;
object-position: center center;
background: #0a0e22;
touch-action: none;
image-rendering: pixelated;
image-rendering: crisp-edges;
@@ -5426,7 +5429,7 @@
<script src="/app-base.js?v=2"></script>
<script src="/Game/socket.io/socket.io.js"></script>
<script src="js/version.js?v=0.0306"></script>
<script src="js/play.js?v=0.006260138"></script>
<script src="js/play.js?v=0.006280301"></script>
<div class="version-tag">v —</div>
<style id="qc-howto-mg2-fix">
/* HOW TO PLAY ของ quiz_carry (Minigame-4) -> ให้เหมือน Minigame-2 (gch-mg2-mock)
+2230 -22
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+544
View File
@@ -1291,3 +1291,547 @@
{"t":1782559326448,"cat":"LobbyB","room":"ผผผ","who":"MONE","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782559336449,"cat":"LobbyB","room":"ผผผ","who":"MONE","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
{"t":1782559365612,"cat":"LobbyB","room":"ผผผ-1782559181164","who":"MONE","ev":"socket-connect","detail":" [+84199ms]","src":"client"}
{"t":1782579771155,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"lobby-open","detail":"space=aa-1782579767950 [+0ms]","src":"client"}
{"t":1782579771259,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"preload-start","detail":" [+97ms]","src":"client"}
{"t":1782579771260,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"manifest-done","detail":" [+97ms]","src":"client"}
{"t":1782579771262,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"mychar-tinted","detail":"roomJoinReady=false [+98ms]","src":"client"}
{"t":1782579771263,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"colors-resolved","detail":" [+98ms]","src":"client"}
{"t":1782579771274,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"preload-occupants-done","detail":" [+98ms]","src":"client"}
{"t":1782579771289,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"preload-ready(occupants)","detail":" [+99ms]","src":"client"}
{"t":1782579771391,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"socket-connect","detail":" [+234ms]","src":"client"}
{"t":1782579771397,"cat":"LobbyA","room":"aa","who":"ผู้เล่น","ev":"join","detail":"สี#5 · คน 1 + บอท 4","src":"server"}
{"t":1782579773477,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"loading-hide","detail":"เริ่มเล่นได้ [+2306ms]","src":"client"}
{"t":1782579773478,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=4 [+2305ms]","src":"client"}
{"t":1782579781256,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"lobby-open","detail":"space=aa-1782579767950 [+0ms]","src":"client"}
{"t":1782579781297,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"preload-start","detail":" [+26ms]","src":"client"}
{"t":1782579781374,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"manifest-done","detail":" [+97ms]","src":"client"}
{"t":1782579781377,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"colors-resolved","detail":" [+98ms]","src":"client"}
{"t":1782579781419,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"socket-connect","detail":" [+133ms]","src":"client"}
{"t":1782579781417,"cat":"LobbyA","room":"aa","who":"Q","ev":"join","detail":"สี#7 · คน 2 + บอท 4","src":"server"}
{"t":1782579782230,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"join-ok","detail":"peers=2 host=other map=mlsbbxfe bots=4 [+950ms]","src":"client"}
{"t":1782579785599,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+4343ms]","src":"client"}
{"t":1782579800788,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"preload-ready(occupants)","detail":" [+19539ms]","src":"client"}
{"t":1782579800790,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+19540ms]","src":"client"}
{"t":1782579800792,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"preload-occupants-done","detail":" [+19539ms]","src":"client"}
{"t":1782579811908,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+30653ms]","src":"client"}
{"t":1782579811909,"cat":"LobbyA","room":"aa-1782579767950","who":"ผู้เล่น","ev":"loading-hide","detail":"เริ่มเล่นได้ [+40757ms]","src":"client"}
{"t":1782579815341,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+34089ms]","src":"client"}
{"t":1782579819761,"cat":"LobbyB","room":"aa-1782579767950","who":"ผู้เล่น","ev":"loading-hide","detail":"เริ่มเล่นได้ [+48608ms]","src":"client"}
{"t":1782579821084,"cat":"MG1","room":"aa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-1 จริงหรือไม่ (การ์ด 1)","src":"server"}
{"t":1782579821197,"cat":"MG1","room":"aa","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782579821511,"cat":"MG1","room":"aa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782579821601,"cat":"MG1","room":"aa","who":"ผู้เล่น","ev":"join","detail":"สี#5 · คน 2 + บอท 4","src":"server"}
{"t":1782579834826,"cat":"MG1","room":"aa","who":"ผู้เล่น","ev":"host-change","detail":"host ใหม่ → Q","src":"server"}
{"t":1782579834826,"cat":"MG1","room":"aa","who":"ผู้เล่น","ev":"leave","detail":"หลุด MG1 → บอทแทนในเกม (สี/ตำแหน่งเดิม) · เหลือคน 1","src":"server"}
{"t":1782579844826,"cat":"MG1","room":"aa","who":"ผู้เล่น","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
{"t":1782579927913,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"lobby-open","detail":"space=aa-1782579767950 [+0ms]","src":"client"}
{"t":1782579927956,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"preload-start","detail":" [+36ms]","src":"client"}
{"t":1782579928042,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"colors-resolved","detail":" [+135ms]","src":"client"}
{"t":1782579928043,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"manifest-done","detail":" [+135ms]","src":"client"}
{"t":1782579928078,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"socket-connect","detail":" [+160ms]","src":"client"}
{"t":1782579928121,"cat":"LobbyB","room":"aa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782579929111,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+1195ms]","src":"client"}
{"t":1782579934433,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+6522ms]","src":"client"}
{"t":1782579944841,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"preload-occupants-done","detail":" [+16929ms]","src":"client"}
{"t":1782579944841,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+16930ms]","src":"client"}
{"t":1782579944842,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"preload-ready(occupants)","detail":" [+16930ms]","src":"client"}
{"t":1782579951830,"cat":"MG2","room":"aa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-2 วิ่งหลบสิ่งกีดขวาง (การ์ด 2)","src":"server"}
{"t":1782579952253,"cat":"MG2","room":"aa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782580028058,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"lobby-open","detail":"space=aa-1782579767950 [+0ms]","src":"client"}
{"t":1782580028122,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"preload-start","detail":" [+49ms]","src":"client"}
{"t":1782580028173,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"manifest-done","detail":" [+127ms]","src":"client"}
{"t":1782580028180,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"colors-resolved","detail":" [+128ms]","src":"client"}
{"t":1782580028212,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"socket-connect","detail":" [+154ms]","src":"client"}
{"t":1782580028254,"cat":"LobbyB","room":"aa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782580030577,"cat":"LobbyA","room":"aa-1782579767950","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=false [+2528ms]","src":"client"}
{"t":1782580031276,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+3224ms]","src":"client"}
{"t":1782580048201,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"preload-occupants-done","detail":" [+20151ms]","src":"client"}
{"t":1782580048202,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"preload-ready(occupants)","detail":" [+20152ms]","src":"client"}
{"t":1782580048204,"cat":"LobbyB","room":"aa-1782579767950","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+20152ms]","src":"client"}
{"t":1782580052993,"cat":"MG3","room":"aa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-3 Tower block (การ์ด 3)","src":"server"}
{"t":1782580053378,"cat":"MG3","room":"aa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782580057568,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5","src":"client"}
{"t":1782580063642,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(0)=[]","src":"client"}
{"t":1782580068966,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(2)=[1:7,2:1]","src":"client"}
{"t":1782580075949,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(5)=[1:7,2:1,3:2,4:3,5:4]","src":"client"}
{"t":1782580082643,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580089157,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580095658,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580102169,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580108670,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580115172,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580121677,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580128183,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580134693,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580141195,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782580147712,"cat":"MG3","room":"aa-1782579767950","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,2:1,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782583092199,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"lobby-open","detail":"space=aaa-1782583089025 [+1ms]","src":"client"}
{"t":1782583092270,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"manifest-done","detail":" [+65ms]","src":"client"}
{"t":1782583092271,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"preload-start","detail":" [+65ms]","src":"client"}
{"t":1782583092281,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"colors-resolved","detail":" [+66ms]","src":"client"}
{"t":1782583092284,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"mychar-tinted","detail":"roomJoinReady=false [+66ms]","src":"client"}
{"t":1782583092285,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"preload-occupants-done","detail":" [+67ms]","src":"client"}
{"t":1782583092302,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"preload-ready(occupants)","detail":" [+67ms]","src":"client"}
{"t":1782583092364,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"socket-connect","detail":" [+173ms]","src":"client"}
{"t":1782583092380,"cat":"LobbyA","room":"aaa","who":"ผู้เล่น","ev":"join","detail":"สี#5 · คน 1 + บอท 4","src":"server"}
{"t":1782583093234,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"loading-hide","detail":"เริ่มเล่นได้ [+1027ms]","src":"client"}
{"t":1782583093234,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=4 [+1026ms]","src":"client"}
{"t":1782583117519,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"lobby-open","detail":"space=aaa-1782583089025 [+0ms]","src":"client"}
{"t":1782583117563,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"preload-start","detail":" [+21ms]","src":"client"}
{"t":1782583117615,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"colors-resolved","detail":" [+100ms]","src":"client"}
{"t":1782583117616,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"manifest-done","detail":" [+100ms]","src":"client"}
{"t":1782583117647,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"socket-connect","detail":" [+106ms]","src":"client"}
{"t":1782583117641,"cat":"LobbyA","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 2 + บอท 4","src":"server"}
{"t":1782583118483,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"join-ok","detail":"peers=2 host=other map=mlsbbxfe bots=4 [+943ms]","src":"client"}
{"t":1782583121863,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+4343ms]","src":"client"}
{"t":1782583131552,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"preload-occupants-done","detail":" [+14035ms]","src":"client"}
{"t":1782583131554,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"preload-ready(occupants)","detail":" [+14035ms]","src":"client"}
{"t":1782583131554,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+14035ms]","src":"client"}
{"t":1782583140042,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+22528ms]","src":"client"}
{"t":1782583140064,"cat":"LobbyA","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"loading-hide","detail":"เริ่มเล่นได้ [+47874ms]","src":"client"}
{"t":1782583144535,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+27014ms]","src":"client"}
{"t":1782583146433,"cat":"LobbyB","room":"aaa-1782583089025","who":"ผู้เล่น","ev":"loading-hide","detail":"เริ่มเล่นได้ [+54245ms]","src":"client"}
{"t":1782583147124,"cat":"MG1","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-1 จริงหรือไม่ (การ์ด 1)","src":"server"}
{"t":1782583147243,"cat":"MG1","room":"aaa","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782583147545,"cat":"MG1","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782583147566,"cat":"MG1","room":"aaa","who":"ผู้เล่น","ev":"join","detail":"สี#5 · คน 2 + บอท 4","src":"server"}
{"t":1782583165312,"cat":"MG1","room":"aaa","who":"ผู้เล่น","ev":"host-change","detail":"host ใหม่ → Q","src":"server"}
{"t":1782583165312,"cat":"MG1","room":"aaa","who":"ผู้เล่น","ev":"leave","detail":"หลุด MG1 → บอทแทนในเกม (สี/ตำแหน่งเดิม) · เหลือคน 1","src":"server"}
{"t":1782583175313,"cat":"MG1","room":"aaa","who":"ผู้เล่น","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
{"t":1782583233274,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"lobby-open","detail":"space=aaa-1782583089025 [+0ms]","src":"client"}
{"t":1782583233335,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"preload-start","detail":" [+63ms]","src":"client"}
{"t":1782583233382,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"socket-connect","detail":" [+120ms]","src":"client"}
{"t":1782583233388,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"manifest-done","detail":" [+125ms]","src":"client"}
{"t":1782583233391,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"colors-resolved","detail":" [+126ms]","src":"client"}
{"t":1782583233387,"cat":"LobbyB","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782583234200,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+937ms]","src":"client"}
{"t":1782583239394,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+6130ms]","src":"client"}
{"t":1782583249750,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+16488ms]","src":"client"}
{"t":1782583249750,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"preload-occupants-done","detail":" [+16487ms]","src":"client"}
{"t":1782583249750,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"preload-ready(occupants)","detail":" [+16488ms]","src":"client"}
{"t":1782583256806,"cat":"MG2","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-2 วิ่งหลบสิ่งกีดขวาง (การ์ด 2)","src":"server"}
{"t":1782583257209,"cat":"MG2","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782583414116,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"lobby-open","detail":"space=aaa-1782583089025 [+0ms]","src":"client"}
{"t":1782583414150,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"preload-start","detail":" [+30ms]","src":"client"}
{"t":1782583414214,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"manifest-done","detail":" [+103ms]","src":"client"}
{"t":1782583414215,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"colors-resolved","detail":" [+103ms]","src":"client"}
{"t":1782583414250,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"socket-connect","detail":" [+133ms]","src":"client"}
{"t":1782583414288,"cat":"LobbyB","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782583415105,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+990ms]","src":"client"}
{"t":1782583421028,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+6916ms]","src":"client"}
{"t":1782583434536,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"preload-occupants-done","detail":" [+20424ms]","src":"client"}
{"t":1782583434537,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+20424ms]","src":"client"}
{"t":1782583434537,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"preload-ready(occupants)","detail":" [+20424ms]","src":"client"}
{"t":1782583453379,"cat":"MG7","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-7 ยิง Mega Virus (การ์ด 3)","src":"server"}
{"t":1782583453807,"cat":"MG7","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782583510309,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"lobby-open","detail":"space=aaa-1782583089025 [+0ms]","src":"client"}
{"t":1782583510350,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"preload-start","detail":" [+43ms]","src":"client"}
{"t":1782583510404,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"manifest-done","detail":" [+127ms]","src":"client"}
{"t":1782583510407,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"colors-resolved","detail":" [+128ms]","src":"client"}
{"t":1782583510443,"cat":"LobbyA","room":"aaa-1782583089025","who":"Q","ev":"socket-connect","detail":" [+152ms]","src":"client"}
{"t":1782583510484,"cat":"LobbyB","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782583511324,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+1034ms]","src":"client"}
{"t":1782583516389,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+6107ms]","src":"client"}
{"t":1782583522191,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"preload-occupants-done","detail":" [+11909ms]","src":"client"}
{"t":1782583522193,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+11909ms]","src":"client"}
{"t":1782583522193,"cat":"LobbyB","room":"aaa-1782583089025","who":"Q","ev":"preload-ready(occupants)","detail":" [+11909ms]","src":"client"}
{"t":1782583852847,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"lobby-open","detail":"space=ฟหก-1782583849918 [+0ms]","src":"client"}
{"t":1782583852885,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"preload-start","detail":" [+20ms]","src":"client"}
{"t":1782583852934,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"manifest-done","detail":" [+100ms]","src":"client"}
{"t":1782583852935,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"colors-resolved","detail":" [+101ms]","src":"client"}
{"t":1782583853014,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"socket-connect","detail":" [+143ms]","src":"client"}
{"t":1782583853016,"cat":"LobbyA","room":"ฟหก","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782583853892,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=4 [+1049ms]","src":"client"}
{"t":1782583858339,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+5504ms]","src":"client"}
{"t":1782583873837,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"preload-occupants-done","detail":" [+20996ms]","src":"client"}
{"t":1782583873838,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+20997ms]","src":"client"}
{"t":1782583873840,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"preload-ready(occupants)","detail":" [+20996ms]","src":"client"}
{"t":1782583883623,"cat":"LobbyA","room":"ฟหก-1782583849918","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+30786ms]","src":"client"}
{"t":1782583885853,"cat":"LobbyB","room":"ฟหก-1782583849918","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+33015ms]","src":"client"}
{"t":1782583887503,"cat":"MG4","room":"ฟหก","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-4 คำศัพท์ในกระบวนการยุติธรรม (การ์ด 1)","src":"server"}
{"t":1782583887959,"cat":"MG4","room":"ฟหก","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782584055690,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"lobby-open","detail":"space=aaa-1782584052736 [+0ms]","src":"client"}
{"t":1782584055734,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"preload-start","detail":" [+34ms]","src":"client"}
{"t":1782584055784,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"manifest-done","detail":" [+117ms]","src":"client"}
{"t":1782584055784,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"colors-resolved","detail":" [+118ms]","src":"client"}
{"t":1782584055827,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"socket-connect","detail":" [+155ms]","src":"client"}
{"t":1782584055828,"cat":"LobbyA","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584056577,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=5 [+872ms]","src":"client"}
{"t":1782584061842,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+6175ms]","src":"client"}
{"t":1782584080513,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"preload-occupants-done","detail":" [+24844ms]","src":"client"}
{"t":1782584080515,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"preload-ready(occupants)","detail":" [+24844ms]","src":"client"}
{"t":1782584080517,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+24845ms]","src":"client"}
{"t":1782584085328,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+29663ms]","src":"client"}
{"t":1782584087864,"cat":"LobbyB","room":"aaa-1782584052736","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+32198ms]","src":"client"}
{"t":1782584089621,"cat":"MG5","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-5 กระโดดขึ้นแท่น (การ์ด 2)","src":"server"}
{"t":1782584090039,"cat":"MG5","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584118486,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"lobby-open","detail":"space=aaa-1782584052736 [+0ms]","src":"client"}
{"t":1782584118528,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"preload-start","detail":" [+32ms]","src":"client"}
{"t":1782584118600,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"manifest-done","detail":" [+125ms]","src":"client"}
{"t":1782584118601,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"colors-resolved","detail":" [+126ms]","src":"client"}
{"t":1782584118648,"cat":"LobbyA","room":"aaa-1782584052736","who":"Q","ev":"socket-connect","detail":" [+153ms]","src":"client"}
{"t":1782584118696,"cat":"LobbyB","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584119711,"cat":"LobbyB","room":"aaa-1782584052736","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+1212ms]","src":"client"}
{"t":1782584121857,"cat":"LobbyB","room":"aaa-1782584052736","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+3382ms]","src":"client"}
{"t":1782584134868,"cat":"LobbyB","room":"aaa-1782584052736","who":"Q","ev":"preload-occupants-done","detail":" [+16392ms]","src":"client"}
{"t":1782584134869,"cat":"LobbyB","room":"aaa-1782584052736","who":"Q","ev":"preload-ready(occupants)","detail":" [+16393ms]","src":"client"}
{"t":1782584134870,"cat":"LobbyB","room":"aaa-1782584052736","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+16393ms]","src":"client"}
{"t":1782584186870,"cat":"LobbyB","room":"aaa-1782584052736","who":"Q","ev":"socket-connect","detail":" [+68400ms]","src":"client"}
{"t":1782584260208,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"lobby-open","detail":"space=ฟฟฟ-1782584257089 [+0ms]","src":"client"}
{"t":1782584260257,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-start","detail":" [+12ms]","src":"client"}
{"t":1782584260322,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"manifest-done","detail":" [+113ms]","src":"client"}
{"t":1782584260328,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"colors-resolved","detail":" [+114ms]","src":"client"}
{"t":1782584260362,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"socket-connect","detail":" [+121ms]","src":"client"}
{"t":1782584260349,"cat":"LobbyA","room":"ฟฟฟ","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584261276,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=5 [+1068ms]","src":"client"}
{"t":1782584267428,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+7215ms]","src":"client"}
{"t":1782584277634,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-occupants-done","detail":" [+17422ms]","src":"client"}
{"t":1782584277637,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+17422ms]","src":"client"}
{"t":1782584277637,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-ready(occupants)","detail":" [+17422ms]","src":"client"}
{"t":1782584286570,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+26362ms]","src":"client"}
{"t":1782584288995,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+28780ms]","src":"client"}
{"t":1782584290165,"cat":"MG2","room":"ฟฟฟ","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-2 วิ่งหลบสิ่งกีดขวาง (การ์ด 1)","src":"server"}
{"t":1782584290638,"cat":"MG2","room":"ฟฟฟ","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584350140,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"lobby-open","detail":"space=ฟฟฟ-1782584257089 [+0ms]","src":"client"}
{"t":1782584350185,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-start","detail":" [+15ms]","src":"client"}
{"t":1782584350245,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"colors-resolved","detail":" [+108ms]","src":"client"}
{"t":1782584350245,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"manifest-done","detail":" [+108ms]","src":"client"}
{"t":1782584350287,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"socket-connect","detail":" [+139ms]","src":"client"}
{"t":1782584350331,"cat":"LobbyB","room":"ฟฟฟ","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584351149,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+992ms]","src":"client"}
{"t":1782584358979,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+8836ms]","src":"client"}
{"t":1782584367188,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+17013ms]","src":"client"}
{"t":1782584372152,"cat":"MG6","room":"ฟฟฟ","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-6 ยิงอุกาบาต Violent Crime (การ์ด 2)","src":"server"}
{"t":1782584374299,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-occupants-done","detail":" [+24159ms]","src":"client"}
{"t":1782584374301,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-ready(occupants)","detail":" [+24160ms]","src":"client"}
{"t":1782584374697,"cat":"MG6","room":"ฟฟฟ","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584474562,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"lobby-open","detail":"space=ฟฟฟ-1782584257089 [+0ms]","src":"client"}
{"t":1782584474604,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-start","detail":" [+13ms]","src":"client"}
{"t":1782584474657,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"manifest-done","detail":" [+133ms]","src":"client"}
{"t":1782584474658,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"colors-resolved","detail":" [+134ms]","src":"client"}
{"t":1782584474700,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"socket-connect","detail":" [+160ms]","src":"client"}
{"t":1782584474745,"cat":"LobbyB","room":"ฟฟฟ","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584475526,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+994ms]","src":"client"}
{"t":1782584478729,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+4205ms]","src":"client"}
{"t":1782584490020,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+15491ms]","src":"client"}
{"t":1782584490020,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-ready(occupants)","detail":" [+15491ms]","src":"client"}
{"t":1782584490020,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-occupants-done","detail":" [+15490ms]","src":"client"}
{"t":1782584498177,"cat":"MG3","room":"ฟฟฟ","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-3 Tower block (การ์ด 3)","src":"server"}
{"t":1782584498619,"cat":"MG3","room":"ฟฟฟ","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584501819,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5","src":"client"}
{"t":1782584508004,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(0)=[]","src":"client"}
{"t":1782584517287,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(1)=[1:7]","src":"client"}
{"t":1782584522538,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(2)=[1:7,3:2]","src":"client"}
{"t":1782584528141,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(2)=[1:7,3:2]","src":"client"}
{"t":1782584533864,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(3)=[1:7,3:2,5:4]","src":"client"}
{"t":1782584540427,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(3)=[1:7,3:2,5:4]","src":"client"}
{"t":1782584548017,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(4)=[1:7,3:2,5:4,1:7]","src":"client"}
{"t":1782584555126,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(4)=[1:7,3:2,5:4,1:7]","src":"client"}
{"t":1782584562286,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(5)=[1:7,3:2,5:4,1:7,3:2]","src":"client"}
{"t":1782584568122,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,3:2,5:4,1:7,3:2,5:4]","src":"client"}
{"t":1782584574692,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,3:2,5:4,1:7,3:2,5:4]","src":"client"}
{"t":1782584581203,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,3:2,5:4,1:7,3:2,5:4]","src":"client"}
{"t":1782584587712,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,3:2,5:4,1:7,3:2,5:4]","src":"client"}
{"t":1782584594217,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(6)=[1:7,3:2,5:4,1:7,3:2,5:4]","src":"client"}
{"t":1782584618001,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"lobby-open","detail":"space=ฟฟฟ-1782584257089 [+0ms]","src":"client"}
{"t":1782584618086,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-start","detail":" [+56ms]","src":"client"}
{"t":1782584618154,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"colors-resolved","detail":" [+153ms]","src":"client"}
{"t":1782584618157,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"manifest-done","detail":" [+152ms]","src":"client"}
{"t":1782584618194,"cat":"LobbyA","room":"ฟฟฟ-1782584257089","who":"Q","ev":"socket-connect","detail":" [+178ms]","src":"client"}
{"t":1782584618238,"cat":"LobbyB","room":"ฟฟฟ","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584619225,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+1199ms]","src":"client"}
{"t":1782584623998,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+5995ms]","src":"client"}
{"t":1782584634119,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-occupants-done","detail":" [+16118ms]","src":"client"}
{"t":1782584634124,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"preload-ready(occupants)","detail":" [+16118ms]","src":"client"}
{"t":1782584634125,"cat":"LobbyB","room":"ฟฟฟ-1782584257089","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+16119ms]","src":"client"}
{"t":1782584638260,"cat":"MG3","room":"ฟฟฟ","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-3 Tower block (การ์ด 3)","src":"server"}
{"t":1782584638675,"cat":"MG3","room":"ฟฟฟ","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 5","src":"server"}
{"t":1782584644939,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:บอท 1 blk=1 av=1 | s2:บอท 2 blk=2 av=2 | s3:บอท 3 blk=3 av=3 | s4:บอท 4 blk=4 av=4 | s5:บอท 5 blk=5 av=5 | s6:Q blk=7 av=7","src":"client"}
{"t":1782584650740,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(0)=[]","src":"client"}
{"t":1782584657306,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(0)=[]","src":"client"}
{"t":1782584663456,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(0)=[]","src":"client"}
{"t":1782584668890,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(0)=[]","src":"client"}
{"t":1782584675277,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(0)=[]","src":"client"}
{"t":1782584680636,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(1)=[1:7]","src":"client"}
{"t":1782584687208,"cat":"MG3","room":"ฟฟฟ-1782584257089","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[Q(self)] :: s1:Q blk=7 av=7 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=5 || layers(4)=[1:7,3:2,5:4,6:5]","src":"client"}
{"t":1782585928744,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"lobby-open","detail":"space=asdad-1782585925649 [+0ms]","src":"client"}
{"t":1782585928788,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"preload-start","detail":" [+26ms]","src":"client"}
{"t":1782585928878,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"manifest-done","detail":" [+122ms]","src":"client"}
{"t":1782585928878,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"colors-resolved","detail":" [+123ms]","src":"client"}
{"t":1782585928889,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"socket-connect","detail":" [+152ms]","src":"client"}
{"t":1782585928897,"cat":"LobbyA","room":"asdad","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782585929733,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=4 [+997ms]","src":"client"}
{"t":1782585933237,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"lobby-open","detail":"space=asdad-1782585925649 [+0ms]","src":"client"}
{"t":1782585933278,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"preload-start","detail":" [+37ms]","src":"client"}
{"t":1782585933282,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"manifest-done","detail":" [+38ms]","src":"client"}
{"t":1782585933289,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"colors-resolved","detail":" [+38ms]","src":"client"}
{"t":1782585933302,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+38ms]","src":"client"}
{"t":1782585933310,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"preload-occupants-done","detail":" [+39ms]","src":"client"}
{"t":1782585933323,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"preload-ready(occupants)","detail":" [+39ms]","src":"client"}
{"t":1782585933390,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"socket-connect","detail":" [+143ms]","src":"client"}
{"t":1782585933407,"cat":"LobbyA","room":"asdad","who":"MONE","ev":"join","detail":"สี#5 · คน 2 + บอท 4","src":"server"}
{"t":1782585933975,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+5240ms]","src":"client"}
{"t":1782585934114,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"join-ok","detail":"peers=2 host=other map=mlsbbxfe bots=4 [+883ms]","src":"client"}
{"t":1782585934115,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+884ms]","src":"client"}
{"t":1782585949349,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"preload-occupants-done","detail":" [+20612ms]","src":"client"}
{"t":1782585949352,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+20613ms]","src":"client"}
{"t":1782585949353,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"preload-ready(occupants)","detail":" [+20612ms]","src":"client"}
{"t":1782585972521,"cat":"LobbyA","room":"asdad-1782585925649","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+43777ms]","src":"client"}
{"t":1782585972522,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+39289ms]","src":"client"}
{"t":1782585974944,"cat":"LobbyB","room":"asdad-1782585925649","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+46208ms]","src":"client"}
{"t":1782585977169,"cat":"MG6","room":"asdad","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-6 ยิงอุกาบาต Violent Crime (การ์ด 2)","src":"server"}
{"t":1782585977289,"cat":"MG6","room":"asdad","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782585977289,"cat":"MG6","room":"asdad","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782585977629,"cat":"MG6","room":"asdad","who":"MONE","ev":"join","detail":"สี#5 · คน 1 + บอท 4","src":"server"}
{"t":1782585977673,"cat":"MG6","room":"asdad","who":"Q","ev":"join","detail":"สี#7 · คน 2 + บอท 4","src":"server"}
{"t":1782586024010,"cat":"MG6","room":"asdad","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782586024010,"cat":"MG6","room":"asdad","who":"Q","ev":"leave","detail":"หลุด MG6 → บอทแทนในเกม (สี/ตำแหน่งเดิม) · เหลือคน 1","src":"server"}
{"t":1782586034012,"cat":"MG6","room":"asdad","who":"Q","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
{"t":1782586064783,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"lobby-open","detail":"space=asdad-1782585925649 [+0ms]","src":"client"}
{"t":1782586064854,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"colors-resolved","detail":" [+56ms]","src":"client"}
{"t":1782586064854,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"manifest-done","detail":" [+55ms]","src":"client"}
{"t":1782586064856,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"preload-start","detail":" [+54ms]","src":"client"}
{"t":1782586064901,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"preload-ready(occupants)","detail":" [+57ms]","src":"client"}
{"t":1782586064901,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"preload-occupants-done","detail":" [+56ms]","src":"client"}
{"t":1782586064904,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+56ms]","src":"client"}
{"t":1782586065049,"cat":"LobbyA","room":"asdad-1782585925649","who":"MONE","ev":"socket-connect","detail":" [+266ms]","src":"client"}
{"t":1782586065186,"cat":"LobbyB","room":"asdad","who":"MONE","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
{"t":1782586067877,"cat":"LobbyB","room":"asdad-1782585925649","who":"MONE","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+3113ms]","src":"client"}
{"t":1782586067943,"cat":"LobbyB","room":"asdad-1782585925649","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+3113ms]","src":"client"}
{"t":1782586106487,"cat":"LobbyB","room":"asdad-1782585925649","who":"MONE","ev":"socket-connect","detail":" [+41734ms]","src":"client"}
{"t":1782586149073,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"lobby-open","detail":"space=aaa-1782586146084 [+0ms]","src":"client"}
{"t":1782586149109,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"preload-start","detail":" [+21ms]","src":"client"}
{"t":1782586149189,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"manifest-done","detail":" [+118ms]","src":"client"}
{"t":1782586149191,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"colors-resolved","detail":" [+119ms]","src":"client"}
{"t":1782586149225,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"socket-connect","detail":" [+146ms]","src":"client"}
{"t":1782586149235,"cat":"LobbyA","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782586150027,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=4 [+955ms]","src":"client"}
{"t":1782586154387,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+5318ms]","src":"client"}
{"t":1782586157229,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"lobby-open","detail":"space=aaa-1782586146084 [+0ms]","src":"client"}
{"t":1782586157255,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"preload-start","detail":" [+46ms]","src":"client"}
{"t":1782586157271,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"manifest-done","detail":" [+47ms]","src":"client"}
{"t":1782586157278,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+48ms]","src":"client"}
{"t":1782586157279,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"colors-resolved","detail":" [+48ms]","src":"client"}
{"t":1782586157296,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"preload-occupants-done","detail":" [+49ms]","src":"client"}
{"t":1782586157298,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"preload-ready(occupants)","detail":" [+49ms]","src":"client"}
{"t":1782586157359,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"socket-connect","detail":" [+150ms]","src":"client"}
{"t":1782586157361,"cat":"LobbyA","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 2 + บอท 4","src":"server"}
{"t":1782586158453,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"join-ok","detail":"peers=2 host=other map=mlsbbxfe bots=4 [+1221ms]","src":"client"}
{"t":1782586158460,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+1221ms]","src":"client"}
{"t":1782586162556,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"preload-ready(occupants)","detail":" [+13483ms]","src":"client"}
{"t":1782586162557,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"preload-occupants-done","detail":" [+13483ms]","src":"client"}
{"t":1782586162558,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+13484ms]","src":"client"}
{"t":1782586179856,"cat":"LobbyA","room":"aaa-1782586146084","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+30786ms]","src":"client"}
{"t":1782586179905,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+22657ms]","src":"client"}
{"t":1782586183782,"cat":"LobbyB","room":"aaa-1782586146084","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+26573ms]","src":"client"}
{"t":1782586184872,"cat":"LobbyB","room":"aaa-1782586146084","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+35796ms]","src":"client"}
{"t":1782586193234,"cat":"MG6","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-6 ยิงอุกาบาต Violent Crime (การ์ด 2)","src":"server"}
{"t":1782586193348,"cat":"MG6","room":"aaa","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782586193348,"cat":"MG6","room":"aaa","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782586193668,"cat":"MG6","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782586193751,"cat":"MG6","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 2 + บอท 4","src":"server"}
{"t":1782586278573,"cat":"MG6","room":"aaa","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782586278574,"cat":"MG6","room":"aaa","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782586288575,"cat":"LobbyB","room":"aaa","who":"Q","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
{"t":1782586288595,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"lobby-open","detail":"space=aaa-1782586146084 [+1ms]","src":"client"}
{"t":1782586288644,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"preload-start","detail":" [+54ms]","src":"client"}
{"t":1782586288647,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"colors-resolved","detail":" [+58ms]","src":"client"}
{"t":1782586288649,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"manifest-done","detail":" [+55ms]","src":"client"}
{"t":1782586288661,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+59ms]","src":"client"}
{"t":1782586288671,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"preload-occupants-done","detail":" [+60ms]","src":"client"}
{"t":1782586288684,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"preload-ready(occupants)","detail":" [+61ms]","src":"client"}
{"t":1782586288763,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"socket-connect","detail":" [+162ms]","src":"client"}
{"t":1782586288807,"cat":"LobbyB","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
{"t":1782586289724,"cat":"LobbyB","room":"aaa-1782586146084","who":"MONE","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+1111ms]","src":"client"}
{"t":1782586289726,"cat":"LobbyB","room":"aaa-1782586146084","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+1112ms]","src":"client"}
{"t":1782586306453,"cat":"MG2","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-2 วิ่งหลบสิ่งกีดขวาง (การ์ด 1)","src":"server"}
{"t":1782586310728,"cat":"MG2","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
{"t":1782586459516,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"lobby-open","detail":"space=aaa-1782586146084 [+1ms]","src":"client"}
{"t":1782586459573,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"preload-start","detail":" [+59ms]","src":"client"}
{"t":1782586459600,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"manifest-done","detail":" [+60ms]","src":"client"}
{"t":1782586459600,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+63ms]","src":"client"}
{"t":1782586459601,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"colors-resolved","detail":" [+62ms]","src":"client"}
{"t":1782586459601,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"preload-occupants-done","detail":" [+64ms]","src":"client"}
{"t":1782586459620,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"preload-ready(occupants)","detail":" [+64ms]","src":"client"}
{"t":1782586459691,"cat":"LobbyA","room":"aaa-1782586146084","who":"MONE","ev":"socket-connect","detail":" [+173ms]","src":"client"}
{"t":1782586459747,"cat":"LobbyB","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
{"t":1782586460741,"cat":"LobbyB","room":"aaa-1782586146084","who":"MONE","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+1224ms]","src":"client"}
{"t":1782586460768,"cat":"LobbyB","room":"aaa-1782586146084","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+1225ms]","src":"client"}
{"t":1782586478049,"cat":"MG3","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-3 Tower block (การ์ด 3)","src":"server"}
{"t":1782586493665,"cat":"MG3","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
{"t":1782586504189,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:บอท 1 blk=1 av=1 | s2:บอท 2 blk=2 av=2 | s3:บอท 3 blk=3 av=3 | s4:บอท 4 blk=4 av=4 | s5:บอท 5 blk=7 av=7 | s6:MONE blk=5 av=5","src":"client"}
{"t":1782586510542,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:บอท 1 blk=1 av=1 | s2:บอท 2 blk=2 av=2 | s3:บอท 3 blk=3 av=3 | s4:บอท 4 blk=4 av=4 | s5:บอท 5 blk=7 av=7 | s6:MONE blk=5 av=5 || layers(0)=[]","src":"client"}
{"t":1782586512824,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-mismatch","detail":"บอท 5 (ฉลาด) seat6 avatar#7 แต่ block#5 (srvnull\/bc5)","src":"client"}
{"t":1782586519758,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=7 || layers(1)=[1:5]","src":"client"}
{"t":1782586526194,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=7 || layers(2)=[1:5,3:2]","src":"client"}
{"t":1782586531732,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=7 || layers(3)=[1:5,3:2,4:3]","src":"client"}
{"t":1782586536916,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=7 || layers(4)=[1:5,3:2,4:3,5:4]","src":"client"}
{"t":1782586543467,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=7 || layers(4)=[1:5,3:2,4:3,5:4]","src":"client"}
{"t":1782586551129,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=7 || layers(6)=[1:5,3:2,4:3,5:4,1:5,2:1]","src":"client"}
{"t":1782586559396,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=7 || layers(7)=[1:5,3:2,4:3,5:4,1:5,2:1,4:3]","src":"client"}
{"t":1782586566218,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=7 || layers(8)=[1:5,3:2,4:3,5:4,1:5,2:1,4:3,6:5]","src":"client"}
{"t":1782586566874,"cat":"MG3","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
{"t":1782586578508,"cat":"MG3","room":"aaa-1782586146084","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=7 av=7","src":"client"}
{"t":1782586599920,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"lobby-open","detail":"space=ฟฟ-1782586596795 [+0ms]","src":"client"}
{"t":1782586599963,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"preload-start","detail":" [+66ms]","src":"client"}
{"t":1782586599967,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"manifest-done","detail":" [+66ms]","src":"client"}
{"t":1782586599968,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"colors-resolved","detail":" [+67ms]","src":"client"}
{"t":1782586599980,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"mychar-tinted","detail":"roomJoinReady=false [+68ms]","src":"client"}
{"t":1782586599981,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"preload-occupants-done","detail":" [+68ms]","src":"client"}
{"t":1782586600007,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"preload-ready(occupants)","detail":" [+69ms]","src":"client"}
{"t":1782586600068,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"socket-connect","detail":" [+171ms]","src":"client"}
{"t":1782586600069,"cat":"LobbyA","room":"ฟฟ","who":"ผู้เล่น","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
{"t":1782586601744,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=5 [+1844ms]","src":"client"}
{"t":1782586601749,"cat":"LobbyA","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"loading-hide","detail":"เริ่มเล่นได้ [+1844ms]","src":"client"}
{"t":1782586626048,"cat":"LobbyB","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"loading-hide","detail":"เริ่มเล่นได้ [+26157ms]","src":"client"}
{"t":1782586630527,"cat":"LobbyB","room":"ฟฟ-1782586596795","who":"ผู้เล่น","ev":"loading-hide","detail":"เริ่มเล่นได้ [+30635ms]","src":"client"}
{"t":1782586633050,"cat":"MG3","room":"ฟฟ","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-3 Tower block (การ์ด 3)","src":"server"}
{"t":1782586633460,"cat":"MG3","room":"ฟฟ","who":"ผู้เล่น","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
{"t":1782586643846,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=6 av=6","src":"client"}
{"t":1782586649112,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=6 av=6 || layers(0)=[]","src":"client"}
{"t":1782586651368,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-mismatch","detail":"บอท 5 (ฉลาด) seat6 avatar#6 แต่ block#5 (srvnull\/bc5)","src":"client"}
{"t":1782586655768,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(1)=[1:5]","src":"client"}
{"t":1782586661373,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(2)=[1:5,2:1]","src":"client"}
{"t":1782586669750,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(3)=[1:5,2:1,4:3]","src":"client"}
{"t":1782586675715,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(4)=[1:5,2:1,4:3,5:4]","src":"client"}
{"t":1782586683542,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(6)=[1:5,2:1,4:3,5:4,6:5,1:5]","src":"client"}
{"t":1782586689080,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(6)=[1:5,2:1,4:3,5:4,6:5,1:5]","src":"client"}
{"t":1782586695222,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(7)=[1:5,2:1,4:3,5:4,6:5,1:5,3:2]","src":"client"}
{"t":1782586700323,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(8)=[1:5,2:1,4:3,5:4,6:5,1:5,3:2,5:4]","src":"client"}
{"t":1782586706231,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(9)=[1:5,2:1,4:3,5:4,6:5,1:5,3:2,5:4,6:5]","src":"client"}
{"t":1782586713288,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(10)=[1:5,2:1,4:3,5:4,6:5,1:5,3:2,5:4,6:5,1:5]","src":"client"}
{"t":1782586720161,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(11)=[1:5,2:1,4:3,5:4,6:5,1:5,3:2,5:4,6:5,1:5,3:2]","src":"client"}
{"t":1782586728003,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(13)=[2:1,4:3,5:4,6:5,1:5,3:2,5:4,6:5,1:5,3:2,4:3,5:4]","src":"client"}
{"t":1782586734571,"cat":"MG3","room":"ฟฟ-1782586596795","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[ผู้เล่น(self)] :: s1:ผู้เล blk=5 av=5 | s2:บอท 1 blk=1 av=1 | s3:บอท 2 blk=2 av=2 | s4:บอท 3 blk=3 av=3 | s5:บอท 4 blk=4 av=4 | s6:บอท 5 blk=5 av=6 || layers(14)=[4:3,5:4,6:5,1:5,3:2,5:4,6:5,1:5,3:2,4:3,5:4,6:5]","src":"client"}
{"t":1782588087506,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"lobby-open","detail":"space=aa-1782588084478 [+0ms]","src":"client"}
{"t":1782588087552,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"preload-start","detail":" [+37ms]","src":"client"}
{"t":1782588087610,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"manifest-done","detail":" [+115ms]","src":"client"}
{"t":1782588087619,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"colors-resolved","detail":" [+116ms]","src":"client"}
{"t":1782588087644,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"socket-connect","detail":" [+143ms]","src":"client"}
{"t":1782588087653,"cat":"LobbyA","room":"aa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782588088404,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=4 [+898ms]","src":"client"}
{"t":1782588094739,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+7247ms]","src":"client"}
{"t":1782588098584,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"lobby-open","detail":"space=aa-1782588084478 [+0ms]","src":"client"}
{"t":1782588098625,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-start","detail":" [+39ms]","src":"client"}
{"t":1782588098628,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"manifest-done","detail":" [+39ms]","src":"client"}
{"t":1782588098660,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-occupants-done","detail":" [+40ms]","src":"client"}
{"t":1782588098661,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"colors-resolved","detail":" [+40ms]","src":"client"}
{"t":1782588098661,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+40ms]","src":"client"}
{"t":1782588098664,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-ready(occupants)","detail":" [+41ms]","src":"client"}
{"t":1782588098753,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"socket-connect","detail":" [+170ms]","src":"client"}
{"t":1782588098750,"cat":"LobbyA","room":"aa","who":"MONE","ev":"join","detail":"สี#1 · คน 2 + บอท 4","src":"server"}
{"t":1782588099502,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"join-ok","detail":"peers=2 host=other map=mlsbbxfe bots=4 [+920ms]","src":"client"}
{"t":1782588099503,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+920ms]","src":"client"}
{"t":1782588106411,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"preload-occupants-done","detail":" [+18916ms]","src":"client"}
{"t":1782588106412,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"preload-ready(occupants)","detail":" [+18917ms]","src":"client"}
{"t":1782588106413,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+18917ms]","src":"client"}
{"t":1782588148441,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+49859ms]","src":"client"}
{"t":1782588148443,"cat":"LobbyA","room":"aa-1782588084478","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+60953ms]","src":"client"}
{"t":1782588153911,"cat":"LobbyB","room":"aa-1782588084478","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+66417ms]","src":"client"}
{"t":1782588162040,"cat":"MG6","room":"aa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-6 ยิงอุกาบาต Violent Crime (การ์ด 2)","src":"server"}
{"t":1782588162157,"cat":"MG6","room":"aa","who":"MONE","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782588162591,"cat":"MG6","room":"aa","who":"MONE","ev":"join","detail":"สี#1 · คน 1 + บอท 4","src":"server"}
{"t":1782588162693,"cat":"MG6","room":"aa","who":"Q","ev":"join","detail":"สี#7 · คน 2 + บอท 4","src":"server"}
{"t":1782588247961,"cat":"MG6","room":"aa","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782588247961,"cat":"MG6","room":"aa","who":"Q","ev":"leave","detail":"หลุด MG6 → บอทแทนในเกม (สี/ตำแหน่งเดิม) · เหลือคน 1","src":"server"}
{"t":1782588257962,"cat":"MG6","room":"aa","who":"Q","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
{"t":1782588276825,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"lobby-open","detail":"space=aa-1782588084478 [+0ms]","src":"client"}
{"t":1782588276873,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-start","detail":" [+41ms]","src":"client"}
{"t":1782588276875,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"colors-resolved","detail":" [+42ms]","src":"client"}
{"t":1782588276875,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+42ms]","src":"client"}
{"t":1782588276877,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"manifest-done","detail":" [+42ms]","src":"client"}
{"t":1782588276902,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-occupants-done","detail":" [+42ms]","src":"client"}
{"t":1782588276915,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-ready(occupants)","detail":" [+43ms]","src":"client"}
{"t":1782588276974,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"socket-connect","detail":" [+158ms]","src":"client"}
{"t":1782588277019,"cat":"LobbyB","room":"aa","who":"MONE","ev":"join","detail":"สี#1 · คน 1 + บอท 5","src":"server"}
{"t":1782588277920,"cat":"LobbyB","room":"aa-1782588084478","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+1079ms]","src":"client"}
{"t":1782588277922,"cat":"LobbyB","room":"aa-1782588084478","who":"MONE","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+1079ms]","src":"client"}
{"t":1782588285908,"cat":"MG2","room":"aa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-2 วิ่งหลบสิ่งกีดขวาง (การ์ด 1)","src":"server"}
{"t":1782588291272,"cat":"MG2","room":"aa","who":"MONE","ev":"join","detail":"สี#1 · คน 1 + บอท 5","src":"server"}
{"t":1782588355075,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"lobby-open","detail":"space=aa-1782588084478 [+0ms]","src":"client"}
{"t":1782588355118,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"manifest-done","detail":" [+45ms]","src":"client"}
{"t":1782588355119,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"colors-resolved","detail":" [+46ms]","src":"client"}
{"t":1782588355121,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-start","detail":" [+45ms]","src":"client"}
{"t":1782588355122,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+46ms]","src":"client"}
{"t":1782588355146,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-occupants-done","detail":" [+46ms]","src":"client"}
{"t":1782588355156,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-ready(occupants)","detail":" [+46ms]","src":"client"}
{"t":1782588355237,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"socket-connect","detail":" [+158ms]","src":"client"}
{"t":1782588355262,"cat":"LobbyB","room":"aa","who":"MONE","ev":"join","detail":"สี#1 · คน 1 + บอท 5","src":"server"}
{"t":1782588356259,"cat":"LobbyB","room":"aa-1782588084478","who":"MONE","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+1190ms]","src":"client"}
{"t":1782588356274,"cat":"LobbyB","room":"aa-1782588084478","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+1191ms]","src":"client"}
{"t":1782588362606,"cat":"MG3","room":"aa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-3 Tower block (การ์ด 3)","src":"server"}
{"t":1782588373660,"cat":"MG3","room":"aa","who":"MONE","ev":"join","detail":"สี#1 · คน 1 + บอท 5","src":"server"}
{"t":1782588378056,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=2 av=2 | s3:บอท 2 blk=3 av=3 | s4:บอท 3 blk=4 av=4 | s5:บอท 4 blk=5 av=5 | s6:บอท 5 blk=7 av=7","src":"client"}
{"t":1782588384899,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=2 av=2 | s3:บอท 2 blk=3 av=3 | s4:บอท 3 blk=4 av=4 | s5:บอท 4 blk=5 av=5 | s6:บอท 5 blk=7 av=7 || layers(0)=[]","src":"client"}
{"t":1782588387177,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-mismatch","detail":"บอท 1 (ฉลาด) seat2 avatar#2 แต่ block#1 (srvnull\/bc1) | บอท 2 (พลาดบ่อย) seat3 avatar#3 แต่ block#2 (srvnull\/bc2) | บอท 3 (พลาดบ่อย) seat4 avatar#4 แต่ block#3 (srvnull\/bc3) | บอท 4 (ฉลาด) seat5 avatar#5 แต่ block#4 (srvnull\/bc4) | บอท 5 (ฉลาด) seat6 avatar#7 แต่ block#5 (srvnull\/bc5)","src":"client"}
{"t":1782588390672,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(1)=[1:1]","src":"client"}
{"t":1782588397899,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(1)=[1:1]","src":"client"}
{"t":1782588403083,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(2)=[1:1,3:2]","src":"client"}
{"t":1782588410627,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(4)=[1:1,3:2,4:3,5:4]","src":"client"}
{"t":1782588417759,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(4)=[1:1,3:2,4:3,5:4]","src":"client"}
{"t":1782588425541,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(5)=[1:1,3:2,4:3,5:4,2:1]","src":"client"}
{"t":1782588433806,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(6)=[1:1,3:2,4:3,5:4,2:1,4:3]","src":"client"}
{"t":1782588440860,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(8)=[1:1,3:2,4:3,5:4,2:1,4:3,5:4,6:5]","src":"client"}
{"t":1782588447372,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(9)=[1:1,3:2,4:3,5:4,2:1,4:3,5:4,6:5,1:1]","src":"client"}
{"t":1782588452689,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(10)=[1:1,3:2,4:3,5:4,2:1,4:3,5:4,6:5,1:1,3:2]","src":"client"}
{"t":1782588458467,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(10)=[1:1,3:2,4:3,5:4,2:1,4:3,5:4,6:5,1:1,3:2]","src":"client"}
{"t":1782588464184,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(11)=[1:1,3:2,4:3,5:4,2:1,4:3,5:4,6:5,1:1,3:2,5:4]","src":"client"}
{"t":1782588470751,"cat":"MG3","room":"aa-1782588084478","who":"host","ev":"color-snap","detail":"order=6seat othHumans=[MONE(self)] :: s1:MONE blk=1 av=1 | s2:บอท 1 blk=1 av=2 | s3:บอท 2 blk=2 av=3 | s4:บอท 3 blk=3 av=4 | s5:บอท 4 blk=4 av=5 | s6:บอท 5 blk=5 av=7 || layers(11)=[1:1,3:2,4:3,5:4,2:1,4:3,5:4,6:5,1:1,3:2,5:4]","src":"client"}
{"t":1782588485541,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"lobby-open","detail":"space=aa-1782588084478 [+0ms]","src":"client"}
{"t":1782588485586,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-start","detail":" [+45ms]","src":"client"}
{"t":1782588485587,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"manifest-done","detail":" [+45ms]","src":"client"}
{"t":1782588485587,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"colors-resolved","detail":" [+46ms]","src":"client"}
{"t":1782588485608,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+46ms]","src":"client"}
{"t":1782588485632,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-ready(occupants)","detail":" [+46ms]","src":"client"}
{"t":1782588485632,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"preload-occupants-done","detail":" [+46ms]","src":"client"}
{"t":1782588485681,"cat":"LobbyA","room":"aa-1782588084478","who":"MONE","ev":"socket-connect","detail":" [+146ms]","src":"client"}
{"t":1782588485743,"cat":"LobbyB","room":"aa","who":"MONE","ev":"join","detail":"สี#1 · คน 1 + บอท 5","src":"server"}
{"t":1782588487484,"cat":"LobbyB","room":"aa-1782588084478","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+1949ms]","src":"client"}
{"t":1782588487486,"cat":"LobbyB","room":"aa-1782588084478","who":"MONE","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+1949ms]","src":"client"}
{"t":1782623739795,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"lobby-open","detail":"space=aaa-1782623736802 [+1ms]","src":"client"}
{"t":1782623739840,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"preload-start","detail":" [+22ms]","src":"client"}
{"t":1782623739892,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"manifest-done","detail":" [+101ms]","src":"client"}
{"t":1782623739902,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"colors-resolved","detail":" [+102ms]","src":"client"}
{"t":1782623739978,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"socket-connect","detail":" [+153ms]","src":"client"}
{"t":1782623739958,"cat":"LobbyA","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782623741409,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"join-ok","detail":"peers=1 host=me map=mlsbbxfe bots=4 [+1575ms]","src":"client"}
{"t":1782623745796,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"mychar-tinted","detail":"roomJoinReady=true [+6010ms]","src":"client"}
{"t":1782623748178,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"lobby-open","detail":"space=aaa-1782623736802 [+0ms]","src":"client"}
{"t":1782623748254,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"manifest-done","detail":" [+80ms]","src":"client"}
{"t":1782623748258,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"preload-start","detail":" [+79ms]","src":"client"}
{"t":1782623748273,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"colors-resolved","detail":" [+81ms]","src":"client"}
{"t":1782623748277,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+82ms]","src":"client"}
{"t":1782623748277,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"preload-occupants-done","detail":" [+82ms]","src":"client"}
{"t":1782623748285,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"preload-ready(occupants)","detail":" [+83ms]","src":"client"}
{"t":1782623748356,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"socket-connect","detail":" [+181ms]","src":"client"}
{"t":1782623748380,"cat":"LobbyA","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 2 + บอท 4","src":"server"}
{"t":1782623750353,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"join-ok","detail":"peers=2 host=other map=mlsbbxfe bots=4 [+2179ms]","src":"client"}
{"t":1782623750354,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+2180ms]","src":"client"}
{"t":1782623754385,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+14598ms]","src":"client"}
{"t":1782623754385,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"preload-ready(occupants)","detail":" [+14598ms]","src":"client"}
{"t":1782623754386,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"preload-occupants-done","detail":" [+14597ms]","src":"client"}
{"t":1782623763883,"cat":"LobbyA","room":"aaa-1782623736802","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+24086ms]","src":"client"}
{"t":1782623763924,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+15747ms]","src":"client"}
{"t":1782623778872,"cat":"LobbyB","room":"aaa-1782623736802","who":"Q","ev":"loading-hide","detail":"เริ่มเล่นได้ [+39085ms]","src":"client"}
{"t":1782623780149,"cat":"LobbyB","room":"aaa-1782623736802","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+31974ms]","src":"client"}
{"t":1782623784466,"cat":"MG2","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-2 วิ่งหลบสิ่งกีดขวาง (การ์ด 1)","src":"server"}
{"t":1782623784583,"cat":"MG2","room":"aaa","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782623784583,"cat":"MG2","room":"aaa","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782623784932,"cat":"MG2","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782623785047,"cat":"MG2","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 2 + บอท 4","src":"server"}
{"t":1782623796820,"cat":"MG2","room":"aaa","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782623796820,"cat":"MG2","room":"aaa","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782623806821,"cat":"MG2","room":"aaa","who":"Q","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
{"t":1782623866823,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"lobby-open","detail":"space=aaa-1782623736802 [+0ms]","src":"client"}
{"t":1782623866892,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"preload-start","detail":" [+59ms]","src":"client"}
{"t":1782623866896,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"colors-resolved","detail":" [+60ms]","src":"client"}
{"t":1782623866897,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"manifest-done","detail":" [+60ms]","src":"client"}
{"t":1782623866915,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"mychar-tinted","detail":"roomJoinReady=false [+60ms]","src":"client"}
{"t":1782623866925,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"preload-occupants-done","detail":" [+61ms]","src":"client"}
{"t":1782623866928,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"preload-ready(occupants)","detail":" [+61ms]","src":"client"}
{"t":1782623867008,"cat":"LobbyA","room":"aaa-1782623736802","who":"MONE","ev":"socket-connect","detail":" [+181ms]","src":"client"}
{"t":1782623867060,"cat":"LobbyB","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
{"t":1782623870157,"cat":"LobbyB","room":"aaa-1782623736802","who":"MONE","ev":"loading-hide","detail":"เริ่มเล่นได้ [+3337ms]","src":"client"}
{"t":1782623870157,"cat":"LobbyB","room":"aaa-1782623736802","who":"MONE","ev":"join-ok","detail":"peers=1 host=me map=mn8nx46h bots=5 [+3336ms]","src":"client"}
{"t":1782623884340,"cat":"MG6","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-6 ยิงอุกาบาต Violent Crime (การ์ด 2)","src":"server"}
{"t":1782623903616,"cat":"MG6","room":"aaa","who":"MONE","ev":"join","detail":"สี#5 · คน 1 + บอท 5","src":"server"}
+3
View File
@@ -204,6 +204,9 @@ tr:last-child td{border-bottom:none}
</style></head>
<body><div class="wrap">
<h1>Justice — Test Case / Bug List<span class="sub">รายการบัก + ผลตรวจ QA · จัดหมวด · srv1361159.hstgr.cloud/testcase</span></h1>
<div class="toolbar" style="margin:10px 0 4px">
<a class="btn v" href="test.html" style="text-decoration:none">🎮 Test Console — เทสมินิเกม + Disconnect (ปุ่มเดียว)</a>
</div>
<div class="cards" id="cards"></div>
<div class="bar">
<input type="text" id="q" placeholder="ค้นหา… (ชื่อ / รายละเอียด / คำอธิบาย)">
+242
View File
@@ -0,0 +1,242 @@
<!DOCTYPE html>
<html lang="th"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Justice — Test Console (เทสมินิเกม + Disconnect)</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root{--bg:#0a0f1e;--panel:#121a30;--panel2:#0e1526;--line:#22304f;--txt:#dce6ff;--mut:#8aa0c8;--cyan:#22d3ee;--green:#22c55e;--red:#f87171;--amber:#fbbf24;--violet:#a78bfa;}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:Kanit,system-ui,sans-serif;background:radial-gradient(circle at 50% -10%,#13213f,#070b16 70%);color:var(--txt);min-height:100vh;padding:24px 16px 80px}
.wrap{max-width:1180px;margin:0 auto}
h1{font-weight:600;font-size:25px;letter-spacing:.4px;margin-bottom:2px}
h1 .sub{color:var(--mut);font-size:13.5px;font-weight:300;display:block;margin-top:5px;line-height:1.5}
h2{font-weight:600;font-size:18px;margin:26px 0 10px;display:flex;align-items:center;gap:9px}
h2 .tag{font-size:11px;font-weight:500;padding:3px 9px;border-radius:7px;background:rgba(34,211,238,.16);color:var(--cyan)}
.topbar{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin:14px 0 6px}
.btn{border:1px solid var(--line);background:var(--panel2);color:var(--txt);border-radius:9px;padding:8px 15px;font-size:13.5px;cursor:pointer;font-family:inherit;text-decoration:none;display:inline-flex;align-items:center;gap:7px}
.btn:hover{border-color:var(--cyan)}
.btn.primary{background:var(--green);color:#042c14;border-color:var(--green);font-weight:600}
.btn.v{background:rgba(167,139,250,.16);border-color:var(--violet);color:var(--violet)}
.btn.amber{background:rgba(251,191,36,.15);border-color:var(--amber);color:var(--amber)}
.btn[disabled]{opacity:.5;cursor:not-allowed}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--amber);border-radius:10px;padding:13px 16px;font-size:13px;color:#e8d9b4;line-height:1.6;margin:10px 0}
.note b{color:var(--amber)}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px;margin-top:6px}
.mg{background:var(--panel);border:1px solid var(--line);border-radius:13px;padding:14px 15px;display:flex;flex-direction:column;gap:9px}
.mg .hd{display:flex;align-items:center;justify-content:space-between;gap:8px}
.mg .nm{font-weight:600;font-size:15px}
.mg .nm small{display:block;color:var(--mut);font-weight:300;font-size:11.5px;margin-top:2px}
.mg .mid{font-family:ui-monospace,monospace;font-size:11px;color:var(--mut)}
.auth{font-size:10.5px;font-weight:600;padding:3px 8px;border-radius:20px;white-space:nowrap}
.auth.srv{background:rgba(34,197,94,.16);color:var(--green)}
.auth.host{background:rgba(248,113,113,.16);color:var(--red)}
.auth.test{background:rgba(251,191,36,.18);color:var(--amber)}
.mgrow{display:flex;gap:7px;flex-wrap:wrap}
.mgrow .btn{flex:1;justify-content:center;padding:7px 10px;font-size:12.5px}
.panel{background:var(--panel);border:1px solid var(--line);border-radius:13px;padding:16px 18px}
.field{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:9px 0}
.field label{font-size:13px;color:var(--mut);min-width:130px}
.field input[type=number],.field select{background:var(--panel2);border:1px solid var(--line);border-radius:8px;color:var(--txt);padding:8px 11px;font-family:inherit;font-size:14px;width:90px}
.field input:focus,.field select:focus{outline:none;border-color:var(--cyan)}
.field select{width:auto;min-width:180px}
.links{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.links a{font-size:12.5px}
ol.steps{margin:8px 0 4px 20px;font-size:13px;line-height:1.8;color:#cfe0ff}
ol.steps b{color:#fff}
ol.steps code{background:var(--panel2);border:1px solid var(--line);border-radius:5px;padding:1px 6px;font-size:12px}
.toggle{display:inline-flex;align-items:center;gap:9px;cursor:pointer;user-select:none;font-size:13.5px}
.sw{width:44px;height:24px;border-radius:13px;background:#33415c;position:relative;transition:.2s;flex:0 0 auto}
.sw::after{content:"";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;transition:.2s}
.toggle.on .sw{background:var(--green)}.toggle.on .sw::after{left:23px}
#status{font-size:12.5px;color:var(--cyan);min-height:18px;margin-top:8px}
.foot{color:var(--mut);font-size:12px;margin-top:26px;text-align:center;line-height:1.7}
.chk{font-size:13px;line-height:1.85;color:#cfe0ff}
.chk li{margin-left:18px}
</style></head>
<body><div class="wrap">
<h1>Justice — Test Console
<span class="sub">เครื่องมือเทสมินิเกม + ระบบ Disconnect แบบกดปุ่มเดียว · ใช้ flow เดียวกับเกมจริง (สร้างห้อง /Game/api/spaces)</span>
</h1>
<div class="topbar">
<a class="btn" href="index.php">← กลับ Bug List</a>
<span class="toggle" id="tmToggle" title="ตั้ง localStorage justiceTestMode สำหรับหน้า /Game ทั้งหมด (origin เดียวกัน)">
<span class="sw"></span><span>Test Mode (debug)</span>
</span>
</div>
<div class="note">
<b>มินิเกมเริ่มได้ 2 ทาง — เลือกให้ตรงกับสิ่งที่จะเทส:</b><br>
• <b>โหมด A พรีวิว</b> = เปิดมินิเกมเดี่ยวพร้อมบอททันที (บอทจำลองฝั่ง host) → เหมาะเช็ค <b>ภาพ / texture / gameplay / UI</b> เร็วๆ ทุกเกมใช้ได้<br>
• <b>โหมด B ห้องจริง LobbyA</b> = flow จริง host→case→มินิเกม (บอทจาก <b>server</b>) → เป็นทางเดียวที่เทส <b>Server-Authority + Disconnect/Takeover</b> ได้จริง
</div>
<!-- ===================== โหมด A ===================== -->
<h2>โหมด A · พรีวิวมินิเกม (เช็คภาพ/gameplay) <span class="tag">บอทฝั่ง host</span></h2>
<div class="grid" id="mgGrid"></div>
<!-- ===================== โหมด B ===================== -->
<h2>โหมด B · เทส Server-Authority + Disconnect (ห้องจริง) <span class="tag">บอทฝั่ง server</span></h2>
<div class="panel">
<div class="field">
<label>จำนวนผู้เล่นจริง (แท็บ)</label>
<input type="number" id="humanCount" value="2" min="1" max="6">
<span style="font-size:12px;color:var(--mut)">เปิดหลายแท็บ = จำลองหลายคน (ทดสอบ disconnect)</span>
</div>
<div class="field">
<label>จำนวนบอท (server)</label>
<input type="number" id="botCount" value="4" min="0" max="6">
<span style="font-size:12px;color:var(--mut)">คน + บอท ≤ 6 (LobbyA cap)</span>
</div>
<div class="field">
<button class="btn primary" id="makeRoom">🎮 สร้างห้อง LobbyA + บอท แล้วเปิดแท็บ</button>
<button class="btn v" id="addTab" disabled>+ เปิดแท็บผู้เล่นเพิ่ม</button>
</div>
<div id="status"></div>
<div class="links" id="roomLinks"></div>
<ol class="steps">
<li>กด <b>สร้างห้อง</b> → เปิดแท็บ LobbyA ตามจำนวนผู้เล่น (อนุญาต popup ถ้าเบราว์เซอร์ถาม)</li>
<li>ในแท็บ host เดินตัวละครไป <b>โซนเริ่มเกมสีส้ม</b> → เกมจะสุ่ม case แล้วเข้ามินิเกม (บอทมาจาก server)</li>
<li>ระหว่างเล่นมินิเกม <b>ปิดแท็บผู้เล่น 1 แท็บ</b> เพื่อจำลอง disconnect → สังเกตว่าตัวนั้นกลายเป็น <b>"(บอท)"</b> เล่นต่อโดย server ไม่ค้าง/ไม่หาย</li>
<li>เปิดแท็บนั้นใหม่ (ลิงก์เดิม) ภายในเวลาที่กำหนด → ผู้เล่นควร <b>กลับเข้ามาแทนบอท</b> ตำแหน่ง/คะแนนเดิม</li>
</ol>
</div>
<!-- ===================== Checklist ===================== -->
<h2>เช็กลิสต์ที่ต้องผ่านทุกเกม (Server-Authority + Disconnect)</h2>
<div class="panel chk">
<ul>
<li><b>บอทเดินจาก server:</b> ปิดแท็บ host แล้วบอทยัง <b>เคลื่อนไหว/ทำคะแนนต่อ</b> (ไม่ใช่ host จำลอง)</li>
<li><b>Host หลุด ≠ เกมหยุด:</b> host ออกกลางเกม เกมยังดำเนินต่อสำหรับคนอื่น</li>
<li><b>Takeover:</b> คนหลุด → กลายเป็นบอทชื่อ <code>(บอท)</code> สี/ตำแหน่งเดิม ไม่หายวับ</li>
<li><b>Reconnect:</b> เปิดลิงก์เดิมกลับมา → แทนที่บอท takeover (ไม่เกิดตัวซ้ำ)</li>
<li><b>คะแนน/เกรด:</b> สรุปผลรวมบอท ตรงกันทุกจอ (server เป็นแหล่งเดียว)</li>
<li><b>ไม่กระทบกันข้ามเกม:</b> เล่นเกมอื่นต่อในห้องเดิม สถานะเกมก่อนหน้าไม่รั่ว</li>
</ul>
</div>
<div class="foot">
สถานะ: <b style="color:var(--green)">MG1 quiz</b> · <b style="color:var(--green)">MG2 gauntlet</b> = server-auth แล้ว ·
<b style="color:var(--amber)">MG3–MG7</b> = กำลังทยอยย้าย · เครื่องมือนี้ไม่แก้ logic เกม (เปิดห้องผ่าน API จริงเท่านั้น)
</div>
</div>
<script>
(function(){
var GAME = '/Game';
var LOBBYA_MAP = 'mlsbbxfe';
var MGS = [
{ mg:'MG1', name:'Quiz', th:'ตอบคำถาม', map:'mng8a80o', max:8, auth:'srv' },
{ mg:'MG2', name:'Gauntlet', th:'วิ่งหลบอุปสรรค (มงกุฎ)', map:'mno9kb07', max:6, auth:'srv' },
{ mg:'MG3', name:'Stack Tower', th:'ต่อหอคอย', map:'mnn93hpi', max:8, auth:'host' },
{ mg:'MG4', name:'Quiz Carry', th:'แบกตอบคำถาม', map:'mnorwqx1', max:8, auth:'host' },
{ mg:'MG5', name:'Jump Survive', th:'กระโดดเอาตัวรอด', map:'mnptfts2', max:8, auth:'host' },
{ mg:'MG6', name:'Space Shooter', th:'ยิงอวกาศ', map:'mnpz6rkp', max:6, auth:'host' },
{ mg:'MG7', name:'Balloon Boss', th:'บอสลูกโป่ง (Mega Virus)', map:'mnq1eml7', max:6, auth:'host' }
];
var AUTH_LABEL = { srv:'server-auth ✓', host:'host-sim (ยังไม่ย้าย)', test:'กำลังเทส' };
function rnick(p){ return (p||'เทส') + Math.floor(Math.random()*9000+1000); }
function setStatus(t, color){ var s=document.getElementById('status'); s.textContent=t; s.style.color=color||'var(--cyan)'; }
// ---- สร้างห้องผ่าน API จริง ----
function createSpace(opts){
// spaceId ฝั่ง server = ชื่อห้อง → ใส่ suffix สุ่มกันชนกันข้ามผู้เทส
var uname = (opts.name || 'test') + '-' + Math.random().toString(36).slice(2,8);
return fetch(GAME + '/api/spaces', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({
mapId: opts.mapId, isPrivate:true, name: uname,
maxPlayers: opts.maxPlayers || 6,
botSlotCount: (opts.botSlotCount != null ? opts.botSlotCount : 0)
})
}).then(function(r){ return r.json(); });
}
// ---- โหมด A: พรีวิว ----
function openPreview(mg){
setStatus('กำลังสร้างห้องพรีวิว ' + mg.mg + '…');
createSpace({ mapId: mg.map, name:'preview', maxPlayers: mg.max, botSlotCount:0 })
.then(function(d){
if (!d || !d.ok || !d.spaceId){ setStatus('สร้างห้องไม่ได้: ' + ((d&&d.error)||'?'),'var(--red)'); return; }
var q = GAME + '/play.html?space=' + encodeURIComponent(d.spaceId)
+ '&nick=' + encodeURIComponent(rnick('พรีวิว'))
+ '&map=' + encodeURIComponent(mg.map)
+ '&preview=1&defaultChar=1&editorEmbed=1&_h=' + Date.now();
window.open(q, '_blank', 'noopener');
setStatus('เปิดพรีวิว ' + mg.mg + ' แล้ว (แท็บใหม่)','var(--green)');
})
.catch(function(){ setStatus('สร้างห้องไม่ได้ (network)','var(--red)'); });
}
// ---- โหมด B: ห้องจริง LobbyA + บอท ----
var currentRoom = null; // { spaceId, opened }
function lobbyUrl(spaceId, nick){
return GAME + '/room-lobby.html?space=' + encodeURIComponent(spaceId)
+ '&nick=' + encodeURIComponent(nick) + '&map=' + encodeURIComponent(LOBBYA_MAP);
}
function makeRoom(){
var humans = Math.max(1, Math.min(6, parseInt(document.getElementById('humanCount').value,10)||2));
var bots = Math.max(0, Math.min(6, parseInt(document.getElementById('botCount').value,10)||0));
setStatus('กำลังสร้างห้อง LobbyA (บอท ' + bots + ')…');
createSpace({ mapId: LOBBYA_MAP, name:'test-lobby', maxPlayers: 6, botSlotCount: bots })
.then(function(d){
if (!d || !d.ok || !d.spaceId){ setStatus('สร้างห้องไม่ได้: ' + ((d&&d.error)||'?'),'var(--red)'); return; }
currentRoom = { spaceId: d.spaceId, opened: 0 };
document.getElementById('addTab').disabled = false;
renderLinks(humans);
for (var i=0;i<humans;i++) openTab(i===0 ? 'host' : null);
setStatus('สร้างห้องแล้ว · spaceId ' + d.spaceId + ' · เปิด ' + humans + ' แท็บ (ถ้า popup ถูกบล็อก ใช้ลิงก์ด้านล่าง)','var(--green)');
})
.catch(function(){ setStatus('สร้างห้องไม่ได้ (network)','var(--red)'); });
}
function openTab(role){
if (!currentRoom) return;
currentRoom.opened++;
var nick = rnick(role==='host' ? 'HOST' : 'P');
window.open(lobbyUrl(currentRoom.spaceId, nick), '_blank', 'noopener');
}
function renderLinks(humans){
var box = document.getElementById('roomLinks'); box.innerHTML='';
if (!currentRoom) return;
for (var i=0;i<humans;i++){
var a = document.createElement('a');
a.className = 'btn'; a.target='_blank'; a.rel='noopener';
a.href = lobbyUrl(currentRoom.spaceId, (i===0?'HOST':'P') + (1000+i));
a.textContent = (i===0?'แท็บ host':'แท็บผู้เล่น ' + (i+1));
box.appendChild(a);
}
}
// ---- render การ์ดมินิเกม ----
(function(){
var grid = document.getElementById('mgGrid');
MGS.forEach(function(mg){
var c = document.createElement('div'); c.className='mg';
c.innerHTML =
'<div class="hd"><div class="nm">' + mg.mg + ' · ' + mg.name + '<small>' + mg.th + '</small></div>'
+ '<span class="auth ' + mg.auth + '" title="'+AUTH_LABEL[mg.auth]+'">' + AUTH_LABEL[mg.auth] + '</span></div>'
+ '<div class="mid">map: ' + mg.map + '</div>'
+ '<div class="mgrow"><button class="btn amber" data-prev="1">▶ พรีวิวเล่น</button></div>';
c.querySelector('[data-prev]').addEventListener('click', function(){ openPreview(mg); });
grid.appendChild(c);
});
})();
document.getElementById('makeRoom').addEventListener('click', makeRoom);
document.getElementById('addTab').addEventListener('click', function(){ openTab(null); });
// ---- Test Mode toggle (localStorage justiceTestMode) ----
var tm = document.getElementById('tmToggle');
function syncTm(){ var on=false; try{ on = localStorage.getItem('justiceTestMode')==='1'; }catch(e){} tm.classList.toggle('on', on); }
tm.addEventListener('click', function(){
var on=false; try{ on = localStorage.getItem('justiceTestMode')==='1'; localStorage.setItem('justiceTestMode', on?'0':'1'); }catch(e){}
syncTm();
});
syncTm();
})();
</script>
</body></html>