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 ไลนจาก 0ticks (เคยลอยคาง/วาป)
ขาลง (บถอยหล 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)">MG3MG7</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>