fix on testcase 1.3

This commit is contained in:
2026-06-26 08:30:12 +00:00
parent 66c1801f3e
commit d4bae38a61
18 changed files with 3581 additions and 93 deletions
+2
View File
@@ -51,6 +51,8 @@
</div>
<div class="admin-top-actions" id="top-actions" hidden>
<a href="/Admin/justice-git.html" class="btn btn-ghost" style="text-decoration:none;margin-right:0.35rem;">อัป Git (justice)</a>
<a href="/gamelog/" target="_blank" rel="noopener noreferrer" class="btn btn-ghost" style="text-decoration:none;margin-right:0.35rem;">📊 Game Log</a>
<a href="/testcase/" target="_blank" rel="noopener noreferrer" class="btn btn-ghost" style="text-decoration:none;margin-right:0.35rem;">🐞 Test Case</a>
<span class="admin-user" id="admin-user-label"></span>
<button type="button" class="btn btn-ghost btn-logout" id="btn-logout">ออกจากระบบ</button>
</div>
+25 -22
View File
@@ -51,9 +51,9 @@
"providerUserId": "p_1775109142385_wq7wfy1p32j",
"notes": "auto: player-coins",
"blocked": false,
"coins": 1918,
"coins": 2072,
"createdAt": "2026-04-02T05:52:21+00:00",
"updatedAt": "2026-06-25T16:09:47+00:00",
"updatedAt": "2026-06-26T07:58:33+00:00",
"daily": {
"anchorMs": 1781197200000,
"claimedDays": [
@@ -67,23 +67,23 @@
],
"lockUntilMs": 0
},
"score": 1738,
"score": 18,
"scoreByCase": {
"1": 16,
"10": 446,
"10": 514,
"8": 296,
"11": 20,
"13": 186,
"13": 224,
"14": 30,
"9": 40,
"12": 380,
"12": 408,
"15": 20,
"4": 20,
"5": 32,
"7": 6,
"3": 132,
"3": 142,
"6": 44,
"2": 70
"2": 80
},
"lbName": "Q",
"achievements": {
@@ -91,7 +91,7 @@
"a1_first_deduction": 1,
"a5_truth_hunter": 3
},
"lobbyColorThemeIndex": 8,
"lobbyColorThemeIndex": 7,
"lobbySkinToneIndex": 1
},
{
@@ -651,21 +651,23 @@
"providerUserId": "p_1782016990238_0u37bv7pxlxs",
"notes": "auto: player-coins",
"blocked": false,
"coins": 150,
"coins": 288,
"createdAt": "2026-06-21T04:43:11+00:00",
"updatedAt": "2026-06-25T15:49:43+00:00",
"updatedAt": "2026-06-26T08:07:51+00:00",
"lobbyColorThemeIndex": 8,
"lobbySkinToneIndex": 1,
"score": 120,
"score": 258,
"scoreByCase": {
"13": 56,
"10": 46,
"13": 82,
"10": 120,
"6": 8,
"8": 10
"8": 20,
"2": 18,
"3": 10
},
"lbName": "MONE",
"achievements": {
"d1_minigame_solver": 12
"d1_minigame_solver": 20
}
},
{
@@ -893,17 +895,18 @@
"providerUserId": "p_1782376007927_lpt9m4xge9",
"notes": "auto: player-coins",
"blocked": false,
"coins": 62,
"coins": 82,
"createdAt": "2026-06-25T08:26:49+00:00",
"updatedAt": "2026-06-25T14:34:19+00:00",
"score": 62,
"updatedAt": "2026-06-26T04:11:43+00:00",
"score": 82,
"scoreByCase": {
"12": 42,
"8": 20
"12": 52,
"8": 20,
"3": 10
},
"lbName": "MONE",
"achievements": {
"d1_minigame_solver": 7
"d1_minigame_solver": 9
}
}
]
+5 -5
View File
@@ -40,7 +40,7 @@
""
],
"stackHeavyBlockPercent": 35,
"jumpSurviveJumpHeightMult": 1.5,
"jumpSurviveJumpHeightMult": 1.7,
"jumpSurviveMissionTimeSec": 0,
"jumpSurvivePlatformTiles": [
{
@@ -51,7 +51,7 @@
{
"url": "/Game/img/Jumper/platfrom-2.png",
"w": 389,
"h": 33
"h": 36
},
{
"url": "/Game/img/Jumper/platfrom-3.png",
@@ -74,7 +74,7 @@
"/Game/img/ViolentCrime/Meteo-3.png"
],
"spaceShooterAsteroidExplodeFrameMs": 60,
"spaceShooterAsteroidIntervalMs": 200,
"spaceShooterAsteroidIntervalMs": 300,
"spaceShooterShipDamageOverlayUrls": [
"/Game/img/ViolentCrime/Rocket-broke-1.png",
"/Game/img/ViolentCrime/Rocket-broke-2.png",
@@ -95,9 +95,9 @@
"balloonBossPlayerBalloonFallbackUrl": "/Game/img/MegaVirus/Artboard%209.png",
"balloonBossBalloonsPerPlayer": 3,
"forcedMinigameKeys": [
"gauntlet",
"stack",
"balloon_boss"
"jump_survive",
"space_shooter"
],
"testSpecialCardByMap": {},
"troublesomeForceOffer": false,
File diff suppressed because one or more lines are too long
+157 -34
View File
@@ -691,11 +691,23 @@
/* host: ผู้เล่นจริงทุกคน (ตัด banned spectator กัน stall) + บอท เรียง id → ที่นั่งคงที่; preserve คนที่กำลังถึงตา */
const prevActorId = (stackCurrentTurnEntryPlay() && stackCurrentTurnEntryPlay().id) || null;
const parts = [];
if (myId != null && !playBannedSpectator) parts.push({ kind: 'human', id: String(myId) });
/* dedup (ghost reconnect) seat ( = )
เทยบดวย playerKey เทาน **าม dedup วยช**: 2 คนตงชอเหมอนก (เช "MONE") = คนละ key = คนละคน
องได seat งค (เด fallback client อซ host กต = "ไม่ได้เล่นเลย") */
const seenHumanKey = new Set();
const tryAddHuman = (id, ent) => {
const pk = (ent && ent.playerKey) ? String(ent.playerKey) : (String(id) === String(myId) ? (typeof getJdPlayerKey === 'function' ? getJdPlayerKey() : '') : '');
if (pk) {
if (seenHumanKey.has(pk)) return; /* playerKey เดียวกัน = ghost reconnect → ข้าม */
seenHumanKey.add(pk);
}
parts.push({ kind: 'human', id: String(id) });
};
if (myId != null && !playBannedSpectator) tryAddHuman(myId, me);
others.forEach((o, id) => {
if (isPreviewBotId(id)) { parts.push({ kind: 'bot', id: String(id), botId: String(id) }); return; }
if (o && o.bannedSpectator) return; /* คนถูกแบน = ดูเฉยๆ ไม่เข้าคิววาง */
parts.push({ kind: 'human', id: String(id) });
tryAddHuman(id, o);
});
parts.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
const capped = parts.slice(0, STACK_PREVIEW_TURN_COUNT);
@@ -720,6 +732,7 @@
/* theme ต่อที่นั่ง (host เป็นเจ้าของ) → ทุกจอลงสี block ตรงกัน (เดิม non-host คำนวณสีเองจาก playTint → ไม่ตรง 2 จอ) */
order: stackPreviewTurnOrder.map((e) => ({ kind: e.kind, seat: e.seat, id: e.id, botId: e.botId || null, theme: stackSeatColorThemePlay(e.seat) || null })),
});
logStackSeatColors('host-bcast');
} catch (e) { /* ignore */ }
}
@@ -753,6 +766,8 @@
pts: hit.pts || 0, progressDelta: hit.progressDelta || 0, landOffsetTiles: hit.landOffsetTiles || 0,
overlap: hit.overlap, supportRatio: hit.supportRatio, delta: hit.delta || 0,
seat: seat || 1, heavy: !!heavy, actorId: actorId != null ? String(actorId) : '', isBot: !!isBot,
/* สี (theme) ที่ host/เจ้าของตา resolve ตอนวาง → ส่งไปด้วย ให้บล็อกที่วางบนทุกจอใช้สีเดียวกัน (กัน host/client คนละสี) */
theme: stackSeatColorThemePlay(seat) || null,
});
} catch (e) { /* ignore */ }
}
@@ -802,18 +817,76 @@
const s = Math.floor(Number(seat)) || 0;
const entry = stackPreviewTurnOrder.find(function (e) { return e && e.seat === s; });
if (!entry) return null;
/* theme ที่ host broadcast มา (stack-state) — ตรงกันทุกจอ → ใช้ก่อน (เดิมคำนวณ local จาก playTint = ไม่ตรง 2 จอ) */
/* (1) host broadcast stack-state = SINGLE SOURCE OF TRUTH client host ( host )
เกม MG3 host เปนเจาของลำดบตา/; host เองไม entry.theme (คำนวณสดตอน broadcast) ตกไป (2) authoritative
เหตอง broadcast-first: client คำนวณเอง (lobbyThemeIndexSrv/playLobbyBotThemes ของ client) แลวขอมลมาช/ไมตรง
หลดจาก host เพยนเรอย . ใช host = ไมทางหล */
const bcTheme = parsePlayLobbyThemeIndex(entry.theme);
if (bcTheme) return bcTheme;
/* (2) authoritative entity ( "host " + client broadcast):
bot playLobbyBotThemes[slot] (server) ; human lobbyThemeIndexSrv (= server lobbyColorThemeIndex) */
const botId = entry.botId || (entry.kind === 'bot' ? entry.id : null);
if (botId && isPreviewBotId(botId)) {
const slot = parseInt(String(botId).slice(PREVIEW_BOT_PREFIX.length), 10);
const bt = (slot >= 0 && Array.isArray(playLobbyBotThemes)) ? playLobbyBotThemes[slot] : null;
const botIdx = bt ? parsePlayLobbyThemeIndex(bt.themeIndex) : null;
if (botIdx) return botIdx;
} else if (entry.id != null) {
const entH = (myId != null && String(entry.id) === String(myId)) ? me : (others.get(entry.id) || others.get(String(entry.id)));
const humanIdx = entH ? parsePlayLobbyThemeIndex(entH.lobbyThemeIndexSrv) : null;
if (humanIdx) return humanIdx;
}
/* (3) ทางเลือกสุดท้าย: เดาจาก playTint (nearest hex — อาจไม่ตรงข้ามจอ) */
let ent = null;
if (entry.id != null) ent = (myId != null && String(entry.id) === String(myId)) ? me : (others.get(entry.id) || others.get(String(entry.id)));
if (!ent && entry.botId) ent = others.get(entry.botId) || others.get(String(entry.botId));
if (!ent) return null;
/* ใช้ server theme index ก่อน (ตรงกันทุกจอ) — fallback ไป playTint ถ้าไม่มี (เช่น บอท) */
const srvIdx = parsePlayLobbyThemeIndex(ent.lobbyThemeIndexSrv);
return srvIdx || playLobbyThemeIndexForEntityPlay(ent);
return ent ? playLobbyThemeIndexForEntityPlay(ent) : null;
}
/** ส่ง log ไป /gamelog (ดูรวมที่หน้าเว็บ) */
function gamelogPost(cat, ev, detail, who) {
try {
fetch('/gamelog/log.php', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, keepalive: true,
body: JSON.stringify({ cat: cat || 'อื่นๆ', room: (typeof spaceId === 'string' ? spaceId : ''), who: who || '', ev: ev || '', detail: detail || '', src: 'client' }),
}).catch(function () {});
} catch (e) { /* ignore */ }
}
/** block MG3 seat " avatar (playTint)" vs " block (resolved)"; /gamelog
เรยกเอง: window.__mg3ColorLog() หร auto throttle */
let _mg3ColorLogAt = 0;
let _mg3LastMismatchKey = '';
function logStackSeatColors(tag, force) {
try {
if (!isStack() || !stackTurnOrderReadyPlay()) return;
const now = Date.now();
if (!force && now - _mg3ColorLogAt < 2000) return;
_mg3ColorLogAt = now;
const rows = stackPreviewTurnOrder.map(function (e) {
let ent = null;
if (e.id != null) ent = (myId != null && String(e.id) === String(myId)) ? me : (others.get(e.id) || others.get(String(e.id)));
if (!ent && e.botId) ent = others.get(e.botId) || others.get(String(e.botId));
const tint = ent && ent.playTint && ent.playTint.body ? ent.playTint.body : '';
const avatarIdx = tint ? nearestIndexFromHexPlay(tint, PLAY_LOBBY_THEME_HEX) : null; /* สีจริงของ avatar (จาก playTint) */
const blockIdx = stackSeatColorThemePlay(e.seat); /* สี block ที่จะวาด */
return { seat: e.seat, kind: e.kind, nick: (ent && ent.nickname) || String(e.id || e.botId || '').slice(0, 6), avatarIdx: avatarIdx, blockIdx: blockIdx, srv: (ent && ent.lobbyThemeIndexSrv) || null, bc: e.theme || null, ok: (avatarIdx && blockIdx) ? (avatarIdx === blockIdx) : null };
});
console.log('[mg3-color/' + (tag || '') + '] host=' + isMePlayHost(), JSON.stringify(rows));
/* ส่งเฉพาะกรณีพบ mismatch (avatar≠block) → เห็นใน /gamelog (MG3) ทันที ไม่ต้องเปิด console */
const bad = rows.filter(function (r) { return r.ok === false; });
if (bad.length) {
const key = (isMePlayHost() ? 'H' : 'C') + '|' + bad.map(function (r) { return r.seat + ':' + r.avatarIdx + '!=' + r.blockIdx; }).join(',');
if (key !== _mg3LastMismatchKey) {
_mg3LastMismatchKey = key;
const role = isMePlayHost() ? 'host' : 'client';
const detail = bad.map(function (r) { return r.nick + ' seat' + r.seat + ' avatar#' + r.avatarIdx + ' แต่ block#' + r.blockIdx + ' (srv' + r.srv + '/bc' + r.bc + ')'; }).join(' | ');
gamelogPost('MG3', 'color-mismatch', detail, role);
}
}
} catch (e) { /* ignore */ }
}
window.__mg3ColorLog = function () { logStackSeatColors('manual', true); };
function pickStackBlockHeavyVisual(seat) {
const slot = Math.max(0, Math.min(7, (Math.floor(Number(seat)) || 1) - 1));
let hU = normalizeGauntletAssetUrlForPlay(playStackBlockHeavyUrls[slot] || '');
@@ -838,6 +911,7 @@
if (!stackMini || !stackMini.nextDropVisualDirty) return;
stackMini.pendingDropSeat = getStackDropActorSeat();
stackMini.pendingDropHeavy = pickStackBlockHeavyVisual(stackMini.pendingDropSeat);
stackMini.pendingDropTheme = null; /* ดรอป local/host → ใช้ stackSeatColorThemePlay เอง (remote ตั้งทับทีหลัง) */
stackMini.widthTiles = stackBlockWidthTilesForPlay(stackMini.initialWidthTiles, !!stackMini.pendingDropHeavy);
stackMini.nextDropVisualDirty = false;
}
@@ -2768,12 +2842,13 @@
const tiNum = parsePlayLobbyThemeIndex(themeIndex);
if (myId != null && sid === String(myId)) {
if (tiNum) me.lobbyThemeIndexSrv = tiNum;
/* : "" (localStorage) theme-index server
(สาเหตกระพร/สล: server sync index ไมตรงเลอก วท me.playTint กคร) */
/* : " server index" (authoritative) "" + block MG3 avatar
เด override วย localStorage จอเราเหเลอก จออ+block MG3 ใช server index = ไมตรง
(เคส server reassign ตอนเลอกชนก server index อสจร องยดอนน) */
const tintMe = (tiNum) ? playTintFromLobbyThemeIndices(themeIndex, skinIndex) : null;
if (tintMe) { me.playTint = tintMe; return; }
const meChosen = loadPlayTintFromLobbyPlayerStorage();
if (meChosen) { me.playTint = meChosen; return; }
const tintMe = playTintFromLobbyThemeIndices(themeIndex, skinIndex);
if (tintMe) me.playTint = tintMe;
if (meChosen) me.playTint = meChosen;
return;
}
const oEarly = others.get(peerId) || others.get(sid);
@@ -7310,6 +7385,7 @@
howto.classList.remove(GCHOWTO_STACK_TOWER_CLASS);
}
gauntletCrownHowtoVisible = false;
hideReadyAvatarsFloat(); /* ซ่อนแถวหน้าตัวละคร ready ตอนเริ่มเกม (กันค้างบนจอระหว่างเล่น) */
hideStackTowerHowtoRulesDom();
const st = document.getElementById('gauntlet-crown-howto-status');
if (st) {
@@ -7645,6 +7721,7 @@
const howto = document.getElementById('gauntlet-crown-howto-overlay');
if (howto) howto.classList.add('is-hidden');
gauntletCrownHowtoVisible = false;
hideReadyAvatarsFloat(); /* ซ่อนแถวหน้าตัวละคร ready ตอนเริ่มเกม (กันค้างบนจอระหว่างเล่น) */
const st = document.getElementById('gauntlet-crown-howto-status');
if (st) {
st.classList.add('is-hidden');
@@ -8025,6 +8102,7 @@
const howto = document.getElementById('gauntlet-crown-howto-overlay');
if (howto) howto.classList.add('is-hidden');
gauntletCrownHowtoVisible = false;
hideReadyAvatarsFloat(); /* ซ่อนแถวหน้าตัวละคร ready ตอนเริ่มเกม (กันค้างบนจอระหว่างเล่น) */
const st = document.getElementById('gauntlet-crown-howto-status');
if (st) {
st.classList.add('is-hidden');
@@ -8601,6 +8679,7 @@
const howto = document.getElementById('gauntlet-crown-howto-overlay');
if (howto) howto.classList.add('is-hidden');
gauntletCrownHowtoVisible = false;
hideReadyAvatarsFloat(); /* ซ่อนแถวหน้าตัวละคร ready ตอนเริ่มเกม (กันค้างบนจอระหว่างเล่น) */
const st = document.getElementById('gauntlet-crown-howto-status');
if (st) {
st.classList.add('is-hidden');
@@ -11878,8 +11957,10 @@
const placedW = hit.placedW != null && Number.isFinite(hit.placedW)
? Math.max(0.85, Math.min(3.2, hit.placedW))
: stackMini.initialWidthTiles;
/* freeze สี (theme) ตอนวาง → block ที่วางแล้วไม่เปลี่ยนสีแม้ turn order/theme resolve เปลี่ยนทีหลัง (ตรงทุกจอ) */
stackMini.layers.push({ w: placedW, cx: hit.placedCx, seat: seatLay, heavy: heavyLay, theme: stackSeatColorThemePlay(seatLay) || null });
/* freeze (theme) block turn order/theme resolve ()
remote drop: ใชจาก host (layerVisualMeta.theme) อน host/client บลอกตรงก */
const _layerTheme = (layerVisualMeta && parsePlayLobbyThemeIndex(layerVisualMeta.theme)) || stackSeatColorThemePlay(seatLay) || null;
stackMini.layers.push({ w: placedW, cx: hit.placedCx, seat: seatLay, heavy: heavyLay, theme: _layerTheme });
const nAfter = stackMini.layers.length;
maybeQueueStackTowerBgScrollStepPlay(nAfter);
const perfectFull =
@@ -12097,6 +12178,7 @@
const m = stackMini;
const actorSeat = m.pendingDropSeat != null ? m.pendingDropSeat : getStackDropActorSeat();
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;
const lh = m.layerWorldH || Math.max(14, tileSize * 0.3);
@@ -12140,6 +12222,7 @@
releaseAttachWorldY: swingAttachY,
actorSeat,
actorHeavy,
actorTheme,
};
return true;
}
@@ -12230,6 +12313,7 @@
ensureStackNextDropVisual();
stackMini.pendingDropSeat = item.seat || 1;
stackMini.pendingDropHeavy = !!item.heavy;
stackMini.pendingDropTheme = item.theme || null; /* สีจาก host สำหรับบล็อกนี้ (remote drop) */
stackMini.widthTiles = stackBlockWidthTilesForPlay(stackMini.initialWidthTiles, !!item.heavy);
return startStackFallAnimation(item.hit, item.actor || { human: true });
}
@@ -12302,7 +12386,7 @@
if (wasHuman) previewAttr = { human: true, id: fallHumanId };
else if (fallBotId) previewAttr = { botId: fallBotId };
}
applyStackHitFromDrop(hit, previewAttr, { seat: stackFall.actorSeat, heavy: stackFall.actorHeavy });
applyStackHitFromDrop(hit, previewAttr, { seat: stackFall.actorSeat, heavy: stackFall.actorHeavy, theme: stackFall.actorTheme });
if (playBotsEnabled() && (wasHuman || fallBotId)) {
stackPreviewLogStackDrop(hit, { human: wasHuman, botId: fallBotId });
}
@@ -18675,8 +18759,17 @@
feetY = lo;
headTop = feetY - bh;
}
vy = 0;
onGround = true;
/* "" ( 0-1) scroll
ตามคำขอ "ตกลงทันที": ทะลแทนนลงไปหาแทนลางเลย (ตอนตก = เจอเฉพาะแท ไมใช object เพดานจร) */
if (feetY - bh < ts) {
feetY = ts + bh + ts; /* ดันให้พ้นโซนเพดาน (หัว ≈ แถว 2) แล้วตกต่อ */
vy = Math.max(vy, ts * 6);
onGround = false;
headTop = feetY - bh;
} else {
vy = 0;
onGround = true;
}
} else if (vy < 0 && jumpSurviveOverlapsObjectSolids(md, pxc - bw, headTop, pxc + bw, feetY)) {
/* กระโดดขึ้น: แพลตฟอร์มทะลุได้ (one-way) — หยุดเฉพาะกำแพง/ขอบ/เพดานจริง (objects) ไม่ให้หัวติดแท่นบน */
const stepUp = ts * 0.035;
@@ -18709,7 +18802,8 @@
headTop = feetY - bh;
if (headTop < ts) {
feetY = ts + bh;
if (vy < 0) vy = 0;
/* ชนเพดานบนสุด → ดันลงทันที (เดิม vy=0 = ลอยนิ่งใต้เพดาน → ตัวละครค้างบนสุด) ให้ตกกลับลงแท่นแล้วไหลตามสายพานต่อ */
if (vy < 0) vy = ts * 6;
headTop = feetY - bh;
}
@@ -19159,7 +19253,9 @@
if (Date.now() - jumpSurviveLastEmitT > 80) {
jumpSurviveLastEmitT = Date.now();
socket.emit('move', { x: me.x, y: me.y, direction: me.direction });
/* isWalking world Y isWalking delta
จะเหนเรา"ซอยเท้า"ตลอด งทนน (#42). งสถานะจร (กดซาย/ขวา หรอลอยอย) ใหคนอ render ตรง */
socket.emit('move', { x: me.x, y: me.y, direction: me.direction, jsWalking: !!me.isWalking });
}
}
@@ -19468,6 +19564,8 @@
nick = me.nickname;
}
me.characterId = myPeer?.characterId || null;
/* สี server-authoritative ของเรา → block MG3 ของเราตรงกับ avatar + ตรงทุกจอ (ตั้งแต่เข้าเกม ไม่ต้องรอ lobby-tint-sync) */
{ const meTi = parsePlayLobbyThemeIndex(myPeer && myPeer.lobbyColorThemeIndex); if (meTi) me.lobbyThemeIndexSrv = meTi; }
meGauntletJumpTicks = typeof myPeer?.gauntletJumpTicks === 'number' ? myPeer.gauntletJumpTicks : 0;
meGauntletJumpVis = meGauntletJumpTicks;
{
@@ -19539,6 +19637,8 @@
const spawnOrd = Number.isFinite(joP) ? Math.max(0, Math.floor(joP)) : Math.max(0, plist.findIndex((q) => q && q.id === p.id));
others.set(p.id, {
x: px, y: py, tx: px, ty: py, direction: p.direction, nickname: p.nickname, characterId: p.characterId,
playerKey: (p.playerKey != null ? String(p.playerKey) : null), /* dedup ผี reconnect */
lobbyThemeIndexSrv: parsePlayLobbyThemeIndex(p.lobbyColorThemeIndex) || null, /* สี server-authoritative → block MG3 ตรงทุกจอ */
spawnJoinOrder: spawnOrd,
playTint: playTintFromJoinPeer(p) || playTintForPreviewBotId(p.id) || resolvePlayTintForPeer(p.id, p),
gauntletJumpTicks: typeof p.gauntletJumpTicks === 'number' ? p.gauntletJumpTicks : 0,
@@ -19959,6 +20059,16 @@
ลบ peer เด (คนจร) "ชื่อ+ตัวละครเดียวกัน" แต id าง อนเพมตวใหม (ญญาณวาเปนคนเด) */
const joinNickDedup = (data.nickname != null ? String(data.nickname).trim() : '');
const joinCidDedup = (data.characterId != null ? String(data.characterId) : '');
/* dedup : playerKey ( browser) socket.id = reconnect/
ลบ peer เกาทงกอนเพมตวใหม ( "เงาซ้ำ" แม default nick dedup าม default งเคยคางเปนผ) */
const joinPkDedup = (data.playerKey != null ? String(data.playerKey).trim() : '');
if (joinPkDedup) {
[...others.keys()].forEach((oid) => {
if (isPreviewBotId(oid) || String(oid) === String(data.id)) return;
const ex = others.get(oid);
if (ex && ex.playerKey && String(ex.playerKey) === joinPkDedup) others.delete(oid);
});
}
/* dedup "" (custom) default ('', '', )
ไมนผเลนจร 2 คนทไมงช (default 'ผู้เล่น') จะถกลบผ จำนวนคนหาย/ready าง 4/5 */
const isGenericNick = !joinNickDedup || /^ผู้เล่น/.test(joinNickDedup) || joinNickDedup === 'บอท';
@@ -19982,6 +20092,8 @@
x: px, y: py, tx: px, ty: py,
direction: data.direction || 'down', nickname: data.nickname,
characterId: data.characterId ?? null,
playerKey: joinPkDedup || null, /* ใช้ dedup ผีตอน reconnect (เก็บไว้เทียบรอบหน้า) */
lobbyThemeIndexSrv: parsePlayLobbyThemeIndex(data.lobbyColorThemeIndex) || null, /* สี server-authoritative → block MG3 ตรงทุกจอ */
spawnJoinOrder: spawnOrdJ,
playTint: playTintFromJoinPeer(data) || playTintForPreviewBotId(data.id) || resolvePlayTintForPeer(data.id, data),
gauntletJumpTicks: typeof data.gauntletJumpTicks === 'number' ? data.gauntletJumpTicks : 0,
@@ -20025,8 +20137,18 @@
peers.forEach((p) => {
if (!p || p.id == null || isPreviewBotId(p.id)) return;
if (myId != null && String(p.id) === String(myId)) return;
/* dedup ผี: ลบ peer เก่าที่ playerKey เดียวกันแต่ id ต่าง (reconnect/เปลี่ยนหน้า ทิ้ง id เก่าไว้ = เงาซ้ำ) */
const pkSnap = (p.playerKey != null ? String(p.playerKey) : '');
if (pkSnap) {
[...others.keys()].forEach((oid) => {
if (isPreviewBotId(oid) || String(oid) === String(p.id)) return;
const ex = others.get(oid);
if (ex && ex.playerKey && String(ex.playerKey) === pkSnap) { others.delete(oid); changed = true; }
});
}
const existing = others.get(p.id) || others.get(String(p.id));
if (existing) {
if (pkSnap && !existing.playerKey) existing.playerKey = pkSnap;
/* entry flag ( bannedSpectator) server
host บผกแบนเปนผเลนทองรอ Ready humansReady ไมครบ เรมเกมไมได */
const nb = !!p.bannedSpectator;
@@ -20043,6 +20165,8 @@
direction: p.direction || 'down',
nickname: p.nickname,
characterId: p.characterId ?? null,
playerKey: pkSnap || null, /* dedup ผี reconnect */
lobbyThemeIndexSrv: parsePlayLobbyThemeIndex(p.lobbyColorThemeIndex) || null, /* สี server-authoritative → block MG3 ตรงทุกจอ */
spawnJoinOrder: Number.isFinite(joP) ? Math.max(0, Math.floor(joP)) : 0,
playTint: playTintFromJoinPeer(p) || playTintForPreviewBotId(p.id) || resolvePlayTintForPeer(p.id, p),
gauntletJumpTicks: 0, gauntletJumpVis: 0, gauntletScore: 0,
@@ -20258,6 +20382,7 @@
};
stackRemoteDropQueue.push({
hit: hit, seat: Number(d.seat) || 1, heavy: !!d.heavy,
theme: parsePlayLobbyThemeIndex(d.theme) || null, /* สีจาก host → freeze บนบล็อกที่วาง (ตรงทุกจอ) */
/* เก็บ id ของคนวาง (ไม่ใช่แค่ human: true) → คะแนนเข้าคนที่วางจริง ไม่เข้า me ทุกครั้ง */
actor: d.isBot ? { botId: String(d.actorId || '') } : { human: true, id: d.actorId != null ? String(d.actorId) : null },
});
@@ -20287,6 +20412,7 @@
const n = stackPreviewTurnOrder.length;
stackPreviewTurnIndex = ((Math.floor(Number(d.idx)) % n) + n) % n;
}
logStackSeatColors('client-recv');
});
/* MG3 stack — host ตัดสินจบรอบ → non-host จบพร้อมกันด้วย outcome เดียวกัน (กัน "จบไม่พร้อมกัน") */
@@ -20498,8 +20624,10 @@
if (data.spaceShooterEliminated != null) o.spaceShooterEliminated = !!data.spaceShooterEliminated;
}
/* MG5: การตายของผู้เล่นมนุษย์มาจากเจ้าตัว (broadcast ผ่าน move) → ทุกเครื่องเห็นตรงกัน (เดิมไม่ส่ง = เพื่อน "ไม่ยอมตาย") */
if (mapData && mapData.gameType === 'jump_survive' && data.jumpSurviveEliminated != null) {
o.jumpSurviveEliminated = !!data.jumpSurviveEliminated;
if (mapData && mapData.gameType === 'jump_survive') {
if (data.jumpSurviveEliminated != null) o.jumpSurviveEliminated = !!data.jumpSurviveEliminated;
/* isWalking จริงจากเจ้าตัว — กัน "ยืนซอยเท้า" ตอนยืนนิ่งบนแท่นเลื่อน (#42) */
if (data.jsWalking != null) o.jumpSurviveRemoteWalking = !!data.jsWalking;
}
if (mapData && mapData.gameType === 'balloon_boss') {
if (data.balloonBossScore != null) {
@@ -20565,19 +20693,11 @@
}
socket.on('peer-disconnect-grace', (d) => { if (!d) return; showDisconnectGraceBanner(d.nickname || 'ผู้เล่น', d.seconds || 10); });
socket.on('peer-grace-cancel', () => { hideDisconnectGraceBanner(); });
socket.on('peer-bot-takeover', (d) => {
socket.on('peer-bot-takeover', () => {
/* "" user-left
ไมเตมบอทเลนแทนกลางเกม (เด bump detectiveBotSlotCount = บอทโผลกลางเกม)
บอทจะกลบมาแทนตอน LobbyB เอง: server เพ botSlotCount +1 (ตามคนออก) องถดไปบอทเลนแทน */
hideDisconnectGraceBanner();
/* คนที่หลุด "ตาย/ตกรอบไปแล้ว" → ไม่ต้องเติมบอทมาเล่นแทน (เดิมเติมบอท alive = คนตายฟื้นกลับมา) */
if (d && d.eliminated) return;
/* host เติมบอท 1 ตัวเล่นแทนคนที่หลุด (ถ้ายังไม่เต็ม 6) — ใช้ rebalancePreviewBots เดิม */
try {
if (isMePlayHost()) {
detectiveCaseFillBots = true;
if (countPlayHumans() + detectiveBotSlotCount < 6) detectiveBotSlotCount += 1;
rebalancePreviewBots();
if (typeof broadcastFillBotState === 'function') broadcastFillBotState();
}
} catch (_e) { /* ignore */ }
});
socket.on('chat', (data) => {
@@ -24929,7 +25049,10 @@
? ((crownRunStripLive && !o.gauntletEliminated) || (o.gauntletJumpTicks || 0) > 0 || (o.gauntletJumpVis || 0) > 0.08)
: (isPreviewBotId(id)
? !!o.botIsWalking
: !!((o.tx != null && Math.abs((o.tx || o.x) - o.x) > 0.02) || (o.ty != null && Math.abs((o.ty || o.y) - o.y) > 0.02)));
: (isJumpSurvive()
/* MG5 ผู้เล่นจริงคนอื่น: ใช้ isWalking จริงจากเจ้าตัว (#42) ไม่เดาจาก delta — world Y เลื่อนลงตามแท่นทุกเฟรมแม้ยืนนิ่ง */
? !!o.jumpSurviveRemoteWalking
: !!((o.tx != null && Math.abs((o.tx || o.x) - o.x) > 0.02) || (o.ty != null && Math.abs((o.ty || o.y) - o.y) > 0.02))));
const ot = o.playTint || resolvePlayTintForPeer(id);
const faceDirOther = isGauntletFaceRightMapMno9kb07() ? 'right' : o.direction;
const botOut = isJumpSurvive() && isPreviewBotId(id) && o.jumpSurviveEliminated;
+75 -6
View File
@@ -606,6 +606,69 @@
});
}
/* ===== Loading overlay สีตัวละคร (peer เข้าใหม่ / เปลี่ยนสี) — ดำ 90% + % แล้วหายเมื่อ tint โหลดเสร็จ ===== */
var _lcOverlayEl = null;
var _lcShowTimer = null;
var _lcActive = false;
function lcEnsureEl() {
if (_lcOverlayEl) return _lcOverlayEl;
var el = document.createElement('div');
el.id = 'lobby-color-loading';
el.style.cssText = 'position:fixed;inset:0;z-index:99999;display:none;flex-direction:column;align-items:center;'
+ 'justify-content:center;gap:16px;background:rgba(0,0,0,.9);opacity:0;transition:opacity .16s ease;'
+ 'pointer-events:all;font-family:Kanit,system-ui,sans-serif';
el.innerHTML =
'<div style="color:#9fe8ff;font-size:17px;letter-spacing:.4px">กำลังโหลดสีตัวละคร...</div>'
+ '<div id="lcl-pct" style="color:#fff;font-size:38px;font-weight:800;text-shadow:0 0 14px rgba(80,200,255,.6)">0%</div>'
+ '<div style="width:260px;height:9px;border-radius:7px;background:rgba(255,255,255,.13);overflow:hidden">'
+ '<div id="lcl-bar" style="height:100%;width:0%;background:linear-gradient(90deg,#36e0ff,#b15bff);transition:width .12s ease"></div>'
+ '</div>';
document.body.appendChild(el);
_lcOverlayEl = el;
return el;
}
function lcProgress(done, total) {
var pct = total > 0 ? Math.round((done / total) * 100) : 100;
pct = Math.max(0, Math.min(100, pct));
var p = document.getElementById('lcl-pct');
var b = document.getElementById('lcl-bar');
if (p) p.textContent = pct + '%';
if (b) b.style.width = pct + '%';
}
function lcShowDeferred() {
if (_lcActive) return;
_lcActive = true;
lcEnsureEl();
lcProgress(0, 1);
/* เลื่อนแสดง ~140ms — ถ้าโหลดเสร็จเร็ว (cache อยู่แล้ว) จะไม่แฟลชจอ */
if (_lcShowTimer) clearTimeout(_lcShowTimer);
_lcShowTimer = setTimeout(function () {
if (!_lcActive) return;
var el = lcEnsureEl();
el.style.display = 'flex';
void el.offsetWidth;
el.style.opacity = '1';
}, 140);
}
function lcHide() {
_lcActive = false;
if (_lcShowTimer) { clearTimeout(_lcShowTimer); _lcShowTimer = null; }
if (!_lcOverlayEl) return;
var el = _lcOverlayEl;
el.style.opacity = '0';
setTimeout(function () { if (!_lcActive) el.style.display = 'none'; }, 180);
}
/** warm tint ของทุก occupant (me+bot+peer) + overlay % — เรียกตอน peer เข้าใหม่/เปลี่ยนสี กันกระตุกตอน render ครั้งแรก */
function lobbyWarmTintsWithOverlay(cb) {
lcShowDeferred();
var safety = setTimeout(lcHide, 8000); /* กันค้าง */
preloadAllLobbyTintedOccupants(function () {
clearTimeout(safety);
lcHide();
if (cb) cb();
}, function (d, t) { lcProgress(d, t); });
}
function readLobbyCharImgSize(img) {
if (!img) return { iw: 0, ih: 0 };
if (img.tagName === 'CANVAS' && img.width > 0 && img.height > 0) {
@@ -794,8 +857,11 @@
}
syncLobbyCaseBots();
reapplyLobbyBotThemesFromServer();
if (mapData && canvas) drawLobbyMap();
if (typeof window.refreshCustomizeColorRow === 'function') window.refreshCustomizeColorRow();
/* มีการเปลี่ยนสี → warm tint ทุกคน + overlay % (กันกระตุกตอน render สีใหม่) */
lobbyWarmTintsWithOverlay(function () {
if (mapData && canvas) drawLobbyMap();
if (typeof window.refreshCustomizeColorRow === 'function') window.refreshCustomizeColorRow();
});
});
}
@@ -3317,10 +3383,13 @@
socket.on('user-joined', (data) => {
peers.set(data.id, normalizeLobbyPeerFromServer(data, mapData));
applyPeerThemeFromServer(peers.get(data.id), data.lobbyColorThemeIndex, data.lobbySkinToneIndex, function () {
updatePlayersHud();
renderPeers();
redrawLobbyMap();
if (typeof window.refreshCustomizeColorRow === 'function') window.refreshCustomizeColorRow();
/* คนเข้าใหม่ → warm tint ทุกคน + overlay % (กันกระตุกตอน render ตัวใหม่ครั้งแรก) */
lobbyWarmTintsWithOverlay(function () {
updatePlayersHud();
renderPeers();
redrawLobbyMap();
if (typeof window.refreshCustomizeColorRow === 'function') window.refreshCustomizeColorRow();
});
});
});
+1 -1
View File
@@ -5412,7 +5412,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.006250415"></script>
<script src="js/play.js?v=0.006260111"></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)
+1 -1
View File
@@ -1648,7 +1648,7 @@
<script src="js/version.js?v=0.0122"></script>
<script src="js/customize-popup.js?v=40" data-customize-triggers="" data-customize-asset-base="img/03-5-Customize"></script>
<script src="js/achievements.js?v=0.002" data-asset-base="img/03-6-Profile"></script>
<script src="js/room-lobby.js?v=0.0323"></script>
<script src="js/room-lobby.js?v=0.0324"></script>
<div class="version-tag">v —</div>
</body>
</html>
+125 -10
View File
@@ -80,22 +80,34 @@ function pickLobbySkinIndexForSlot(slotIndex) {
return (Math.abs(Math.floor(s)) % LOBBY_SKIN_COUNT) + 1;
}
/** จัดสี bot ตามลำดับช่อง — ไม่ซ้ำกับผู้เล่น */
/** bot
taken = ของ "คนจริง" เทาน แลวสะสมสบอททดไปแล (ไมรวม pin ของ slot วเอง)
pin เดมท valid + ไมชนคน/บอทอ = คงเด (ไมเปลยนสวท sync); ชนเมอไหรอยเลอกใหม
แก 2 อยาง: () เด getTakenLobbyThemeIndices รวม pin ของ slot วเอง re-pick เสมอ = บอท churn sync
() "เงาสะท้อนของ user" = บอทสตรงคน (เช bot ทดแทนคนทออก pin คนนนไว) พอคนนนกลบมา บอท re-pick นสคน */
function syncLobbyBotThemeSlots(space) {
if (!space) return [];
const botCount = effectiveBotSlotCount(space);
if (!space.lobbyBotThemeBySlot || typeof space.lobbyBotThemeBySlot !== 'object') {
space.lobbyBotThemeBySlot = {};
}
const taken = getTakenLobbyThemeIndices(space);
const assigned = new Set();
space.peers.forEach((p) => {
const ti = parseInt(p.lobbyColorThemeIndex, 10);
if (ti >= 1 && ti <= LOBBY_THEME_COUNT) assigned.add(ti);
});
const pickFree = () => {
for (let t = 1; t <= LOBBY_THEME_COUNT; t++) { if (!assigned.has(t)) return t; }
return (assigned.size % LOBBY_THEME_COUNT) + 1;
};
const out = [];
for (let i = 0; i < botCount; i++) {
let slot = space.lobbyBotThemeBySlot[i];
const slot = space.lobbyBotThemeBySlot[i];
let themeIndex = parseInt(slot && slot.themeIndex, 10);
if (!(themeIndex >= 1 && themeIndex <= LOBBY_THEME_COUNT) || taken.has(themeIndex)) {
themeIndex = pickFreeLobbyThemeIndex(space);
if (!(themeIndex >= 1 && themeIndex <= LOBBY_THEME_COUNT) || assigned.has(themeIndex)) {
themeIndex = pickFree();
}
taken.add(themeIndex);
assigned.add(themeIndex);
const skinToneIndex = (() => {
const si = parseInt(slot && slot.skinToneIndex, 10);
return (si >= 1 && si <= LOBBY_SKIN_COUNT) ? si : pickLobbySkinIndexForSlot(i);
@@ -126,6 +138,78 @@ function emitLobbyTintSync(spaceId, space) {
});
}
/* ===== Game Log (รวม 7 มินิเกม + LobbyA/B) → /var/www/html/gamelog/data/log.jsonl อ่านที่ /gamelog ===== */
const GAMELOG_DIR = path.join(__dirname, '..', 'gamelog', 'data');
const GAMELOG_FILE = path.join(GAMELOG_DIR, 'log.jsonl');
try { fs.mkdirSync(GAMELOG_DIR, { recursive: true }); } catch (eMk) { /* ignore */ }
function gameLogCategoryOf(space) {
if (!space) return 'อื่นๆ';
try {
const md = (space.mapId && maps.get(space.mapId)) || space.mapData;
const gt = md && md.gameType;
const m = { quiz: 'MG1', gauntlet: 'MG2', stack: 'MG3', quiz_carry: 'MG4', jump_survive: 'MG5', space_shooter: 'MG6', balloon_boss: 'MG7' };
if (gt && m[gt]) return m[gt];
if (serverMapIsPostCaseLobbyB(space)) return 'LobbyB';
return 'LobbyA';
} catch (e) { return 'อื่นๆ'; }
}
function glog(spaceId, space, ev, detail, who) {
try {
const rec = {
t: Date.now(),
cat: gameLogCategoryOf(space),
room: String((space && space.spaceName) || spaceId || '').slice(0, 64),
who: String(who || '').slice(0, 48),
ev: String(ev || '').slice(0, 48),
detail: detail == null ? '' : String(detail).slice(0, 500),
src: 'server',
};
fs.appendFile(GAMELOG_FILE, JSON.stringify(rec) + '\n', () => {});
} catch (e) { /* ignore */ }
}
/** เก็บกวาด peer ซ้ำ (ghost / "เงาสะท้อนของ user") ออกจาก space.peers งกอน นกรณ dedup -join พลาด/race
จน peer คนเดมคาง 2 snapshot งไปทกจอ = เหนตวซ + MG3 วตาม seat = เกมคาง.
เทยบ playerKey อน (เชอถอส าม reconnect) แลวช custom; เกบตวทอยาย Map (เพ join/active าส) */
function pruneDuplicateSpacePeers(spaceId, space) {
if (!space || !space.peers || space.peers.size < 2) return false;
const validKey = (p) => (p && typeof p.playerKey === 'string' && /^[a-zA-Z0-9_-]{8,128}$/.test(p.playerKey)) ? p.playerKey : '';
const isCustom = (nn) => nn && nn !== 'ผู้เล่น' && nn.indexOf('ผู้เล่น') !== 0 && nn.indexOf('บอท') !== 0;
const lastByKey = new Map();
const lastByNickNoKey = new Map(); /* ชื่อ → pid เฉพาะ peer ที่ "ไม่มี playerKey" (ก้ำกึ่งว่าเป็น ghost) */
space.peers.forEach((p, pid) => {
if (!p) return;
const pk = validKey(p);
if (pk) { lastByKey.set(pk, pid); return; } /* มี key → ไม่เข้า nickname dedup */
const nn = normalizeLobbyNickname(p.nickname);
if (isCustom(nn)) lastByNickNoKey.set(nn, pid);
});
const remove = [];
space.peers.forEach((p, pid) => {
if (!p) return;
const pk = validKey(p);
if (pk) {
/* playerKey key (= ghost reconnect )
ไมลบดวยช: 2 คนตงชอเหมอนก (เช "MONE") = คนละ playerKey = คนละคน องเกบทงค */
if (lastByKey.get(pk) !== pid) remove.push(pid);
return;
}
/* ไม่มี playerKey → dedup ชื่อ custom ได้ (เทียบเฉพาะกลุ่มไม่มี key ด้วยกัน) */
const nn = normalizeLobbyNickname(p.nickname);
if (isCustom(nn) && lastByNickNoKey.get(nn) !== pid) remove.push(pid);
});
if (!remove.length) return false;
remove.forEach((pid) => {
const gp = space.peers.get(pid);
space.peers.delete(pid);
if (space.quizCarryLobbyReady) delete space.quizCarryLobbyReady[pid];
if (space.gauntletCrownLobbyReady) delete space.gauntletCrownLobbyReady[pid];
io.to(spaceId).emit('user-left', { id: pid });
glog(spaceId, space, 'ghost-prune', 'ลบตัวซ้ำ id=' + String(pid).slice(0, 6), (gp && gp.nickname) || '');
});
return true;
}
/** จำนวน Bot จาก Create Room — ถ้าไม่เคยบันทึก ใช้ 6 − maxPlayers (เช่น 3 คน → 3 Bot) */
function effectiveBotSlotCount(space) {
let n = parseInt(space && space.botSlotCount, 10);
@@ -3601,6 +3685,7 @@ function beginDetectiveSuspectMinigame(sid, space, cardEntry, selectedIndex) {
space.quizSession = null;
space.mapId = mapId;
space.mapData = md;
glog(sid, space, 'game-start', 'เริ่มมินิเกม: ' + ((md && md.name) || mapId) + ' (การ์ด ' + ((selectedIndex != null ? selectedIndex + 1 : '?')) + ')');
/* สุ่มไอคอนคำถามพิเศษสำหรับรอบนี้ (1 ใน 3) — ส่งจริงผ่าน joinCb เมื่อ client เข้า play.html */
maybeSpawnSpecialQuizForRun(space);
/* ไอคอนยังไม่โผล่ตอนนี้ — รอ client ส่ง 'special-quiz-arm' เมื่อเกม "live จริง" (พ้น howto/นับถอยหลัง) แล้วค่อยหน่วง 2 วิโผล่ */
@@ -7662,10 +7747,11 @@ io.on('connection', (socket) => {
io.to(spaceId).emit('peer-grace-cancel', { nickname: nickname || 'ผู้เล่น' });
}
}
/* reconnect dedup 2: playerKey match (/ peer lobbyB playerKey)
คนเดมกลบเขามากลายเปนตวซ (ghost) playerKey block ไมได "ตัวละครซ้ำ 2 ตัว" บน "ทุกจอรวม host"
(fill-bot-state humans cleanup เป non-host only host ไมเคยลบ ghost). ลบททางดวย "ชื่อ custom เดียวกัน".
ามช default (เล/บอท/าง) เลนจร 2 คนทไมงชออาจชน default นได */
/* reconnect dedup 2: ghost playerKey block (peer playerKey)
ลบดวย "ชื่อ custom เดียวกัน" แต **เฉพาะ peer เกาทไม valid playerKey**
ามลบ peer playerKey คนละค: 2 คนตงชอเหมอนก (เช "MONE") = คนละคน องเกบทงค
(เดมลบทกตวทอตรง client อซ host กลบ = "ไม่ได้เล่นเลย") */
const validPkStr = (p) => (p && typeof p.playerKey === 'string' && /^[a-zA-Z0-9_-]{8,128}$/.test(p.playerKey)) ? p.playerKey : '';
const newNickNorm = normalizeLobbyNickname(nickname);
const newNickIsCustom = newNickNorm && newNickNorm !== 'ผู้เล่น'
&& newNickNorm.indexOf('ผู้เล่น') !== 0 && newNickNorm.indexOf('บอท') !== 0;
@@ -7673,6 +7759,8 @@ io.on('connection', (socket) => {
const ghostIds = [];
space.peers.forEach((p, pid) => {
if (pid === socket.id || !p) return;
const ppk = validPkStr(p);
if (ppk && ppk !== joinPlayerKey) return; /* คนละ playerKey = คนละคน (ชื่อซ้ำได้) → ไม่ลบ */
if (normalizeLobbyNickname(p.nickname) === newNickNorm) ghostIds.push(pid);
});
ghostIds.forEach((pid) => {
@@ -7730,6 +7818,24 @@ io.on('connection', (socket) => {
peer.y = sn.y;
}
space.peers.set(socket.id, peer);
/* เก็บกวาด peer ซ้ำทั้งก้อน (ghost) — เผื่อ dedup ต่อ-join ด้านบนพลาด/race จนคนเดิมค้าง 2 ตัว */
pruneDuplicateSpacePeers(spaceId, space);
glog(spaceId, space, 'join', 'สี#' + (peer.lobbyColorThemeIndex || '?') + ' · คน ' + space.peers.size + ' + บอท ' + effectiveBotSlotCount(space) + (joinPlayerKey ? '' : ' · NO-KEY'), nickname || 'ผู้เล่น');
/* invariant: + 6 " user": grace (botSlotCount+1, )
พอคนนนกลบเขามา (reconnect หล grace) จะมงคนจร + บอทสเดยวก = วซ. ดบอทสวนเกนออกใหรวมไมเก 6
(เกม cap 6-humans อยแล ~rebalancePreviewBots แต LobbyB ไมได cap ดท server นทาง) */
{
const humansNow = space.peers.size;
const botsNow = effectiveBotSlotCount(space);
if (humansNow + botsNow > LOBBY_SLOT_TOTAL) {
space.botSlotCount = Math.max(0, LOBBY_SLOT_TOTAL - humansNow);
}
/* re-sync (re)join + "" (/LobbyB ):
เคส "เงาสะท้อนของ user" = คนหลบอทใชคนน (pin)คนนนกลบมา. join handler เคลยร pin ชนสคนแล
(7704) + syncLobbyBotThemeSlots เลอกสใหมไมชนคน แตเด emit เฉพาะตอน total>6 จออนไมปเดต = บอทยงสเด.
emit join บอทเปลยนสนจากส user นทกจอ */
emitLobbyTintSync(spaceId, space);
}
/* กลับ LobbyB เฉพาะเมื่อไม่ได้เข้า play ฉากมินิเกมที่กำลังเล่น (เช่น refresh room-lobby) */
if (space.detectiveMinigameActive) {
const clientMap = playMapId != null ? String(playMapId).trim() : '';
@@ -8894,9 +9000,11 @@ io.on('connection', (socket) => {
าเขยนท key host/client สลบกนถาวรหลงเข minigame) */
if (!space.hostPlayerKey && firstPeer.playerKey) space.hostPlayerKey = firstPeer.playerKey;
io.to(sid).emit('host-changed', { hostId: firstPeer.id });
glog(sid, space, 'host-change', 'host ใหม่ → ' + ((firstPeer.nickname) || String(firstPeer.id).slice(0, 6)), leftNick);
}
}
io.to(sid).emit('user-left', { id: socket.id });
glog(sid, space, 'leave', 'ออกจากห้อง · เหลือคน ' + space.peers.size, leftNick);
if (space.qbwSolved) qbwBroadcastScores(sid, space); // Quiz Battle ฉากเดิน — อัปเดตอันดับหลังมีคนออก
emitDetectiveLobbyBPresence(sid, space); /* คนออกจาก LobbyB → อัปเดตจำนวนที่พร้อมให้ host */
/* กำลังรอกด «เล่นต่อ» (special-quiz) แล้วมีคนออก — เอา ack คนนั้นออกแล้วเช็คใหม่ (กันค้างรอคนที่ไม่อยู่แล้ว) */
@@ -8962,6 +9070,7 @@ io.on('connection', (socket) => {
}
} catch (e) { /* ignore */ }
io.to(sid).emit('peer-bot-takeover', { nickname: leftNick, eliminated: leftWasEliminated });
glog(sid, sp, 'bot-takeover', 'หลุดเกิน ' + DISCONNECT_GRACE_SEC + 'วิ → บอทแทน' + (leftWasEliminated ? ' (ตกรอบแล้ว ไม่ spawn)' : ' (สีตามคนออก)'), leftNick);
}, DISCONNECT_GRACE_SEC * 1000);
}
}
@@ -9259,6 +9368,11 @@ io.on('connection', (socket) => {
p.jumpSurviveEliminated = true;
moveForceEmit = true;
}
/* isWalking relay anim ( out jsWalking = client anim );
เปลยนสถานะ (เรมเด/หย) force emit แมตำแหนงไมเปลยน */
const jwIn = !!data.jsWalking;
if (jwIn !== !!p.jumpSurviveWalking) moveForceEmit = true;
p.jumpSurviveWalking = jwIn;
}
if (p && md && md.gameType === 'balloon_boss' && data) {
const bbDefaultBalloons = balloonBossBalloonsForMap(md);
@@ -9333,6 +9447,7 @@ io.on('connection', (socket) => {
}
if (md && md.gameType === 'jump_survive') {
out.jumpSurviveEliminated = !!p.jumpSurviveEliminated;
out.jsWalking = !!p.jumpSurviveWalking; /* anim วิ่งให้คนอื่นเห็น */
}
if (nx !== data.x || ny !== data.y) socket.emit('user-move', out);
socket.to(sid).emit('user-move', out);
+35
View File
@@ -0,0 +1,35 @@
{"t":1782460590560,"cat":"MG3","room":"hhh","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-3 Tower block (การ์ด 1)","src":"server"}
{"t":1782460590711,"cat":"MG3","room":"hhh","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782460590711,"cat":"MG3","room":"hhh","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782460591013,"cat":"MG3","room":"hhh","who":"MONE","ev":"join","detail":"สี#8 · คน 1 + บอท 3","src":"server"}
{"t":1782460591315,"cat":"MG3","room":"hhh","who":"Q","ev":"join","detail":"สี#7 · คน 2 + บอท 3","src":"server"}
{"t":1782460717354,"cat":"LobbyB","room":"hhh","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782460717354,"cat":"LobbyB","room":"hhh","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782460718032,"cat":"LobbyB","room":"hhh","who":"Q","ev":"join","detail":"สี#7 · คน 2 + บอท 3","src":"server"}
{"t":1782460719931,"cat":"LobbyB","room":"hhh","who":"MONE","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782460720509,"cat":"LobbyB","room":"hhh","who":"MONE","ev":"join","detail":"สี#8 · คน 2 + บอท 3","src":"server"}
{"t":1782461143015,"cat":"MG3","room":"hhh","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-3 Tower block (การ์ด 1)","src":"server"}
{"t":1782461143223,"cat":"MG3","room":"hhh","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782461143223,"cat":"MG3","room":"hhh","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782461143637,"cat":"MG3","room":"hhh","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 3","src":"server"}
{"t":1782461143920,"cat":"MG3","room":"hhh","who":"MONE","ev":"join","detail":"สี#8 · คน 2 + บอท 3","src":"server"}
{"t":1782461213243,"cat":"MG3","room":"hhh","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782461213243,"cat":"MG3","room":"hhh","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782461223245,"cat":"MG3","room":"hhh","who":"Q","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
{"t":1782461274423,"cat":"LobbyB","room":"hhh","who":"MONE","ev":"join","detail":"สี#8 · คน 1 + บอท 4","src":"server"}
{"t":1782461327954,"cat":"LobbyA","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 3","src":"server"}
{"t":1782461351978,"cat":"LobbyA","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 4","src":"server"}
{"t":1782461363786,"cat":"LobbyA","room":"aaa","who":"MONE","ev":"join","detail":"สี#8 · คน 2 + บอท 4","src":"server"}
{"t":1782461415359,"cat":"LobbyB","room":"aaa","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782461415359,"cat":"LobbyB","room":"aaa","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782461425362,"cat":"LobbyB","room":"aaa","who":"Q","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
{"t":1782461428827,"cat":"LobbyA","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 1 + บอท 3","src":"server"}
{"t":1782461438053,"cat":"LobbyA","room":"aaa","who":"MONE","ev":"join","detail":"สี#8 · คน 2 + บอท 3","src":"server"}
{"t":1782461466584,"cat":"MG3","room":"aaa","who":"","ev":"game-start","detail":"เริ่มมินิเกม: Minigame-3 Tower block (การ์ด 1)","src":"server"}
{"t":1782461466705,"cat":"MG3","room":"aaa","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782461466705,"cat":"MG3","room":"aaa","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782461467047,"cat":"MG3","room":"aaa","who":"MONE","ev":"join","detail":"สี#8 · คน 1 + บอท 3","src":"server"}
{"t":1782461467352,"cat":"MG3","room":"aaa","who":"Q","ev":"join","detail":"สี#7 · คน 2 + บอท 3","src":"server"}
{"t":1782461544478,"cat":"MG3","room":"aaa","who":"Q","ev":"host-change","detail":"host ใหม่ → MONE","src":"server"}
{"t":1782461544479,"cat":"MG3","room":"aaa","who":"Q","ev":"leave","detail":"ออกจากห้อง · เหลือคน 1","src":"server"}
{"t":1782461554486,"cat":"MG3","room":"aaa","who":"Q","ev":"bot-takeover","detail":"หลุดเกิน 10วิ → บอทแทน (สีตามคนออก)","src":"server"}
+129
View File
@@ -0,0 +1,129 @@
<?php /* gamelog viewer — log รวม 7 มินิเกม + LobbyA/B (อ่านจาก log.php) */ ?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Game Log · Justice</title>
<style>
:root { --bg:#0a0e1a; --panel:#121a2e; --line:#23304d; --txt:#cfe0ff; --dim:#7f93bd; --acc:#37e0ff; }
* { box-sizing:border-box; }
body { margin:0; background:var(--bg); color:var(--txt); font:14px/1.5 "Kanit",system-ui,sans-serif; }
header { position:sticky; top:0; z-index:5; background:linear-gradient(180deg,#0d1426,#0a0e1a); border-bottom:1px solid var(--line); padding:10px 14px; }
h1 { margin:0 0 8px; font-size:18px; letter-spacing:.5px; color:#fff; }
h1 small { color:var(--dim); font-size:12px; font-weight:400; }
.tabs { display:flex; flex-wrap:wrap; gap:6px; }
.tab { cursor:pointer; padding:5px 11px; border-radius:8px; border:1px solid var(--line); background:#0f1730; color:var(--dim); font-size:13px; user-select:none; }
.tab:hover { border-color:var(--acc); color:var(--txt); }
.tab.active { background:var(--acc); color:#05101f; border-color:var(--acc); font-weight:700; }
.tab .n { opacity:.7; font-size:11px; margin-left:4px; }
.bar { display:flex; align-items:center; gap:12px; margin-top:8px; font-size:12px; color:var(--dim); flex-wrap:wrap; }
.bar label { cursor:pointer; }
.bar input[type=text]{ background:#0f1730; border:1px solid var(--line); color:var(--txt); border-radius:7px; padding:5px 9px; min-width:160px; }
.btn { cursor:pointer; padding:5px 11px; border-radius:8px; border:1px solid var(--line); background:#0f1730; color:var(--txt); font-size:12px; }
.btn:hover { border-color:var(--acc); }
.btn.danger:hover { border-color:#ff5b6e; color:#ff8a97; }
#wrap { padding:6px 8px 60px; }
table { width:100%; border-collapse:collapse; }
th,td { text-align:left; padding:5px 9px; border-bottom:1px solid #182238; vertical-align:top; }
th { position:sticky; top:0; color:var(--dim); font-weight:600; font-size:12px; background:var(--bg); }
td.t { color:var(--dim); white-space:nowrap; font-variant-numeric:tabular-nums; font-size:12px; }
td.cat span { display:inline-block; padding:1px 8px; border-radius:6px; font-size:12px; font-weight:700; }
td.room { color:var(--dim); font-size:12px; max-width:130px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
td.who { color:#bcd; font-size:13px; }
td.ev { font-weight:600; }
td.detail { color:var(--dim); font-size:12px; word-break:break-word; }
tr.src-client td.ev::after { content:" ·c"; color:#6a7da6; font-size:10px; font-weight:400; }
.empty { text-align:center; color:var(--dim); padding:40px; }
/* cat colors */
.c-LobbyA{background:#1d3a5f;color:#9fd4ff} .c-LobbyB{background:#2a2f63;color:#c4c2ff}
.c-MG1{background:#7a2540;color:#ffc0d4} .c-MG2{background:#2a5f3a;color:#bfffd0}
.c-MG3{background:#5f4a1d;color:#ffe6a0} .c-MG4{background:#1d5f5a;color:#a0fff2}
.c-MG5{background:#3a2a5f;color:#d6c0ff} .c-MG6{background:#5f1d3a;color:#ffb0c8}
.c-MG7{background:#5f3a1d;color:#ffd0a0} .cother{background:#2a3550;color:#9fb0d0}
.ev-leave td.ev, .ev-disconnect td.ev{color:#ff8a97} .ev-join td.ev{color:#7ee787}
.ev-bot-takeover td.ev,.ev-ghost-prune td.ev{color:#ffd166} .ev-error td.ev{color:#ff5b6e}
</style>
</head>
<body>
<header>
<h1>🎮 Game Log <small>— 7 มินิเกม + LobbyA / LobbyB · srv1361159</small></h1>
<div class="tabs" id="tabs"></div>
<div class="bar">
<label><input type="checkbox" id="auto" checked> auto-refresh 3s</label>
<input type="text" id="search" placeholder="ค้นหา (ชื่อ/ห้อง/event/detail)">
<span id="status"></span>
<span style="flex:1"></span>
<button class="btn" id="reload">รีโหลด</button>
<button class="btn danger" id="clear">ล้าง log</button>
</div>
</header>
<div id="wrap"><div class="empty">กำลังโหลด...</div></div>
<script>
var CATS = ['ALL','LobbyA','LobbyB','MG1','MG2','MG3','MG4','MG5','MG6','MG7','อื่นๆ'];
var curCat = 'ALL';
var rows = [];
var counts = {};
function catClass(c){ var m={'LobbyA':'c-LobbyA','LobbyB':'c-LobbyB','MG1':'c-MG1','MG2':'c-MG2','MG3':'c-MG3','MG4':'c-MG4','MG5':'c-MG5','MG6':'c-MG6','MG7':'c-MG7'}; return m[c]||'cother'; }
function fmtTime(ms){ var d=new Date(ms); function p(n){return (n<10?'0':'')+n;} return p(d.getHours())+':'+p(d.getMinutes())+':'+p(d.getSeconds()); }
function esc(s){ return String(s==null?'':s).replace(/[&<>]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;'}[c];}); }
function renderTabs(){
var t=document.getElementById('tabs'); t.innerHTML='';
CATS.forEach(function(c){
var d=document.createElement('div'); d.className='tab'+(c===curCat?' active':'');
var n=counts[c]!=null?('<span class="n">'+counts[c]+'</span>'):'';
d.innerHTML=esc(c)+n;
d.onclick=function(){ curCat=c; renderTabs(); render(); load(); };
t.appendChild(d);
});
}
function render(){
var wrap=document.getElementById('wrap');
var q=(document.getElementById('search').value||'').toLowerCase().trim();
var list=rows.filter(function(r){
if(!q) return true;
return ((r.who||'')+' '+(r.room||'')+' '+(r.ev||'')+' '+(r.detail||'')).toLowerCase().indexOf(q)>=0;
});
if(!list.length){ wrap.innerHTML='<div class="empty">ยังไม่มี log'+(curCat!=='ALL'?(' ใน '+esc(curCat)):'')+'</div>'; return; }
var h='<table><thead><tr><th>เวลา</th><th>หมวด</th><th>ห้อง</th><th>ใคร</th><th>event</th><th>รายละเอียด</th></tr></thead><tbody>';
list.forEach(function(r){
h+='<tr class="src-'+esc(r.src||'')+' ev-'+esc((r.ev||'').replace(/[^a-z-]/gi,''))+'">'+
'<td class="t">'+fmtTime(r.t)+'</td>'+
'<td class="cat"><span class="'+catClass(r.cat)+'">'+esc(r.cat)+'</span></td>'+
'<td class="room" title="'+esc(r.room)+'">'+esc(r.room)+'</td>'+
'<td class="who">'+esc(r.who)+'</td>'+
'<td class="ev">'+esc(r.ev)+'</td>'+
'<td class="detail">'+esc(r.detail)+'</td></tr>';
});
h+='</tbody></table>';
wrap.innerHTML=h;
}
function load(){
fetch('log.php?cat='+encodeURIComponent(curCat)+'&limit=800&_='+Date.now())
.then(function(r){return r.json();})
.then(function(j){
rows = (j&&j.logs)||[];
document.getElementById('status').textContent = (rows.length)+' รายการ · '+new Date().toLocaleTimeString('th-TH');
render();
}).catch(function(){ document.getElementById('status').textContent='โหลดไม่ได้'; });
// counts ทุกหมวด (จาก ALL)
fetch('log.php?cat=ALL&limit=2000&_c='+Date.now()).then(function(r){return r.json();}).then(function(j){
counts={ALL:0}; CATS.forEach(function(c){counts[c]=0;});
((j&&j.logs)||[]).forEach(function(r){ counts.ALL++; if(counts[r.cat]!=null) counts[r.cat]++; else counts['อื่นๆ']++; });
renderTabs();
}).catch(function(){});
}
document.getElementById('reload').onclick=load;
document.getElementById('search').oninput=render;
document.getElementById('clear').onclick=function(){
if(!confirm('ล้าง log ทั้งหมด?')) return;
fetch('log.php?action=clear').then(function(){ load(); });
};
renderTabs(); load();
setInterval(function(){ if(document.getElementById('auto').checked) load(); }, 3000);
</script>
</body>
</html>
+64
View File
@@ -0,0 +1,64 @@
<?php
/* gamelog endpoint — POST append (client) / GET fetch (viewer) / GET action=clear
เก็บเป็น JSONL ที่ data/log.jsonl (server.js เขียนตรง ๆ ด้วย; ไฟล์เดียวกัน) */
header('Content-Type: application/json; charset=utf-8');
$dir = __DIR__ . '/data';
$file = $dir . '/log.jsonl';
if (!is_dir($dir)) { @mkdir($dir, 0777, true); }
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
if ($method === 'POST') {
$raw = file_get_contents('php://input');
$j = json_decode($raw, true);
if (!is_array($j)) { echo json_encode(['ok' => false, 'err' => 'bad json']); exit; }
$items = (isset($j['items']) && is_array($j['items'])) ? $j['items'] : [$j];
$lines = '';
foreach ($items as $it) {
if (!is_array($it)) continue;
$rec = [
't' => isset($it['t']) ? (int)$it['t'] : (int)(microtime(true) * 1000),
'cat' => mb_substr((string)(isset($it['cat']) ? $it['cat'] : 'อื่นๆ'), 0, 24),
'room' => mb_substr((string)(isset($it['room']) ? $it['room'] : ''), 0, 64),
'who' => mb_substr((string)(isset($it['who']) ? $it['who'] : ''), 0, 48),
'ev' => mb_substr((string)(isset($it['ev']) ? $it['ev'] : ''), 0, 48),
'detail' => mb_substr((string)(isset($it['detail']) ? $it['detail'] : ''), 0, 500),
'src' => mb_substr((string)(isset($it['src']) ? $it['src'] : 'client'), 0, 12),
];
$lines .= json_encode($rec, JSON_UNESCAPED_UNICODE) . "\n";
}
if ($lines !== '') @file_put_contents($file, $lines, FILE_APPEND | LOCK_EX);
/* ring trim กันไฟล์ใหญ่เกิน */
if (@filesize($file) > 4000000) {
$all = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($all && count($all) > 5000) {
$all = array_slice($all, -5000);
@file_put_contents($file, implode("\n", $all) . "\n", LOCK_EX);
}
}
echo json_encode(['ok' => true]);
exit;
}
if (isset($_GET['action']) && $_GET['action'] === 'clear') {
@file_put_contents($file, '', LOCK_EX);
echo json_encode(['ok' => true]);
exit;
}
/* GET: คืน log (กรองตาม cat) ล่าสุดก่อน */
$cat = isset($_GET['cat']) ? $_GET['cat'] : '';
$limit = isset($_GET['limit']) ? min(3000, max(1, (int)$_GET['limit'])) : 800;
$out = [];
if (is_file($file)) {
$all = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($all) {
for ($i = count($all) - 1; $i >= 0 && count($out) < $limit; $i--) {
$r = json_decode($all[$i], true);
if (!is_array($r)) continue;
if ($cat && $cat !== 'ALL' && (!isset($r['cat']) || $r['cat'] !== $cat)) continue;
$out[] = $r;
}
}
}
echo json_encode(['ok' => true, 'logs' => $out, 'count' => count($out)]);
+31 -11
View File
@@ -165,9 +165,9 @@
"detail": "เกม Quiz battle เกมที่ 1 ตัวละครผู้เล่นเร็วเกินไป",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"updated": "2026-06-25",
"note": "",
"cat": "Minigame 2"
"cat": "อื่นๆ"
},
{
"n": 18,
@@ -414,9 +414,9 @@
"name": "เกม กระโดด",
"detail": "ท่ากระโดดแปลก ยืนซอยเท้าทำไม",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"status": "แก้ไขแล้ว",
"updated": "2026-06-26",
"note": "anim ไม่ sync: ยืนบนแท่นที่เลื่อนลง world Y เปลี่ยนทุกเฟรม → ปลายทางเดา isWalking จาก delta = เห็น ซอยเท้า ตลอด. แก้: ส่ง isWalking จริงใน move (jsWalking) + ปลายทางใช้ค่านั้น (play.js ~19162/20501/24930). v0.006260100",
"cat": "Minigame 5"
},
{
@@ -424,9 +424,9 @@
"name": "เกม กระโดด",
"detail": "ขั้นบันไดมันถี่ไป บางทีกระโดดแล้วเบิ้ลขั้นเลย ปรับให้ระยะห่างแต่ละขั้นมันเท่ากัน",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"status": "แก้ไขแล้ว",
"updated": "2026-06-26",
"note": "ขั้นถี่/ไม่เท่า → เบิ้ลขั้น+ติดรอแท่ง. แก้ layout mnptfts2: ระยะเท่ากันทุกขั้น (gap 3 แถว, rows 2/5/8/11/14/17, zigzag กว้าง6) ในโซนปลอดภัย (เลี่ยง hazard แถว 0/18/19). reach apex 4.5 > 3 = ถึงทุกขั้น (ไม่ติดรอ #43b) + ขั้นถัดไป-อีกขั้น 6 แถว > reach = กระโดดข้ามไม่ได้ (ไม่เบิ้ล). seam รอยต่อ wrap ห่าง 5 แถว 1 จุด/รอบ (h=20 หาร 3 ไม่ลง). restart service หลังแก้แมป | รอบ2: (ก) แก้ลายแท่นบิด — เดิม width เท่ากัน(6)ทำให้ภาพถูกบีบ → ตั้ง width = ขนาดจริงของรูป (เล็ก=var3 7ช่อง/212px, กลาง=var2 13ช่อง/389px) วาดได้สัดส่วนจริงไม่บิด. (ข) ติดบนสุด = seam รอยต่อ wrap 5แถว >ระยะกระโดด(apex4.5) → bump jumpSurviveJumpHeightMult 1.5→1.7 (apex 5.1) ให้ข้าม seam ได้ แต่ข้าม2ขั้น(6แถว)ยังไม่ได้. var1(ใหญ่ 46ช่อง)ใหญ่เกินแมป35 ใช้ไม่ได้ไม่บิด → ใช้ เล็ก/กลาง",
"cat": "Minigame 5"
},
{
@@ -484,9 +484,9 @@
"name": "กรณีผู้เล่นหลุดออกจากเกม",
"detail": "ถ้ามีคนหลุด ให้กลายเป้นบอทแทน",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว 2026-06-25: คนออกระหว่าง LobbyB/เปลี่ยนหน้า กลายเป็นบอทสีตามคนออก (ดูบั๊ก #73)",
"cat": "ระบบ disconnect"
},
{
@@ -718,5 +718,25 @@
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod room-lobby.js?v=0.0313): เดิม overlay เป็น display:flex default จึงเด้งทุกครั้งที่โหลดหน้า. เพิ่มตรวจสิทธิ์ผ่าน navigator.permissions.query(microphone) + จำสถานะใน localStorage (justiceMicPromptHandled). แสดงเฉพาะครั้งแรกที่ยังไม่เคยตัดสินใจ (permission=prompt และยังไม่เคยกดอนุญาต/ปิด); ถ้า granted แล้ว/เคยจัดการแล้ว → ซ่อนทันที (มี inline script กันแฟลชก่อน paint). ปุ่ม 🎤 ขอสิทธิ์ไมค์ ในห้องยังใช้ขอใหม่ได้. ไฟล์: room-lobby.html (inline gate) + room-lobby.js (gateMicPermissionOverlay, hideMicPermissionOverlay).",
"cat": "LobbyA"
},
{
"n": 73,
"name": "คนออก LobbyB / ช่วงเปลี่ยนหน้า → ไม่เติมบอท + ค้างรอผู้เล่น เริ่มเกมต่อไม่ได้",
"detail": "คนหลุดระหว่างนั่งอยู่ LobbyB หรือช่วงเปลี่ยนหน้า minigame→LobbyB เดิมไม่ถูกเติมบอทแทน + ยังถูกนับใน roster คดี → host กดเริ่มสืบสวนรอบต่อไปไม่ได้ ค้าง รอผู้เล่น (x/y) ตลอดไป",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "ขยาย grace+bot-takeover gate ให้ครอบ LobbyB/คดีล็อก ไม่ใช่แค่ minigame active (server.js ~8921: inCaseFlowForBot = detectiveMinigameActive || serverMapIsPostCaseLobbyB || (joinLocked && caseParticipantNicknames.size>0)). คนออก→หลัง grace 10วิ ลบจาก roster + เติมบอทสีตามคนออก + presence ใหม่ → allHere=true เล่นต่อได้. + ส่ง botSlotCount ใน lobby-tint-sync (server.js ~121) + client อัปเดต lobbyBotSlotCount ก่อน render (room-lobby.js ~768) ให้บอทโผล่ทันทีไม่ต้อง re-join. v0.0323",
"cat": "ระบบ disconnect"
},
{
"n": 74,
"name": "แถวหน้าตัวละคร (ready ใต้ปุ่ม HOW TO) ค้างบนจอระหว่างเล่นเกม",
"detail": "หลังกด HOW TO/Ready แล้วเริ่มเกม กล่องแถว avatar คนที่กด ready (ใต้ปุ่ม) ไม่ถูกซ่อน ค้างอยู่กลางจอล่างทับฉากเกม (เห็นชัดใน MG3 stack) — กระทบ MG1/3/5/6",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-26",
"note": "กล่องลอย #gch-ready-avatars-float ถูกซ่อนเฉพาะ MG2 (dismissGauntletCrownHowtoOverlayPlay) + MG4 (quiz_carry) เท่านั้น. begin*CountdownThenRun ของ MG1/3/5/6 (stack/jump/space/quiz) ตั้ง gauntletCrownHowtoVisible=false แต่ไม่เรียก hideReadyAvatarsFloat() → กล่องค้าง display:flex ทั้งเกม (ไม่มี driver เรียกใหม่ตอน live). แก้: เพิ่ม hideReadyAvatarsFloat() ในทั้ง 4 ฟังก์ชันเริ่มเกม (play.js). v0.006260101",
"cat": "อื่นๆ"
}
]
@@ -0,0 +1,722 @@
[
{
"n": 1,
"name": "แต่งตัวไม่ได้",
"detail": "แต่งตัวยังไม่ได้ มีพี่คิวทำได้คนเดียว",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 2,
"name": "ตัวละครไม่แสดง",
"detail": "หน้าเลือกเกม Quiz battle ภาพตัวละครไม่แสดง",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 1"
},
{
"n": 3,
"name": "ระบบ join เกมมีปัญหา",
"detail": "เมื่อเข้ามินิเกม : บางคนเล่นเกมกะบอท บางคนเล่นกะเพื่อน แต่ละคนเข้ามินิเกมแล้วเจอกันบ้าง ไม่เจอกันบ้าง",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 4,
"name": "ระบบ Host มีปัญหา - 1",
"detail": "เป็น host ในเกม ตอนที่เลือกผู้ต้องสงสัย รอบที่ 2 ระบบบอกว่าให้รอ host ไม่สามารถเลือกการ์ดและกดเริ่มสืบสวนได้",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 5,
"name": "ระบบ Host มีปัญหา - 2",
"detail": "เมื่อกดออกจากเกม Host เปลี่ยนจากพี่ต้อมเป็นพี่คิวอัติโนมัติ",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 6,
"name": "ระบบ Host มีปัญหา - 3",
"detail": "เมื่อเข้ามินิเกม ทุกคนเห็นตัวเองเป็น host หมดเลย แต่กดเริ่มเกมไม่ได้",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 7,
"name": "ระบบสีตัวละคร ยังใช้ไม่ได้",
"detail": "สีตัวละครเปลี่ยนไปเรื่อย ไม่รุ้ว่าเราสีอะไรแน่ งงไปหมด",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 8,
"name": "หน้าเข้าร่วมเกมส์",
"detail": "ห้องไหนที่เริ่มเกมส์ไปแล้ว เมื่อกดเข้าร่วมแสดง popup alert error ปรับดีไซต์ให้สวยงามกว่านี้",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 9,
"name": "เกมวิ่งขึ้นบันได ผิด flow",
"detail": "ปรับ flow ให้ถูกต้อง คือ ใครตกจอตาย แต่ถ้าขึ้นไปอยู่บนสุดแล้วจะกระโดดไม่ไปต่อเฉยๆ แต่ไม่ตาย",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 10,
"name": "เกมวิ่งขึ้นบันได",
"detail": "เวลาหมดแล้ว ให้เล่นรอบ 2 auto / รอบ 3 มี popup ให้กดเล่นต่อ",
"prio": "",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 2"
},
{
"n": 11,
"name": "เกมยิงลูกโป่ง",
"detail": "ฉากเกมยิงลูกโป่ง (MG7 Mega Virus) เล็กไป — ต้องการให้ภาพพื้นหลัง (BG) เต็มจอ แต่การเดิน (movement) และพื้นที่เล่น (area) ต่างๆ คงเดิม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod play.js?v=0.006250401): BG ของ MG7 เป็นภาพ 16:9 (1920x1080) แต่โค้ดเดิม map ภาพเข้า world rect 4:3 (640x480) ทำให้ภาพถูกบีบ + เหลือขอบดำซ้าย-ขวา = ฉากดูเล็ก. เปลี่ยนการวาด BG ของ balloon_boss เป็นแบบ cover เต็มจอ (รักษาสัดส่วน 16:9, crop ส่วนเกิน, จัดกึ่งกลาง) วาดแยกจาก world layer → กล้อง/movement/area ของเกมคงเดิมทุกอย่าง เปลี่ยนเฉพาะพื้นหลังให้เต็มจอ. ไฟล์: Game/public/js/play.js (draw loop, สาขา isBalloonBoss cover-fill).",
"cat": "Minigame 7"
},
{
"n": 12,
"name": "เกมต่อตึก ผิด flow",
"detail": "ปรับ flow ให้ถูกต้อง คือ เล่นทีละคน เรียงตามคิวไปเรื่อย โดยที่ blog ตามสีผู้เล่นด้วย",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 13,
"name": "เข้าร่วมห้องไม่ได้",
"detail": "สร้างห้องส่วนตัวเเล้ว เเต่เพื่อนจอยเข้าไปไม่ได้ ทั้งที่กรอกข้อมูลครบ",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 14,
"name": "Room ID",
"detail": "เวลาจอยห้องให้กรอกเเค่ ID กับรหัสที่สร้าง",
"prio": "ต่ำ",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 15,
"name": "จอยห้องคนอื่น",
"detail": "ในกรณีที่จอยห้องคนอื่น ไปหน้าเข้าร่วม เเละกดปุ่มเข้าร่วมไปเเล้ว จากนั้นกดปุ่ม exit จะพาไปหน้าสร้างห้อง สิ่งที่ถูกต้อง มันควรมาหน้า ห้องเข้าร่วมเกม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 16,
"name": "grid กับ ฉาก ไม่ตรงกัน",
"detail": "เกม Quiz battle เกมที่ 1 ฉากกับกริดไม่ตรงกัน ทำให้เดินทะลุ หรือเดินติดในจุดที่ไม่ควรติด",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 1"
},
{
"n": 17,
"name": "ตัวละครวิ่งเร็วเกินไป",
"detail": "เกม Quiz battle เกมที่ 1 ตัวละครผู้เล่นเร็วเกินไป",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 2"
},
{
"n": 18,
"name": "Oauth Social log-in error",
"detail": "ไม่สามารถ log-inด้วย facebook / Google ได้ path: /oauth-public.php",
"prio": "ต่ำ",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 19,
"name": "หน้า login",
"detail": "นโยบายความเป็นส่วนตัวกดไม่ได้",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 20,
"name": "แสดงเลข version ตรงมุมจอ",
"detail": "แสดงเลข version ตรงมุมจอขวาบนหรือขวาล่างก็ได้",
"prio": "ต่ำ",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — badge มุมขวาล่าง อ่าน ?v= อัตโนมัติ",
"cat": "อื่นๆ"
},
{
"n": 21,
"name": "ลดขนาดตัวละคร",
"detail": "ลดขนาดตัวละครในห้อง lobby ลงอีก 2เท่า",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 22,
"name": "เปลี่ยน ui popup ตอนพูดบนหัว",
"detail": "แก้ png seq บนหัวใหม่ เป็นแบบอื่น",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 23,
"name": "ชื่่อตัวละครทับลำโพง/ทับ bubble",
"detail": "ใน lobby ขยับชื่อลงมาอย่าให้ทับ",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 24,
"name": "UI แต่งตัว : การแจ้งเตือนสีตัวละครถูกใช้แล้ว",
"detail": "ตอนเลือกสีให้มีคำแจ้งเตือน used",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 25,
"name": "เพิ่ม ข้อความแจ้งเตือนอัพเดทกลางจอ",
"detail": "ถ้ามีการอัพเดทซีน อัพเดทสีตัวละครให้มีป็อปอัพเด้งมากลางหน้าจอ ว่ามีการอัพเดทและให้รอโหลด",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 26,
"name": "เอาเงาตัวละครออก 1 อัน",
"detail": "ตอนนี้ตัวละครมีเงา 2 ชั้น เอาออกไป 1 ชั้น",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — ปิด default-shadow overlay เหลือเงาในสไปรต์",
"cat": "อื่นๆ"
},
{
"n": 27,
"name": "optimize ตัวละครให้เล็กลงอีก มันโหลดช้า",
"detail": "",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 28,
"name": "ปุ่ม start ใน lobby",
"detail": "ทำให้ดูโต้ตอบหน่อย เช่นพอเดินเข้าไปใกล้ให้มัน glow ขึ้นมา",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 29,
"name": "เกมต่อตึก ผิด flow",
"detail": "สี blog กับคนเล่นยังไม่สัมพันกัน ต้องเรียงตามสี และตามคิว",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 30,
"name": "เกมต่อตึก บั๊กคนที่ 5",
"detail": "คนที่ 5 ไม่สามารถเล่นได้",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 31,
"name": "เอา mock การ์ดผู้ต้องหาเก่าออกไป",
"detail": "มันแว่บๆมา",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส) — ซ่อนรูปจนรูปคดีจริงโหลด + spinner",
"cat": "LobbyB"
},
{
"n": 32,
"name": "เอา popup ขออนุญาตใช้ไมค์ออกไปเลย น่ารำคาญ",
"detail": "",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 33,
"name": "หน้า howto เปลี่ยนสีปุ่ม ready ด้วยตอน กดready แล้ว",
"detail": "เปลี่ยนเป้นสีเขียวพร้อมเครื่องหมายถูกตามดีไซน์",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 34,
"name": "เกม ใช่ หรือไม่ : ตอนจบบั๊ค",
"detail": "ตอนสรุปคะแนน ยังนับเวลาอยุ่ และขึ้น gameover ทับหน้าสรุปคะแนน",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG1 ใช่/ไม่: capture mg1AnyScore ก่อนล้าง (gameover) + guard quiz-phase หลังจบ (timer)",
"cat": "Minigame 1"
},
{
"n": 35,
"name": "เกมวิ่ง : กดปุ่มไม่กระโดด",
"detail": "ปุ่ม jump กดแล้วไม่กระโดด",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 36,
"name": "เกมวิ่ง : ตอนจบบั๊ค วิ่งเลยฉาก",
"detail": "ตอนจบบั๊ค วิ่งเลยฉาก (ในบางรอบ)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG2 วิ่งเลยฉาก: walk-to-finish ค่อยๆเดินเข้าเส้น + clamp ไม่เลยเส้น",
"cat": "Minigame 2"
},
{
"n": 37,
"name": "popup รับหลักฐาน ทับไมค์",
"detail": "กดไมค์ไม่ได้ ตอนรับหลักฐาน ไปดูเรื่องเลเยอร์อีกที",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 38,
"name": "หน้าส่งหลักฐาน ปุ่มส่งหลักฐานหลุดจอ (เครื่องของเนส)",
"detail": "เป้นเครื่องเนสคนเดียว",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "ระบบ disconnect"
},
{
"n": 39,
"name": "ห้องส่งหลักฐาน : เมื่อมีคนหลุด 1 คน จะไปต่อไม่ได้เลย",
"detail": "",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — testimony: disconnect ลบ submit/ready + client ใช้ totalPlayers authoritative",
"cat": "ระบบ disconnect"
},
{
"n": 40,
"name": "จำนวนคน ให้เล่นได้ตั้งแต่ 4-6 คน",
"detail": "กรณีเลือกผู้เล่นตั้งแต่ 4 คนขึ้นไปไม่ต้องมี bot",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 41,
"name": "เกม หยิบแผ่นป้าย",
"detail": "ต้องเล่นให้ครบ 10 ข้อก่อน ค่อยจบเกม (ตอนนี้เล่นถูก 1 ข้อ จบเกมเลย)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG4 หยิบป้าย: ใช้ map จริง (currentPlayMapId) เปิด mission flow + default 10 ข้อ",
"cat": "Minigame 4"
},
{
"n": 42,
"name": "เกม กระโดด",
"detail": "ท่ากระโดดแปลก ยืนซอยเท้าทำไม",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 43,
"name": "เกม กระโดด",
"detail": "ขั้นบันไดมันถี่ไป บางทีกระโดดแล้วเบิ้ลขั้นเลย ปรับให้ระยะห่างแต่ละขั้นมันเท่ากัน",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 44,
"name": "เกมกระโดด",
"detail": "อีเวนท์พิเศษ (ทนาย/เทพความรู้) หายไป",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG5 อีเวนต์พิเศษ: เพิ่ม branch jump_survive วาง icon บน platform",
"cat": "Minigame 5"
},
{
"n": 45,
"name": "เกมไม่รองรับ 4K",
"detail": "หน้ารับหลักฐาน (หลังจากเล่นเกมจบ)",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 46,
"name": "เกมยิิงอุกาบาด",
"detail": "สีตัวละครยังไม่ตรงกับที่เลือกมา",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG6 สี: ยาน Rocket-1..8 ตาม lobby theme ของผู้เล่น",
"cat": "Minigame 6"
},
{
"n": 47,
"name": "เกมยิิงอุกาบาด",
"detail": "อุกาบาดให้พุ่งเร็วกว่านี้ และลูกเล็กกว่านี้เกมง่ายไป",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG6: อุกาบาตเร็วขึ้น (+~50%) + เล็กลง (48->34/11->8)",
"cat": "Minigame 6"
},
{
"n": 48,
"name": "เพิ่ม popup เมื่อเกมค้าง เล่นต้่อไม่ได้ ให้มีปุ่มกดออกไปหน้าแรกสุด",
"detail": "สาเหตุเกิดจากมีคนหลุดระหว่างรอ host กด",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "ระบบ disconnect"
},
{
"n": 49,
"name": "กรณีผู้เล่นหลุดออกจากเกม",
"detail": "ถ้ามีคนหลุด ให้กลายเป้นบอทแทน",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "ระบบ disconnect"
},
{
"n": 50,
"name": "ผู้เล่น 6 คน: บางเกมเล่นได้แค่ 5 (คนที่ 6 หายจากสรุป)",
"detail": "สรุปคะแนนฝั่ง client slice(0,5) ตัดคนที่ 6 — แก้เป็น slice(0,6) ทุกมินิเกม (MG1/3/4/5/6/7)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส)",
"cat": "Minigame 1"
},
{
"n": 51,
"name": "ผู้เล่น 6 คน: คะแนนตอนจบไม่ตรงกันแต่ละจอ",
"detail": "MG1 merge คะแนน server / MG6,7 merge คะแนน host -> ครบ 6 ตรงกัน (MG3 ยัง local)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส)",
"cat": "Minigame 6"
},
{
"n": 52,
"name": "หน้าไต่สวน/สรุปหลักฐาน: กรอบปลิ้นตอน 6 คน",
"detail": "member row grid repeat(5)->repeat(6) ใน testimony-overlay.css (คนที่6 ตกแถว2)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส)",
"cat": "LobbyB"
},
{
"n": 53,
"name": "เพิ่มหน้าโหลด (loading) ตอนเข้าเกม",
"detail": "overlay spinner กันเห็นจอเปล่า/asset แว่บ ซ่อนเมื่อ draw เฟรมแรก + safety 8s",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 54,
"name": "host โหลดนานมากตอนเข้าเกม",
"detail": "optimize asset/char (เกี่ยว row 28) — loading overlay ช่วยซ่อน แต่ยังโหลดช้าจริง",
"prio": "สูง",
"status": "เปิด",
"updated": "2026-06-25",
"note": "loading overlay บังอาการแล้ว · root = canvas tint ภาพตัวละครใหญ่ (source ไม่อยู่ใน repo) → ต้อง optimize ภาพแยก",
"cat": "อื่นๆ"
},
{
"n": 55,
"name": "หลังออกจาก LobbyA ไม่ให้ใครเข้า join",
"detail": "server มี joinLocked (set ตอน LobbyA->case ~8656, check ~7585 whitelist nickname) — ต้อง repro ว่าใครเข้าได้ยังไง (room list ยังโชว์? ชื่อซ้ำ whitelist?)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 — กรองห้อง joinLocked ออกจาก /api/spaces list (ห้องเริ่มคดีแล้วหาย) + server reject อยู่แล้ว",
"cat": "LobbyB"
},
{
"n": 56,
"name": "ตอนกลับ LobbyB โชว์รูปแรกก่อนแล้วค่อยถูก",
"detail": "เหมือน #1 การ์ด — ต้องใส่ loading state ตอน return LobbyB ด้วย (มี room-loading-overlay อยู่แต่ซ่อนเร็วไป)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0308 — การ์ดมี spinner (#1) + room-loading overlay ใส่ % + รอ char preload เสร็จก่อนซ่อน + safety 12s",
"cat": "อื่นๆ"
},
{
"n": 57,
"name": "เล่นมินิเกมเกิน 3 ครั้งต่อผู้ต้องสงสัย (+การ์ดพิเศษโผล่รอบที่4)",
"detail": "server cap suspect-pick-start (max หลักฐาน>=3 reject) + award cap 3 + client canInvestigateSuspect<3 (เดิม stub return true). UI messaging มีอยู่แล้ว",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "LobbyB"
},
{
"n": 58,
"name": "ปุ่ม \"เล่นเกมต่อ\" ไม่รู้ว่าค้างหรือรอคนอื่น",
"detail": "text \"รอผู้เล่นอื่น (X/Y)\" ถูก set อยู่แล้วแต่ CSS font-size:0 ซ่อน → เพิ่ม .is-waiting CSS โชว์ข้อความ+spinner แทนรูปปุ่ม",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 59,
"name": "loading ไม่คลุม scene transition (เห็นฉากเก่าก่อนเปลี่ยน)",
"detail": "LobbyA->B (in-page swap) โชว์ room-loading ระหว่าง fetch+swap · LobbyB->เกม โชว์ overlay ก่อน location.href · play.html มี overlay รับช่วง",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "LobbyA"
},
{
"n": 60,
"name": "ทำหน้า testcase บนเซิร์ฟเวอร์",
"detail": "srv1361159.hstgr.cloud/testcase — bug list + filter/search/นับสถานะ generate จาก xlsx",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 61,
"name": "MG3 สี block ไม่ตรง user (เกม+admin)",
"detail": "PNG Block-Code-1..8 deploy ครบแล้ว · ต้อง wire สี block = lobby theme ของผู้วาง (seat=theme refactor, ต้องเทส 2 จอ) + admin 8-slot config",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006250200 — block สีตาม theme ผู้เล่นที่ seat (Block-Code-1..8) ตอนวาด ไม่แตะ turn/sync",
"cat": "Minigame 3"
},
{
"n": 62,
"name": "MG1/MG4 user/bot ติดกัน (เดินไม่ผ่าน)",
"detail": "เพิ่ม anti-stuck: ดันชนเพื่อน → เขย่า (shake) + ปลดล็อกเดินผ่านกันชั่วคราว คงกำแพง/area/quiz-lock; ทั้งผู้เล่นและบอท",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006250100 (รอเทส)",
"cat": "Minigame 4"
},
{
"n": 63,
"name": "LobbyA มือถือ กดเดินไม่ได้",
"detail": "lobby canvas มีแค่ dblclick (ไม่มี touch) → มือถือแตะเดินไม่ได้. แก้: เพิ่ม tap-to-walk (เฉพาะ touch, PC ยัง dblclick) + DPI-safe coord + touch-action:none + zoom-out default บนจอ<600px (PC ย่อส่วน)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 room-lobby.js?v=0.0310 (รอเทสมือถือ) · PC ไม่กระทบ",
"cat": "LobbyA"
},
{
"n": 64,
"name": "มือถือ: ปุ่มแชท AI ใหญ่ไป + ปุ่ม F (host) ไม่มี",
"detail": "AI chat collapsed btn 213x201px → ใหญ่บนมือถือ. + การกด F (interact: host เลือกระดับ/คดี, ห้องแต่งตัว) ไม่มีบนมือถือ",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (css 0.0176/js 0.0311): media<600px ย่อปุ่ม AI (92px) + ปุ่มลอย \"โต้ตอบ(F)\" บน touch-primary device (dispatch synthetic KeyF). PC ไม่เห็น",
"cat": "LobbyB"
},
{
"n": 65,
"name": "อีเวนต์พิเศษ (ทนาย/เทพ) = 1 ใน 3 (ตามดีไซน์)",
"detail": "ดีไซน์: 3 เกม/LobbyB เล่นได้เกมละ 3 ครั้ง · อีเวนต์พิเศษ 1/3 ต่อครั้ง (1 ใบ) · Test Mode บังคับ. revert การแก้ \">1 ต่อรอบ\" กลับเป็น 1 ใบ/ครั้งที่ติด",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ revert 2026-06-25 — กลับเป็น 1/3 (1 ใบ/ครั้งที่ติด) ตามดีไซน์ · ยืนยันโครงสร้าง 3เกม×3ครั้ง + disruptor toggle (>4คน/Test Mode) ถูกต้อง",
"cat": "อื่นๆ"
},
{
"n": 66,
"name": "host หลุด → ย้าย host ให้คนอยู่นานสุด",
"detail": "pickNextHostPeer เลือก spawnJoinOrder ต่ำสุด (เสถียรข้าม reconnect) แทน Map-first + host จริง reclaim ได้",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (server) — Part 1 host migration",
"cat": "ระบบ disconnect"
},
{
"n": 67,
"name": "คนหลุดกลางเกม → 10 วิ reconnect + บอทเล่นแทน",
"detail": "peer-disconnect-grace แบนเนอร์นับ 10 วิ · กลับทัน=cancel เล่นต่อ · ครบเวลา=peer-bot-takeover host เติมบอท 1 ตัว (rebalance, cap≤6). บอทไม่สืบทอดตำแหน่ง/คะแนน",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (server+play.js v0.006250400) — Part 2 แบบ A · seamless(host คุม entity เดิม)=ค้าง(เสี่ยง)",
"cat": "ระบบ disconnect"
},
{
"n": 68,
"name": "ห้องแต่งตัว (customize) มือถือแนวนอนใช้ไม่ได้",
"detail": "@media max-width:960px stack แนวตั้ง → panel สูงตาม aspect ล้นจอแนวนอน. แก้: เพิ่ม media (landscape + max-height:540px) ใช้เลย์เอาต์แนวนอน + ขนาดอิงความสูงจอ",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 customize-popup.css?v=43 (Main-Lobby + room-lobby) — รอเทสมือถือแนวนอน",
"cat": "Minigame 3"
},
{
"n": 69,
"name": "UI ห้องเกม (LobbyA) มือถือแนวนอน — ย่อให้พอดี",
"detail": "room-lobby ไม่มี landscape media query → chat panel(576x525), ปุ่ม voice/แว่น/ถ้วย(80px), AI chat ล้น/ใหญ่บนแนวนอน. เพิ่ม @media(landscape+max-height:540px) ย่อตามความสูงจอ (อิง Main-Lobby pattern)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 room-lobby.css?v=0.0177 — chat/BR buttons/AI/ready/exit · overlay หลักฐาน-อันดับ (inline/JS) ยังต้องทำแยก",
"cat": "LobbyB"
},
{
"n": 70,
"name": "การ์ดรางวัลหลักฐาน มือถือใหญ่เกิน กดปุ่มไม่ได้",
"detail": "หน้าโชว์การ์ดรางวัล \"คุณได้รับหลักฐาน!\" (หลังจบมินิเกมสืบสวน) บนมือถือการ์ดใหญ่มากจนเนื้อหา+ปุ่ม \"เก็บเข้าแฟ้ม\" หลุดล่างจอ กดปิดไม่ได้",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod play.js?v=0.006250402): ทำ overlay เป็น scroll container + inner margin:auto (กัน flexbox clip ด้านบนเวลาเนื้อหาสูงเกินจอ) + คุมขนาดการ์ด/หัวข้อ/ปุ่มด้วย clamp ตาม viewport (การ์ด max-width min(72vw,360px) + max-height 52vh, ปุ่ม font/padding clamp). มือถือแนวตั้งเลื่อนถึงปุ่มได้เสมอ. ไฟล์: Game/public/js/play.js (showInSceneEvidenceReveal).",
"cat": "ระบบ disconnect"
},
{
"n": 71,
"name": "หน้าผลชนะ (โพเดียม) ตัวละครลอย ไม่ยืนบนพื้น",
"detail": "หน้า \"ยินดีด้วย ทีมนักสืบ ชนะ!\" ตัวละครผู้ชนะลอยอยู่กลางอากาศ ไม่ยืนบนพื้นเวที",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod room-lobby.js?v=0.0312): พื้นหลัง win-bg เดิม center/cover ทำให้พื้นเวทีถูก crop เลื่อน + podium ตั้ง bottom:9% ตัวละครเลยลอย. แก้เป็น (1) ปักพื้นหลังไว้ขอบล่าง background-position:center bottom -> พื้นเวทีติดขอบล่างจอเสมอทุกสัดส่วน (2) ลด podium bottom 9% -> 2% ให้ตัวละครยืนบนพื้น. ไฟล์: Game/public/js/room-lobby.js (injectFinalResultStyle, #trial-final-podium).",
"cat": "อื่นๆ"
},
{
"n": 72,
"name": "ป๊อปอัปขอสิทธิ์ไมค์ เด้งทุกครั้งที่เข้า Lobby",
"detail": "หน้าขอสิทธิ์ไมโครโฟน (mic-permission-overlay) เด้งขึ้นทุกครั้งที่เข้า LobbyA/LobbyB น่ารำคาญ ทั้งที่เบราว์เซอร์จำสิทธิ์ให้แล้ว",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod room-lobby.js?v=0.0313): เดิม overlay เป็น display:flex default จึงเด้งทุกครั้งที่โหลดหน้า. เพิ่มตรวจสิทธิ์ผ่าน navigator.permissions.query(microphone) + จำสถานะใน localStorage (justiceMicPromptHandled). แสดงเฉพาะครั้งแรกที่ยังไม่เคยตัดสินใจ (permission=prompt และยังไม่เคยกดอนุญาต/ปิด); ถ้า granted แล้ว/เคยจัดการแล้ว → ซ่อนทันที (มี inline script กันแฟลชก่อน paint). ปุ่ม 🎤 ขอสิทธิ์ไมค์ ในห้องยังใช้ขอใหม่ได้. ไฟล์: room-lobby.html (inline gate) + room-lobby.js (gateMicPermissionOverlay, hideMicPermissionOverlay).",
"cat": "LobbyA"
}
]
@@ -0,0 +1,732 @@
[
{
"n": 1,
"name": "แต่งตัวไม่ได้",
"detail": "แต่งตัวยังไม่ได้ มีพี่คิวทำได้คนเดียว",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 2,
"name": "ตัวละครไม่แสดง",
"detail": "หน้าเลือกเกม Quiz battle ภาพตัวละครไม่แสดง",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 1"
},
{
"n": 3,
"name": "ระบบ join เกมมีปัญหา",
"detail": "เมื่อเข้ามินิเกม : บางคนเล่นเกมกะบอท บางคนเล่นกะเพื่อน แต่ละคนเข้ามินิเกมแล้วเจอกันบ้าง ไม่เจอกันบ้าง",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 4,
"name": "ระบบ Host มีปัญหา - 1",
"detail": "เป็น host ในเกม ตอนที่เลือกผู้ต้องสงสัย รอบที่ 2 ระบบบอกว่าให้รอ host ไม่สามารถเลือกการ์ดและกดเริ่มสืบสวนได้",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 5,
"name": "ระบบ Host มีปัญหา - 2",
"detail": "เมื่อกดออกจากเกม Host เปลี่ยนจากพี่ต้อมเป็นพี่คิวอัติโนมัติ",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 6,
"name": "ระบบ Host มีปัญหา - 3",
"detail": "เมื่อเข้ามินิเกม ทุกคนเห็นตัวเองเป็น host หมดเลย แต่กดเริ่มเกมไม่ได้",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 7,
"name": "ระบบสีตัวละคร ยังใช้ไม่ได้",
"detail": "สีตัวละครเปลี่ยนไปเรื่อย ไม่รุ้ว่าเราสีอะไรแน่ งงไปหมด",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 8,
"name": "หน้าเข้าร่วมเกมส์",
"detail": "ห้องไหนที่เริ่มเกมส์ไปแล้ว เมื่อกดเข้าร่วมแสดง popup alert error ปรับดีไซต์ให้สวยงามกว่านี้",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 9,
"name": "เกมวิ่งขึ้นบันได ผิด flow",
"detail": "ปรับ flow ให้ถูกต้อง คือ ใครตกจอตาย แต่ถ้าขึ้นไปอยู่บนสุดแล้วจะกระโดดไม่ไปต่อเฉยๆ แต่ไม่ตาย",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 10,
"name": "เกมวิ่งขึ้นบันได",
"detail": "เวลาหมดแล้ว ให้เล่นรอบ 2 auto \/ รอบ 3 มี popup ให้กดเล่นต่อ",
"prio": "",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 2"
},
{
"n": 11,
"name": "เกมยิงลูกโป่ง",
"detail": "ฉากเกมยิงลูกโป่ง (MG7 Mega Virus) เล็กไป — ต้องการให้ภาพพื้นหลัง (BG) เต็มจอ แต่การเดิน (movement) และพื้นที่เล่น (area) ต่างๆ คงเดิม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod play.js?v=0.006250401): BG ของ MG7 เป็นภาพ 16:9 (1920x1080) แต่โค้ดเดิม map ภาพเข้า world rect 4:3 (640x480) ทำให้ภาพถูกบีบ + เหลือขอบดำซ้าย-ขวา = ฉากดูเล็ก. เปลี่ยนการวาด BG ของ balloon_boss เป็นแบบ cover เต็มจอ (รักษาสัดส่วน 16:9, crop ส่วนเกิน, จัดกึ่งกลาง) วาดแยกจาก world layer → กล้อง\/movement\/area ของเกมคงเดิมทุกอย่าง เปลี่ยนเฉพาะพื้นหลังให้เต็มจอ. ไฟล์: Game\/public\/js\/play.js (draw loop, สาขา isBalloonBoss cover-fill).",
"cat": "Minigame 7"
},
{
"n": 12,
"name": "เกมต่อตึก ผิด flow",
"detail": "ปรับ flow ให้ถูกต้อง คือ เล่นทีละคน เรียงตามคิวไปเรื่อย โดยที่ blog ตามสีผู้เล่นด้วย",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 13,
"name": "เข้าร่วมห้องไม่ได้",
"detail": "สร้างห้องส่วนตัวเเล้ว เเต่เพื่อนจอยเข้าไปไม่ได้ ทั้งที่กรอกข้อมูลครบ",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 14,
"name": "Room ID",
"detail": "เวลาจอยห้องให้กรอกเเค่ ID กับรหัสที่สร้าง",
"prio": "ต่ำ",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 15,
"name": "จอยห้องคนอื่น",
"detail": "ในกรณีที่จอยห้องคนอื่น ไปหน้าเข้าร่วม เเละกดปุ่มเข้าร่วมไปเเล้ว จากนั้นกดปุ่ม exit จะพาไปหน้าสร้างห้อง สิ่งที่ถูกต้อง มันควรมาหน้า ห้องเข้าร่วมเกม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 16,
"name": "grid กับ ฉาก ไม่ตรงกัน",
"detail": "เกม Quiz battle เกมที่ 1 ฉากกับกริดไม่ตรงกัน ทำให้เดินทะลุ หรือเดินติดในจุดที่ไม่ควรติด",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 1"
},
{
"n": 17,
"name": "ตัวละครวิ่งเร็วเกินไป",
"detail": "เกม Quiz battle เกมที่ 1 ตัวละครผู้เล่นเร็วเกินไป",
"prio": "กลาง",
"status": "เปิด",
"updated": "2026-06-25",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 18,
"name": "Oauth Social log-in error",
"detail": "ไม่สามารถ log-inด้วย facebook \/ Google ได้ path: \/oauth-public.php",
"prio": "ต่ำ",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 19,
"name": "หน้า login",
"detail": "นโยบายความเป็นส่วนตัวกดไม่ได้",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 20,
"name": "แสดงเลข version ตรงมุมจอ",
"detail": "แสดงเลข version ตรงมุมจอขวาบนหรือขวาล่างก็ได้",
"prio": "ต่ำ",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — badge มุมขวาล่าง อ่าน ?v= อัตโนมัติ",
"cat": "อื่นๆ"
},
{
"n": 21,
"name": "ลดขนาดตัวละคร",
"detail": "ลดขนาดตัวละครในห้อง lobby ลงอีก 2เท่า",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 22,
"name": "เปลี่ยน ui popup ตอนพูดบนหัว",
"detail": "แก้ png seq บนหัวใหม่ เป็นแบบอื่น",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 23,
"name": "ชื่่อตัวละครทับลำโพง\/ทับ bubble",
"detail": "ใน lobby ขยับชื่อลงมาอย่าให้ทับ",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 24,
"name": "UI แต่งตัว : การแจ้งเตือนสีตัวละครถูกใช้แล้ว",
"detail": "ตอนเลือกสีให้มีคำแจ้งเตือน used",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 25,
"name": "เพิ่ม ข้อความแจ้งเตือนอัพเดทกลางจอ",
"detail": "ถ้ามีการอัพเดทซีน อัพเดทสีตัวละครให้มีป็อปอัพเด้งมากลางหน้าจอ ว่ามีการอัพเดทและให้รอโหลด",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 26,
"name": "เอาเงาตัวละครออก 1 อัน",
"detail": "ตอนนี้ตัวละครมีเงา 2 ชั้น เอาออกไป 1 ชั้น",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — ปิด default-shadow overlay เหลือเงาในสไปรต์",
"cat": "อื่นๆ"
},
{
"n": 27,
"name": "optimize ตัวละครให้เล็กลงอีก มันโหลดช้า",
"detail": "",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 28,
"name": "ปุ่ม start ใน lobby",
"detail": "ทำให้ดูโต้ตอบหน่อย เช่นพอเดินเข้าไปใกล้ให้มัน glow ขึ้นมา",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 29,
"name": "เกมต่อตึก ผิด flow",
"detail": "สี blog กับคนเล่นยังไม่สัมพันกัน ต้องเรียงตามสี และตามคิว",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 30,
"name": "เกมต่อตึก บั๊กคนที่ 5",
"detail": "คนที่ 5 ไม่สามารถเล่นได้",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 31,
"name": "เอา mock การ์ดผู้ต้องหาเก่าออกไป",
"detail": "มันแว่บๆมา",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส) — ซ่อนรูปจนรูปคดีจริงโหลด + spinner",
"cat": "LobbyB"
},
{
"n": 32,
"name": "เอา popup ขออนุญาตใช้ไมค์ออกไปเลย น่ารำคาญ",
"detail": "",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 33,
"name": "หน้า howto เปลี่ยนสีปุ่ม ready ด้วยตอน กดready แล้ว",
"detail": "เปลี่ยนเป้นสีเขียวพร้อมเครื่องหมายถูกตามดีไซน์",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 34,
"name": "เกม ใช่ หรือไม่ : ตอนจบบั๊ค",
"detail": "ตอนสรุปคะแนน ยังนับเวลาอยุ่ และขึ้น gameover ทับหน้าสรุปคะแนน",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG1 ใช่\/ไม่: capture mg1AnyScore ก่อนล้าง (gameover) + guard quiz-phase หลังจบ (timer)",
"cat": "Minigame 1"
},
{
"n": 35,
"name": "เกมวิ่ง : กดปุ่มไม่กระโดด",
"detail": "ปุ่ม jump กดแล้วไม่กระโดด",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 36,
"name": "เกมวิ่ง : ตอนจบบั๊ค วิ่งเลยฉาก",
"detail": "ตอนจบบั๊ค วิ่งเลยฉาก (ในบางรอบ)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG2 วิ่งเลยฉาก: walk-to-finish ค่อยๆเดินเข้าเส้น + clamp ไม่เลยเส้น",
"cat": "Minigame 2"
},
{
"n": 37,
"name": "popup รับหลักฐาน ทับไมค์",
"detail": "กดไมค์ไม่ได้ ตอนรับหลักฐาน ไปดูเรื่องเลเยอร์อีกที",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 38,
"name": "หน้าส่งหลักฐาน ปุ่มส่งหลักฐานหลุดจอ (เครื่องของเนส)",
"detail": "เป้นเครื่องเนสคนเดียว",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "ระบบ disconnect"
},
{
"n": 39,
"name": "ห้องส่งหลักฐาน : เมื่อมีคนหลุด 1 คน จะไปต่อไม่ได้เลย",
"detail": "",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — testimony: disconnect ลบ submit\/ready + client ใช้ totalPlayers authoritative",
"cat": "ระบบ disconnect"
},
{
"n": 40,
"name": "จำนวนคน ให้เล่นได้ตั้งแต่ 4-6 คน",
"detail": "กรณีเลือกผู้เล่นตั้งแต่ 4 คนขึ้นไปไม่ต้องมี bot",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 41,
"name": "เกม หยิบแผ่นป้าย",
"detail": "ต้องเล่นให้ครบ 10 ข้อก่อน ค่อยจบเกม (ตอนนี้เล่นถูก 1 ข้อ จบเกมเลย)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG4 หยิบป้าย: ใช้ map จริง (currentPlayMapId) เปิด mission flow + default 10 ข้อ",
"cat": "Minigame 4"
},
{
"n": 42,
"name": "เกม กระโดด",
"detail": "ท่ากระโดดแปลก ยืนซอยเท้าทำไม",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 43,
"name": "เกม กระโดด",
"detail": "ขั้นบันไดมันถี่ไป บางทีกระโดดแล้วเบิ้ลขั้นเลย ปรับให้ระยะห่างแต่ละขั้นมันเท่ากัน",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 44,
"name": "เกมกระโดด",
"detail": "อีเวนท์พิเศษ (ทนาย\/เทพความรู้) หายไป",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG5 อีเวนต์พิเศษ: เพิ่ม branch jump_survive วาง icon บน platform",
"cat": "Minigame 5"
},
{
"n": 45,
"name": "เกมไม่รองรับ 4K",
"detail": "หน้ารับหลักฐาน (หลังจากเล่นเกมจบ)",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 46,
"name": "เกมยิิงอุกาบาด",
"detail": "สีตัวละครยังไม่ตรงกับที่เลือกมา",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG6 สี: ยาน Rocket-1..8 ตาม lobby theme ของผู้เล่น",
"cat": "Minigame 6"
},
{
"n": 47,
"name": "เกมยิิงอุกาบาด",
"detail": "อุกาบาดให้พุ่งเร็วกว่านี้ และลูกเล็กกว่านี้เกมง่ายไป",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG6: อุกาบาตเร็วขึ้น (+~50%) + เล็กลง (48->34\/11->8)",
"cat": "Minigame 6"
},
{
"n": 48,
"name": "เพิ่ม popup เมื่อเกมค้าง เล่นต้่อไม่ได้ ให้มีปุ่มกดออกไปหน้าแรกสุด",
"detail": "สาเหตุเกิดจากมีคนหลุดระหว่างรอ host กด",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "ระบบ disconnect"
},
{
"n": 49,
"name": "กรณีผู้เล่นหลุดออกจากเกม",
"detail": "ถ้ามีคนหลุด ให้กลายเป้นบอทแทน",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว 2026-06-25: คนออกระหว่าง LobbyB\/เปลี่ยนหน้า กลายเป็นบอทสีตามคนออก (ดูบั๊ก #73)",
"cat": "ระบบ disconnect"
},
{
"n": 50,
"name": "ผู้เล่น 6 คน: บางเกมเล่นได้แค่ 5 (คนที่ 6 หายจากสรุป)",
"detail": "สรุปคะแนนฝั่ง client slice(0,5) ตัดคนที่ 6 — แก้เป็น slice(0,6) ทุกมินิเกม (MG1\/3\/4\/5\/6\/7)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส)",
"cat": "Minigame 1"
},
{
"n": 51,
"name": "ผู้เล่น 6 คน: คะแนนตอนจบไม่ตรงกันแต่ละจอ",
"detail": "MG1 merge คะแนน server \/ MG6,7 merge คะแนน host -> ครบ 6 ตรงกัน (MG3 ยัง local)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส)",
"cat": "Minigame 6"
},
{
"n": 52,
"name": "หน้าไต่สวน\/สรุปหลักฐาน: กรอบปลิ้นตอน 6 คน",
"detail": "member row grid repeat(5)->repeat(6) ใน testimony-overlay.css (คนที่6 ตกแถว2)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส)",
"cat": "LobbyB"
},
{
"n": 53,
"name": "เพิ่มหน้าโหลด (loading) ตอนเข้าเกม",
"detail": "overlay spinner กันเห็นจอเปล่า\/asset แว่บ ซ่อนเมื่อ draw เฟรมแรก + safety 8s",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 54,
"name": "host โหลดนานมากตอนเข้าเกม",
"detail": "optimize asset\/char (เกี่ยว row 28) — loading overlay ช่วยซ่อน แต่ยังโหลดช้าจริง",
"prio": "สูง",
"status": "เปิด",
"updated": "2026-06-25",
"note": "loading overlay บังอาการแล้ว · root = canvas tint ภาพตัวละครใหญ่ (source ไม่อยู่ใน repo) → ต้อง optimize ภาพแยก",
"cat": "อื่นๆ"
},
{
"n": 55,
"name": "หลังออกจาก LobbyA ไม่ให้ใครเข้า join",
"detail": "server มี joinLocked (set ตอน LobbyA->case ~8656, check ~7585 whitelist nickname) — ต้อง repro ว่าใครเข้าได้ยังไง (room list ยังโชว์? ชื่อซ้ำ whitelist?)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 — กรองห้อง joinLocked ออกจาก \/api\/spaces list (ห้องเริ่มคดีแล้วหาย) + server reject อยู่แล้ว",
"cat": "LobbyB"
},
{
"n": 56,
"name": "ตอนกลับ LobbyB โชว์รูปแรกก่อนแล้วค่อยถูก",
"detail": "เหมือน #1 การ์ด — ต้องใส่ loading state ตอน return LobbyB ด้วย (มี room-loading-overlay อยู่แต่ซ่อนเร็วไป)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0308 — การ์ดมี spinner (#1) + room-loading overlay ใส่ % + รอ char preload เสร็จก่อนซ่อน + safety 12s",
"cat": "อื่นๆ"
},
{
"n": 57,
"name": "เล่นมินิเกมเกิน 3 ครั้งต่อผู้ต้องสงสัย (+การ์ดพิเศษโผล่รอบที่4)",
"detail": "server cap suspect-pick-start (max หลักฐาน>=3 reject) + award cap 3 + client canInvestigateSuspect<3 (เดิม stub return true). UI messaging มีอยู่แล้ว",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309\/server (รอเทส)",
"cat": "LobbyB"
},
{
"n": 58,
"name": "ปุ่ม \"เล่นเกมต่อ\" ไม่รู้ว่าค้างหรือรอคนอื่น",
"detail": "text \"รอผู้เล่นอื่น (X\/Y)\" ถูก set อยู่แล้วแต่ CSS font-size:0 ซ่อน → เพิ่ม .is-waiting CSS โชว์ข้อความ+spinner แทนรูปปุ่ม",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309\/server (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 59,
"name": "loading ไม่คลุม scene transition (เห็นฉากเก่าก่อนเปลี่ยน)",
"detail": "LobbyA->B (in-page swap) โชว์ room-loading ระหว่าง fetch+swap · LobbyB->เกม โชว์ overlay ก่อน location.href · play.html มี overlay รับช่วง",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309\/server (รอเทส)",
"cat": "LobbyA"
},
{
"n": 60,
"name": "ทำหน้า testcase บนเซิร์ฟเวอร์",
"detail": "srv1361159.hstgr.cloud\/testcase — bug list + filter\/search\/นับสถานะ generate จาก xlsx",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309\/server (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 61,
"name": "MG3 สี block ไม่ตรง user (เกม+admin)",
"detail": "PNG Block-Code-1..8 deploy ครบแล้ว · ต้อง wire สี block = lobby theme ของผู้วาง (seat=theme refactor, ต้องเทส 2 จอ) + admin 8-slot config",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006250200 — block สีตาม theme ผู้เล่นที่ seat (Block-Code-1..8) ตอนวาด ไม่แตะ turn\/sync",
"cat": "Minigame 3"
},
{
"n": 62,
"name": "MG1\/MG4 user\/bot ติดกัน (เดินไม่ผ่าน)",
"detail": "เพิ่ม anti-stuck: ดันชนเพื่อน → เขย่า (shake) + ปลดล็อกเดินผ่านกันชั่วคราว คงกำแพง\/area\/quiz-lock; ทั้งผู้เล่นและบอท",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006250100 (รอเทส)",
"cat": "Minigame 4"
},
{
"n": 63,
"name": "LobbyA มือถือ กดเดินไม่ได้",
"detail": "lobby canvas มีแค่ dblclick (ไม่มี touch) → มือถือแตะเดินไม่ได้. แก้: เพิ่ม tap-to-walk (เฉพาะ touch, PC ยัง dblclick) + DPI-safe coord + touch-action:none + zoom-out default บนจอ<600px (PC ย่อส่วน)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 room-lobby.js?v=0.0310 (รอเทสมือถือ) · PC ไม่กระทบ",
"cat": "LobbyA"
},
{
"n": 64,
"name": "มือถือ: ปุ่มแชท AI ใหญ่ไป + ปุ่ม F (host) ไม่มี",
"detail": "AI chat collapsed btn 213x201px → ใหญ่บนมือถือ. + การกด F (interact: host เลือกระดับ\/คดี, ห้องแต่งตัว) ไม่มีบนมือถือ",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (css 0.0176\/js 0.0311): media<600px ย่อปุ่ม AI (92px) + ปุ่มลอย \"โต้ตอบ(F)\" บน touch-primary device (dispatch synthetic KeyF). PC ไม่เห็น",
"cat": "LobbyB"
},
{
"n": 65,
"name": "อีเวนต์พิเศษ (ทนาย\/เทพ) = 1 ใน 3 (ตามดีไซน์)",
"detail": "ดีไซน์: 3 เกม\/LobbyB เล่นได้เกมละ 3 ครั้ง · อีเวนต์พิเศษ 1\/3 ต่อครั้ง (1 ใบ) · Test Mode บังคับ. revert การแก้ \">1 ต่อรอบ\" กลับเป็น 1 ใบ\/ครั้งที่ติด",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ revert 2026-06-25 — กลับเป็น 1\/3 (1 ใบ\/ครั้งที่ติด) ตามดีไซน์ · ยืนยันโครงสร้าง 3เกม×3ครั้ง + disruptor toggle (>4คน\/Test Mode) ถูกต้อง",
"cat": "อื่นๆ"
},
{
"n": 66,
"name": "host หลุด → ย้าย host ให้คนอยู่นานสุด",
"detail": "pickNextHostPeer เลือก spawnJoinOrder ต่ำสุด (เสถียรข้าม reconnect) แทน Map-first + host จริง reclaim ได้",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (server) — Part 1 host migration",
"cat": "ระบบ disconnect"
},
{
"n": 67,
"name": "คนหลุดกลางเกม → 10 วิ reconnect + บอทเล่นแทน",
"detail": "peer-disconnect-grace แบนเนอร์นับ 10 วิ · กลับทัน=cancel เล่นต่อ · ครบเวลา=peer-bot-takeover host เติมบอท 1 ตัว (rebalance, cap≤6). บอทไม่สืบทอดตำแหน่ง\/คะแนน",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (server+play.js v0.006250400) — Part 2 แบบ A · seamless(host คุม entity เดิม)=ค้าง(เสี่ยง)",
"cat": "ระบบ disconnect"
},
{
"n": 68,
"name": "ห้องแต่งตัว (customize) มือถือแนวนอนใช้ไม่ได้",
"detail": "@media max-width:960px stack แนวตั้ง → panel สูงตาม aspect ล้นจอแนวนอน. แก้: เพิ่ม media (landscape + max-height:540px) ใช้เลย์เอาต์แนวนอน + ขนาดอิงความสูงจอ",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 customize-popup.css?v=43 (Main-Lobby + room-lobby) — รอเทสมือถือแนวนอน",
"cat": "Minigame 3"
},
{
"n": 69,
"name": "UI ห้องเกม (LobbyA) มือถือแนวนอน — ย่อให้พอดี",
"detail": "room-lobby ไม่มี landscape media query → chat panel(576x525), ปุ่ม voice\/แว่น\/ถ้วย(80px), AI chat ล้น\/ใหญ่บนแนวนอน. เพิ่ม @media(landscape+max-height:540px) ย่อตามความสูงจอ (อิง Main-Lobby pattern)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 room-lobby.css?v=0.0177 — chat\/BR buttons\/AI\/ready\/exit · overlay หลักฐาน-อันดับ (inline\/JS) ยังต้องทำแยก",
"cat": "LobbyB"
},
{
"n": 70,
"name": "การ์ดรางวัลหลักฐาน มือถือใหญ่เกิน กดปุ่มไม่ได้",
"detail": "หน้าโชว์การ์ดรางวัล \"คุณได้รับหลักฐาน!\" (หลังจบมินิเกมสืบสวน) บนมือถือการ์ดใหญ่มากจนเนื้อหา+ปุ่ม \"เก็บเข้าแฟ้ม\" หลุดล่างจอ กดปิดไม่ได้",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod play.js?v=0.006250402): ทำ overlay เป็น scroll container + inner margin:auto (กัน flexbox clip ด้านบนเวลาเนื้อหาสูงเกินจอ) + คุมขนาดการ์ด\/หัวข้อ\/ปุ่มด้วย clamp ตาม viewport (การ์ด max-width min(72vw,360px) + max-height 52vh, ปุ่ม font\/padding clamp). มือถือแนวตั้งเลื่อนถึงปุ่มได้เสมอ. ไฟล์: Game\/public\/js\/play.js (showInSceneEvidenceReveal).",
"cat": "ระบบ disconnect"
},
{
"n": 71,
"name": "หน้าผลชนะ (โพเดียม) ตัวละครลอย ไม่ยืนบนพื้น",
"detail": "หน้า \"ยินดีด้วย ทีมนักสืบ ชนะ!\" ตัวละครผู้ชนะลอยอยู่กลางอากาศ ไม่ยืนบนพื้นเวที",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod room-lobby.js?v=0.0312): พื้นหลัง win-bg เดิม center\/cover ทำให้พื้นเวทีถูก crop เลื่อน + podium ตั้ง bottom:9% ตัวละครเลยลอย. แก้เป็น (1) ปักพื้นหลังไว้ขอบล่าง background-position:center bottom -> พื้นเวทีติดขอบล่างจอเสมอทุกสัดส่วน (2) ลด podium bottom 9% -> 2% ให้ตัวละครยืนบนพื้น. ไฟล์: Game\/public\/js\/room-lobby.js (injectFinalResultStyle, #trial-final-podium).",
"cat": "อื่นๆ"
},
{
"n": 72,
"name": "ป๊อปอัปขอสิทธิ์ไมค์ เด้งทุกครั้งที่เข้า Lobby",
"detail": "หน้าขอสิทธิ์ไมโครโฟน (mic-permission-overlay) เด้งขึ้นทุกครั้งที่เข้า LobbyA\/LobbyB น่ารำคาญ ทั้งที่เบราว์เซอร์จำสิทธิ์ให้แล้ว",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod room-lobby.js?v=0.0313): เดิม overlay เป็น display:flex default จึงเด้งทุกครั้งที่โหลดหน้า. เพิ่มตรวจสิทธิ์ผ่าน navigator.permissions.query(microphone) + จำสถานะใน localStorage (justiceMicPromptHandled). แสดงเฉพาะครั้งแรกที่ยังไม่เคยตัดสินใจ (permission=prompt และยังไม่เคยกดอนุญาต\/ปิด); ถ้า granted แล้ว\/เคยจัดการแล้ว → ซ่อนทันที (มี inline script กันแฟลชก่อน paint). ปุ่ม 🎤 ขอสิทธิ์ไมค์ ในห้องยังใช้ขอใหม่ได้. ไฟล์: room-lobby.html (inline gate) + room-lobby.js (gateMicPermissionOverlay, hideMicPermissionOverlay).",
"cat": "LobbyA"
},
{
"n": 73,
"name": "คนออก LobbyB \/ ช่วงเปลี่ยนหน้า → ไม่เติมบอท + ค้างรอผู้เล่น เริ่มเกมต่อไม่ได้",
"detail": "คนหลุดระหว่างนั่งอยู่ LobbyB หรือช่วงเปลี่ยนหน้า minigame→LobbyB เดิมไม่ถูกเติมบอทแทน + ยังถูกนับใน roster คดี → host กดเริ่มสืบสวนรอบต่อไปไม่ได้ ค้าง รอผู้เล่น (x\/y) ตลอดไป",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "ขยาย grace+bot-takeover gate ให้ครอบ LobbyB\/คดีล็อก ไม่ใช่แค่ minigame active (server.js ~8921: inCaseFlowForBot = detectiveMinigameActive || serverMapIsPostCaseLobbyB || (joinLocked && caseParticipantNicknames.size>0)). คนออก→หลัง grace 10วิ ลบจาก roster + เติมบอทสีตามคนออก + presence ใหม่ → allHere=true เล่นต่อได้. + ส่ง botSlotCount ใน lobby-tint-sync (server.js ~121) + client อัปเดต lobbyBotSlotCount ก่อน render (room-lobby.js ~768) ให้บอทโผล่ทันทีไม่ต้อง re-join. v0.0323",
"cat": "ระบบ disconnect"
}
]
@@ -0,0 +1,742 @@
[
{
"n": 1,
"name": "แต่งตัวไม่ได้",
"detail": "แต่งตัวยังไม่ได้ มีพี่คิวทำได้คนเดียว",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 2,
"name": "ตัวละครไม่แสดง",
"detail": "หน้าเลือกเกม Quiz battle ภาพตัวละครไม่แสดง",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 1"
},
{
"n": 3,
"name": "ระบบ join เกมมีปัญหา",
"detail": "เมื่อเข้ามินิเกม : บางคนเล่นเกมกะบอท บางคนเล่นกะเพื่อน แต่ละคนเข้ามินิเกมแล้วเจอกันบ้าง ไม่เจอกันบ้าง",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 4,
"name": "ระบบ Host มีปัญหา - 1",
"detail": "เป็น host ในเกม ตอนที่เลือกผู้ต้องสงสัย รอบที่ 2 ระบบบอกว่าให้รอ host ไม่สามารถเลือกการ์ดและกดเริ่มสืบสวนได้",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 5,
"name": "ระบบ Host มีปัญหา - 2",
"detail": "เมื่อกดออกจากเกม Host เปลี่ยนจากพี่ต้อมเป็นพี่คิวอัติโนมัติ",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 6,
"name": "ระบบ Host มีปัญหา - 3",
"detail": "เมื่อเข้ามินิเกม ทุกคนเห็นตัวเองเป็น host หมดเลย แต่กดเริ่มเกมไม่ได้",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 7,
"name": "ระบบสีตัวละคร ยังใช้ไม่ได้",
"detail": "สีตัวละครเปลี่ยนไปเรื่อย ไม่รุ้ว่าเราสีอะไรแน่ งงไปหมด",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 8,
"name": "หน้าเข้าร่วมเกมส์",
"detail": "ห้องไหนที่เริ่มเกมส์ไปแล้ว เมื่อกดเข้าร่วมแสดง popup alert error ปรับดีไซต์ให้สวยงามกว่านี้",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 9,
"name": "เกมวิ่งขึ้นบันได ผิด flow",
"detail": "ปรับ flow ให้ถูกต้อง คือ ใครตกจอตาย แต่ถ้าขึ้นไปอยู่บนสุดแล้วจะกระโดดไม่ไปต่อเฉยๆ แต่ไม่ตาย",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 10,
"name": "เกมวิ่งขึ้นบันได",
"detail": "เวลาหมดแล้ว ให้เล่นรอบ 2 auto / รอบ 3 มี popup ให้กดเล่นต่อ",
"prio": "",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 2"
},
{
"n": 11,
"name": "เกมยิงลูกโป่ง",
"detail": "ฉากเกมยิงลูกโป่ง (MG7 Mega Virus) เล็กไป — ต้องการให้ภาพพื้นหลัง (BG) เต็มจอ แต่การเดิน (movement) และพื้นที่เล่น (area) ต่างๆ คงเดิม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod play.js?v=0.006250401): BG ของ MG7 เป็นภาพ 16:9 (1920x1080) แต่โค้ดเดิม map ภาพเข้า world rect 4:3 (640x480) ทำให้ภาพถูกบีบ + เหลือขอบดำซ้าย-ขวา = ฉากดูเล็ก. เปลี่ยนการวาด BG ของ balloon_boss เป็นแบบ cover เต็มจอ (รักษาสัดส่วน 16:9, crop ส่วนเกิน, จัดกึ่งกลาง) วาดแยกจาก world layer → กล้อง/movement/area ของเกมคงเดิมทุกอย่าง เปลี่ยนเฉพาะพื้นหลังให้เต็มจอ. ไฟล์: Game/public/js/play.js (draw loop, สาขา isBalloonBoss cover-fill).",
"cat": "Minigame 7"
},
{
"n": 12,
"name": "เกมต่อตึก ผิด flow",
"detail": "ปรับ flow ให้ถูกต้อง คือ เล่นทีละคน เรียงตามคิวไปเรื่อย โดยที่ blog ตามสีผู้เล่นด้วย",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 13,
"name": "เข้าร่วมห้องไม่ได้",
"detail": "สร้างห้องส่วนตัวเเล้ว เเต่เพื่อนจอยเข้าไปไม่ได้ ทั้งที่กรอกข้อมูลครบ",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 14,
"name": "Room ID",
"detail": "เวลาจอยห้องให้กรอกเเค่ ID กับรหัสที่สร้าง",
"prio": "ต่ำ",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 15,
"name": "จอยห้องคนอื่น",
"detail": "ในกรณีที่จอยห้องคนอื่น ไปหน้าเข้าร่วม เเละกดปุ่มเข้าร่วมไปเเล้ว จากนั้นกดปุ่ม exit จะพาไปหน้าสร้างห้อง สิ่งที่ถูกต้อง มันควรมาหน้า ห้องเข้าร่วมเกม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 16,
"name": "grid กับ ฉาก ไม่ตรงกัน",
"detail": "เกม Quiz battle เกมที่ 1 ฉากกับกริดไม่ตรงกัน ทำให้เดินทะลุ หรือเดินติดในจุดที่ไม่ควรติด",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 1"
},
{
"n": 17,
"name": "ตัวละครวิ่งเร็วเกินไป",
"detail": "เกม Quiz battle เกมที่ 1 ตัวละครผู้เล่นเร็วเกินไป",
"prio": "กลาง",
"status": "เปิด",
"updated": "2026-06-25",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 18,
"name": "Oauth Social log-in error",
"detail": "ไม่สามารถ log-inด้วย facebook / Google ได้ path: /oauth-public.php",
"prio": "ต่ำ",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 19,
"name": "หน้า login",
"detail": "นโยบายความเป็นส่วนตัวกดไม่ได้",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 20,
"name": "แสดงเลข version ตรงมุมจอ",
"detail": "แสดงเลข version ตรงมุมจอขวาบนหรือขวาล่างก็ได้",
"prio": "ต่ำ",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — badge มุมขวาล่าง อ่าน ?v= อัตโนมัติ",
"cat": "อื่นๆ"
},
{
"n": 21,
"name": "ลดขนาดตัวละคร",
"detail": "ลดขนาดตัวละครในห้อง lobby ลงอีก 2เท่า",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 22,
"name": "เปลี่ยน ui popup ตอนพูดบนหัว",
"detail": "แก้ png seq บนหัวใหม่ เป็นแบบอื่น",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 23,
"name": "ชื่่อตัวละครทับลำโพง/ทับ bubble",
"detail": "ใน lobby ขยับชื่อลงมาอย่าให้ทับ",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 24,
"name": "UI แต่งตัว : การแจ้งเตือนสีตัวละครถูกใช้แล้ว",
"detail": "ตอนเลือกสีให้มีคำแจ้งเตือน used",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 25,
"name": "เพิ่ม ข้อความแจ้งเตือนอัพเดทกลางจอ",
"detail": "ถ้ามีการอัพเดทซีน อัพเดทสีตัวละครให้มีป็อปอัพเด้งมากลางหน้าจอ ว่ามีการอัพเดทและให้รอโหลด",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 26,
"name": "เอาเงาตัวละครออก 1 อัน",
"detail": "ตอนนี้ตัวละครมีเงา 2 ชั้น เอาออกไป 1 ชั้น",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — ปิด default-shadow overlay เหลือเงาในสไปรต์",
"cat": "อื่นๆ"
},
{
"n": 27,
"name": "optimize ตัวละครให้เล็กลงอีก มันโหลดช้า",
"detail": "",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 28,
"name": "ปุ่ม start ใน lobby",
"detail": "ทำให้ดูโต้ตอบหน่อย เช่นพอเดินเข้าไปใกล้ให้มัน glow ขึ้นมา",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 29,
"name": "เกมต่อตึก ผิด flow",
"detail": "สี blog กับคนเล่นยังไม่สัมพันกัน ต้องเรียงตามสี และตามคิว",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 30,
"name": "เกมต่อตึก บั๊กคนที่ 5",
"detail": "คนที่ 5 ไม่สามารถเล่นได้",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 31,
"name": "เอา mock การ์ดผู้ต้องหาเก่าออกไป",
"detail": "มันแว่บๆมา",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส) — ซ่อนรูปจนรูปคดีจริงโหลด + spinner",
"cat": "LobbyB"
},
{
"n": 32,
"name": "เอา popup ขออนุญาตใช้ไมค์ออกไปเลย น่ารำคาญ",
"detail": "",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 33,
"name": "หน้า howto เปลี่ยนสีปุ่ม ready ด้วยตอน กดready แล้ว",
"detail": "เปลี่ยนเป้นสีเขียวพร้อมเครื่องหมายถูกตามดีไซน์",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 34,
"name": "เกม ใช่ หรือไม่ : ตอนจบบั๊ค",
"detail": "ตอนสรุปคะแนน ยังนับเวลาอยุ่ และขึ้น gameover ทับหน้าสรุปคะแนน",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG1 ใช่/ไม่: capture mg1AnyScore ก่อนล้าง (gameover) + guard quiz-phase หลังจบ (timer)",
"cat": "Minigame 1"
},
{
"n": 35,
"name": "เกมวิ่ง : กดปุ่มไม่กระโดด",
"detail": "ปุ่ม jump กดแล้วไม่กระโดด",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 36,
"name": "เกมวิ่ง : ตอนจบบั๊ค วิ่งเลยฉาก",
"detail": "ตอนจบบั๊ค วิ่งเลยฉาก (ในบางรอบ)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG2 วิ่งเลยฉาก: walk-to-finish ค่อยๆเดินเข้าเส้น + clamp ไม่เลยเส้น",
"cat": "Minigame 2"
},
{
"n": 37,
"name": "popup รับหลักฐาน ทับไมค์",
"detail": "กดไมค์ไม่ได้ ตอนรับหลักฐาน ไปดูเรื่องเลเยอร์อีกที",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 38,
"name": "หน้าส่งหลักฐาน ปุ่มส่งหลักฐานหลุดจอ (เครื่องของเนส)",
"detail": "เป้นเครื่องเนสคนเดียว",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "ระบบ disconnect"
},
{
"n": 39,
"name": "ห้องส่งหลักฐาน : เมื่อมีคนหลุด 1 คน จะไปต่อไม่ได้เลย",
"detail": "",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — testimony: disconnect ลบ submit/ready + client ใช้ totalPlayers authoritative",
"cat": "ระบบ disconnect"
},
{
"n": 40,
"name": "จำนวนคน ให้เล่นได้ตั้งแต่ 4-6 คน",
"detail": "กรณีเลือกผู้เล่นตั้งแต่ 4 คนขึ้นไปไม่ต้องมี bot",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 41,
"name": "เกม หยิบแผ่นป้าย",
"detail": "ต้องเล่นให้ครบ 10 ข้อก่อน ค่อยจบเกม (ตอนนี้เล่นถูก 1 ข้อ จบเกมเลย)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG4 หยิบป้าย: ใช้ map จริง (currentPlayMapId) เปิด mission flow + default 10 ข้อ",
"cat": "Minigame 4"
},
{
"n": 42,
"name": "เกม กระโดด",
"detail": "ท่ากระโดดแปลก ยืนซอยเท้าทำไม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-26",
"note": "anim ไม่ sync: ยืนบนแท่นที่เลื่อนลง world Y เปลี่ยนทุกเฟรม → ปลายทางเดา isWalking จาก delta = เห็น ซอยเท้า ตลอด. แก้: ส่ง isWalking จริงใน move (jsWalking) + ปลายทางใช้ค่านั้น (play.js ~19162/20501/24930). v0.006260100",
"cat": "Minigame 5"
},
{
"n": 43,
"name": "เกม กระโดด",
"detail": "ขั้นบันไดมันถี่ไป บางทีกระโดดแล้วเบิ้ลขั้นเลย ปรับให้ระยะห่างแต่ละขั้นมันเท่ากัน",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-26",
"note": "ขั้นถี่/ไม่เท่า → เบิ้ลขั้น+ติดรอแท่ง. แก้ layout mnptfts2: ระยะเท่ากันทุกขั้น (gap 3 แถว, rows 2/5/8/11/14/17, zigzag กว้าง6) ในโซนปลอดภัย (เลี่ยง hazard แถว 0/18/19). reach apex 4.5 > 3 = ถึงทุกขั้น (ไม่ติดรอ #43b) + ขั้นถัดไป-อีกขั้น 6 แถว > reach = กระโดดข้ามไม่ได้ (ไม่เบิ้ล). seam รอยต่อ wrap ห่าง 5 แถว 1 จุด/รอบ (h=20 หาร 3 ไม่ลง). restart service หลังแก้แมป",
"cat": "Minigame 5"
},
{
"n": 44,
"name": "เกมกระโดด",
"detail": "อีเวนท์พิเศษ (ทนาย/เทพความรู้) หายไป",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG5 อีเวนต์พิเศษ: เพิ่ม branch jump_survive วาง icon บน platform",
"cat": "Minigame 5"
},
{
"n": 45,
"name": "เกมไม่รองรับ 4K",
"detail": "หน้ารับหลักฐาน (หลังจากเล่นเกมจบ)",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 46,
"name": "เกมยิิงอุกาบาด",
"detail": "สีตัวละครยังไม่ตรงกับที่เลือกมา",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG6 สี: ยาน Rocket-1..8 ตาม lobby theme ของผู้เล่น",
"cat": "Minigame 6"
},
{
"n": 47,
"name": "เกมยิิงอุกาบาด",
"detail": "อุกาบาดให้พุ่งเร็วกว่านี้ และลูกเล็กกว่านี้เกมง่ายไป",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG6: อุกาบาตเร็วขึ้น (+~50%) + เล็กลง (48->34/11->8)",
"cat": "Minigame 6"
},
{
"n": 48,
"name": "เพิ่ม popup เมื่อเกมค้าง เล่นต้่อไม่ได้ ให้มีปุ่มกดออกไปหน้าแรกสุด",
"detail": "สาเหตุเกิดจากมีคนหลุดระหว่างรอ host กด",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "ระบบ disconnect"
},
{
"n": 49,
"name": "กรณีผู้เล่นหลุดออกจากเกม",
"detail": "ถ้ามีคนหลุด ให้กลายเป้นบอทแทน",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว 2026-06-25: คนออกระหว่าง LobbyB/เปลี่ยนหน้า กลายเป็นบอทสีตามคนออก (ดูบั๊ก #73)",
"cat": "ระบบ disconnect"
},
{
"n": 50,
"name": "ผู้เล่น 6 คน: บางเกมเล่นได้แค่ 5 (คนที่ 6 หายจากสรุป)",
"detail": "สรุปคะแนนฝั่ง client slice(0,5) ตัดคนที่ 6 — แก้เป็น slice(0,6) ทุกมินิเกม (MG1/3/4/5/6/7)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส)",
"cat": "Minigame 1"
},
{
"n": 51,
"name": "ผู้เล่น 6 คน: คะแนนตอนจบไม่ตรงกันแต่ละจอ",
"detail": "MG1 merge คะแนน server / MG6,7 merge คะแนน host -> ครบ 6 ตรงกัน (MG3 ยัง local)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส)",
"cat": "Minigame 6"
},
{
"n": 52,
"name": "หน้าไต่สวน/สรุปหลักฐาน: กรอบปลิ้นตอน 6 คน",
"detail": "member row grid repeat(5)->repeat(6) ใน testimony-overlay.css (คนที่6 ตกแถว2)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส)",
"cat": "LobbyB"
},
{
"n": 53,
"name": "เพิ่มหน้าโหลด (loading) ตอนเข้าเกม",
"detail": "overlay spinner กันเห็นจอเปล่า/asset แว่บ ซ่อนเมื่อ draw เฟรมแรก + safety 8s",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 54,
"name": "host โหลดนานมากตอนเข้าเกม",
"detail": "optimize asset/char (เกี่ยว row 28) — loading overlay ช่วยซ่อน แต่ยังโหลดช้าจริง",
"prio": "สูง",
"status": "เปิด",
"updated": "2026-06-25",
"note": "loading overlay บังอาการแล้ว · root = canvas tint ภาพตัวละครใหญ่ (source ไม่อยู่ใน repo) → ต้อง optimize ภาพแยก",
"cat": "อื่นๆ"
},
{
"n": 55,
"name": "หลังออกจาก LobbyA ไม่ให้ใครเข้า join",
"detail": "server มี joinLocked (set ตอน LobbyA->case ~8656, check ~7585 whitelist nickname) — ต้อง repro ว่าใครเข้าได้ยังไง (room list ยังโชว์? ชื่อซ้ำ whitelist?)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 — กรองห้อง joinLocked ออกจาก /api/spaces list (ห้องเริ่มคดีแล้วหาย) + server reject อยู่แล้ว",
"cat": "LobbyB"
},
{
"n": 56,
"name": "ตอนกลับ LobbyB โชว์รูปแรกก่อนแล้วค่อยถูก",
"detail": "เหมือน #1 การ์ด — ต้องใส่ loading state ตอน return LobbyB ด้วย (มี room-loading-overlay อยู่แต่ซ่อนเร็วไป)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0308 — การ์ดมี spinner (#1) + room-loading overlay ใส่ % + รอ char preload เสร็จก่อนซ่อน + safety 12s",
"cat": "อื่นๆ"
},
{
"n": 57,
"name": "เล่นมินิเกมเกิน 3 ครั้งต่อผู้ต้องสงสัย (+การ์ดพิเศษโผล่รอบที่4)",
"detail": "server cap suspect-pick-start (max หลักฐาน>=3 reject) + award cap 3 + client canInvestigateSuspect<3 (เดิม stub return true). UI messaging มีอยู่แล้ว",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "LobbyB"
},
{
"n": 58,
"name": "ปุ่ม \"เล่นเกมต่อ\" ไม่รู้ว่าค้างหรือรอคนอื่น",
"detail": "text \"รอผู้เล่นอื่น (X/Y)\" ถูก set อยู่แล้วแต่ CSS font-size:0 ซ่อน → เพิ่ม .is-waiting CSS โชว์ข้อความ+spinner แทนรูปปุ่ม",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 59,
"name": "loading ไม่คลุม scene transition (เห็นฉากเก่าก่อนเปลี่ยน)",
"detail": "LobbyA->B (in-page swap) โชว์ room-loading ระหว่าง fetch+swap · LobbyB->เกม โชว์ overlay ก่อน location.href · play.html มี overlay รับช่วง",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "LobbyA"
},
{
"n": 60,
"name": "ทำหน้า testcase บนเซิร์ฟเวอร์",
"detail": "srv1361159.hstgr.cloud/testcase — bug list + filter/search/นับสถานะ generate จาก xlsx",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 61,
"name": "MG3 สี block ไม่ตรง user (เกม+admin)",
"detail": "PNG Block-Code-1..8 deploy ครบแล้ว · ต้อง wire สี block = lobby theme ของผู้วาง (seat=theme refactor, ต้องเทส 2 จอ) + admin 8-slot config",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006250200 — block สีตาม theme ผู้เล่นที่ seat (Block-Code-1..8) ตอนวาด ไม่แตะ turn/sync",
"cat": "Minigame 3"
},
{
"n": 62,
"name": "MG1/MG4 user/bot ติดกัน (เดินไม่ผ่าน)",
"detail": "เพิ่ม anti-stuck: ดันชนเพื่อน → เขย่า (shake) + ปลดล็อกเดินผ่านกันชั่วคราว คงกำแพง/area/quiz-lock; ทั้งผู้เล่นและบอท",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006250100 (รอเทส)",
"cat": "Minigame 4"
},
{
"n": 63,
"name": "LobbyA มือถือ กดเดินไม่ได้",
"detail": "lobby canvas มีแค่ dblclick (ไม่มี touch) → มือถือแตะเดินไม่ได้. แก้: เพิ่ม tap-to-walk (เฉพาะ touch, PC ยัง dblclick) + DPI-safe coord + touch-action:none + zoom-out default บนจอ<600px (PC ย่อส่วน)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 room-lobby.js?v=0.0310 (รอเทสมือถือ) · PC ไม่กระทบ",
"cat": "LobbyA"
},
{
"n": 64,
"name": "มือถือ: ปุ่มแชท AI ใหญ่ไป + ปุ่ม F (host) ไม่มี",
"detail": "AI chat collapsed btn 213x201px → ใหญ่บนมือถือ. + การกด F (interact: host เลือกระดับ/คดี, ห้องแต่งตัว) ไม่มีบนมือถือ",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (css 0.0176/js 0.0311): media<600px ย่อปุ่ม AI (92px) + ปุ่มลอย \"โต้ตอบ(F)\" บน touch-primary device (dispatch synthetic KeyF). PC ไม่เห็น",
"cat": "LobbyB"
},
{
"n": 65,
"name": "อีเวนต์พิเศษ (ทนาย/เทพ) = 1 ใน 3 (ตามดีไซน์)",
"detail": "ดีไซน์: 3 เกม/LobbyB เล่นได้เกมละ 3 ครั้ง · อีเวนต์พิเศษ 1/3 ต่อครั้ง (1 ใบ) · Test Mode บังคับ. revert การแก้ \">1 ต่อรอบ\" กลับเป็น 1 ใบ/ครั้งที่ติด",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ revert 2026-06-25 — กลับเป็น 1/3 (1 ใบ/ครั้งที่ติด) ตามดีไซน์ · ยืนยันโครงสร้าง 3เกม×3ครั้ง + disruptor toggle (>4คน/Test Mode) ถูกต้อง",
"cat": "อื่นๆ"
},
{
"n": 66,
"name": "host หลุด → ย้าย host ให้คนอยู่นานสุด",
"detail": "pickNextHostPeer เลือก spawnJoinOrder ต่ำสุด (เสถียรข้าม reconnect) แทน Map-first + host จริง reclaim ได้",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (server) — Part 1 host migration",
"cat": "ระบบ disconnect"
},
{
"n": 67,
"name": "คนหลุดกลางเกม → 10 วิ reconnect + บอทเล่นแทน",
"detail": "peer-disconnect-grace แบนเนอร์นับ 10 วิ · กลับทัน=cancel เล่นต่อ · ครบเวลา=peer-bot-takeover host เติมบอท 1 ตัว (rebalance, cap≤6). บอทไม่สืบทอดตำแหน่ง/คะแนน",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (server+play.js v0.006250400) — Part 2 แบบ A · seamless(host คุม entity เดิม)=ค้าง(เสี่ยง)",
"cat": "ระบบ disconnect"
},
{
"n": 68,
"name": "ห้องแต่งตัว (customize) มือถือแนวนอนใช้ไม่ได้",
"detail": "@media max-width:960px stack แนวตั้ง → panel สูงตาม aspect ล้นจอแนวนอน. แก้: เพิ่ม media (landscape + max-height:540px) ใช้เลย์เอาต์แนวนอน + ขนาดอิงความสูงจอ",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 customize-popup.css?v=43 (Main-Lobby + room-lobby) — รอเทสมือถือแนวนอน",
"cat": "Minigame 3"
},
{
"n": 69,
"name": "UI ห้องเกม (LobbyA) มือถือแนวนอน — ย่อให้พอดี",
"detail": "room-lobby ไม่มี landscape media query → chat panel(576x525), ปุ่ม voice/แว่น/ถ้วย(80px), AI chat ล้น/ใหญ่บนแนวนอน. เพิ่ม @media(landscape+max-height:540px) ย่อตามความสูงจอ (อิง Main-Lobby pattern)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 room-lobby.css?v=0.0177 — chat/BR buttons/AI/ready/exit · overlay หลักฐาน-อันดับ (inline/JS) ยังต้องทำแยก",
"cat": "LobbyB"
},
{
"n": 70,
"name": "การ์ดรางวัลหลักฐาน มือถือใหญ่เกิน กดปุ่มไม่ได้",
"detail": "หน้าโชว์การ์ดรางวัล \"คุณได้รับหลักฐาน!\" (หลังจบมินิเกมสืบสวน) บนมือถือการ์ดใหญ่มากจนเนื้อหา+ปุ่ม \"เก็บเข้าแฟ้ม\" หลุดล่างจอ กดปิดไม่ได้",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod play.js?v=0.006250402): ทำ overlay เป็น scroll container + inner margin:auto (กัน flexbox clip ด้านบนเวลาเนื้อหาสูงเกินจอ) + คุมขนาดการ์ด/หัวข้อ/ปุ่มด้วย clamp ตาม viewport (การ์ด max-width min(72vw,360px) + max-height 52vh, ปุ่ม font/padding clamp). มือถือแนวตั้งเลื่อนถึงปุ่มได้เสมอ. ไฟล์: Game/public/js/play.js (showInSceneEvidenceReveal).",
"cat": "ระบบ disconnect"
},
{
"n": 71,
"name": "หน้าผลชนะ (โพเดียม) ตัวละครลอย ไม่ยืนบนพื้น",
"detail": "หน้า \"ยินดีด้วย ทีมนักสืบ ชนะ!\" ตัวละครผู้ชนะลอยอยู่กลางอากาศ ไม่ยืนบนพื้นเวที",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod room-lobby.js?v=0.0312): พื้นหลัง win-bg เดิม center/cover ทำให้พื้นเวทีถูก crop เลื่อน + podium ตั้ง bottom:9% ตัวละครเลยลอย. แก้เป็น (1) ปักพื้นหลังไว้ขอบล่าง background-position:center bottom -> พื้นเวทีติดขอบล่างจอเสมอทุกสัดส่วน (2) ลด podium bottom 9% -> 2% ให้ตัวละครยืนบนพื้น. ไฟล์: Game/public/js/room-lobby.js (injectFinalResultStyle, #trial-final-podium).",
"cat": "อื่นๆ"
},
{
"n": 72,
"name": "ป๊อปอัปขอสิทธิ์ไมค์ เด้งทุกครั้งที่เข้า Lobby",
"detail": "หน้าขอสิทธิ์ไมโครโฟน (mic-permission-overlay) เด้งขึ้นทุกครั้งที่เข้า LobbyA/LobbyB น่ารำคาญ ทั้งที่เบราว์เซอร์จำสิทธิ์ให้แล้ว",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod room-lobby.js?v=0.0313): เดิม overlay เป็น display:flex default จึงเด้งทุกครั้งที่โหลดหน้า. เพิ่มตรวจสิทธิ์ผ่าน navigator.permissions.query(microphone) + จำสถานะใน localStorage (justiceMicPromptHandled). แสดงเฉพาะครั้งแรกที่ยังไม่เคยตัดสินใจ (permission=prompt และยังไม่เคยกดอนุญาต/ปิด); ถ้า granted แล้ว/เคยจัดการแล้ว → ซ่อนทันที (มี inline script กันแฟลชก่อน paint). ปุ่ม 🎤 ขอสิทธิ์ไมค์ ในห้องยังใช้ขอใหม่ได้. ไฟล์: room-lobby.html (inline gate) + room-lobby.js (gateMicPermissionOverlay, hideMicPermissionOverlay).",
"cat": "LobbyA"
},
{
"n": 73,
"name": "คนออก LobbyB / ช่วงเปลี่ยนหน้า → ไม่เติมบอท + ค้างรอผู้เล่น เริ่มเกมต่อไม่ได้",
"detail": "คนหลุดระหว่างนั่งอยู่ LobbyB หรือช่วงเปลี่ยนหน้า minigame→LobbyB เดิมไม่ถูกเติมบอทแทน + ยังถูกนับใน roster คดี → host กดเริ่มสืบสวนรอบต่อไปไม่ได้ ค้าง รอผู้เล่น (x/y) ตลอดไป",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "ขยาย grace+bot-takeover gate ให้ครอบ LobbyB/คดีล็อก ไม่ใช่แค่ minigame active (server.js ~8921: inCaseFlowForBot = detectiveMinigameActive || serverMapIsPostCaseLobbyB || (joinLocked && caseParticipantNicknames.size>0)). คนออก→หลัง grace 10วิ ลบจาก roster + เติมบอทสีตามคนออก + presence ใหม่ → allHere=true เล่นต่อได้. + ส่ง botSlotCount ใน lobby-tint-sync (server.js ~121) + client อัปเดต lobbyBotSlotCount ก่อน render (room-lobby.js ~768) ให้บอทโผล่ทันทีไม่ต้อง re-join. v0.0323",
"cat": "ระบบ disconnect"
},
{
"n": 74,
"name": "แถวหน้าตัวละคร (ready ใต้ปุ่ม HOW TO) ค้างบนจอระหว่างเล่นเกม",
"detail": "หลังกด HOW TO/Ready แล้วเริ่มเกม กล่องแถว avatar คนที่กด ready (ใต้ปุ่ม) ไม่ถูกซ่อน ค้างอยู่กลางจอล่างทับฉากเกม (เห็นชัดใน MG3 stack) — กระทบ MG1/3/5/6",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-26",
"note": "กล่องลอย #gch-ready-avatars-float ถูกซ่อนเฉพาะ MG2 (dismissGauntletCrownHowtoOverlayPlay) + MG4 (quiz_carry) เท่านั้น. begin*CountdownThenRun ของ MG1/3/5/6 (stack/jump/space/quiz) ตั้ง gauntletCrownHowtoVisible=false แต่ไม่เรียก hideReadyAvatarsFloat() → กล่องค้าง display:flex ทั้งเกม (ไม่มี driver เรียกใหม่ตอน live). แก้: เพิ่ม hideReadyAvatarsFloat() ในทั้ง 4 ฟังก์ชันเริ่มเกม (play.js). v0.006260101",
"cat": "อื่นๆ"
}
]
@@ -0,0 +1,732 @@
[
{
"n": 1,
"name": "แต่งตัวไม่ได้",
"detail": "แต่งตัวยังไม่ได้ มีพี่คิวทำได้คนเดียว",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 2,
"name": "ตัวละครไม่แสดง",
"detail": "หน้าเลือกเกม Quiz battle ภาพตัวละครไม่แสดง",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 1"
},
{
"n": 3,
"name": "ระบบ join เกมมีปัญหา",
"detail": "เมื่อเข้ามินิเกม : บางคนเล่นเกมกะบอท บางคนเล่นกะเพื่อน แต่ละคนเข้ามินิเกมแล้วเจอกันบ้าง ไม่เจอกันบ้าง",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 4,
"name": "ระบบ Host มีปัญหา - 1",
"detail": "เป็น host ในเกม ตอนที่เลือกผู้ต้องสงสัย รอบที่ 2 ระบบบอกว่าให้รอ host ไม่สามารถเลือกการ์ดและกดเริ่มสืบสวนได้",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 5,
"name": "ระบบ Host มีปัญหา - 2",
"detail": "เมื่อกดออกจากเกม Host เปลี่ยนจากพี่ต้อมเป็นพี่คิวอัติโนมัติ",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 6,
"name": "ระบบ Host มีปัญหา - 3",
"detail": "เมื่อเข้ามินิเกม ทุกคนเห็นตัวเองเป็น host หมดเลย แต่กดเริ่มเกมไม่ได้",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 7,
"name": "ระบบสีตัวละคร ยังใช้ไม่ได้",
"detail": "สีตัวละครเปลี่ยนไปเรื่อย ไม่รุ้ว่าเราสีอะไรแน่ งงไปหมด",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 8,
"name": "หน้าเข้าร่วมเกมส์",
"detail": "ห้องไหนที่เริ่มเกมส์ไปแล้ว เมื่อกดเข้าร่วมแสดง popup alert error ปรับดีไซต์ให้สวยงามกว่านี้",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 9,
"name": "เกมวิ่งขึ้นบันได ผิด flow",
"detail": "ปรับ flow ให้ถูกต้อง คือ ใครตกจอตาย แต่ถ้าขึ้นไปอยู่บนสุดแล้วจะกระโดดไม่ไปต่อเฉยๆ แต่ไม่ตาย",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 10,
"name": "เกมวิ่งขึ้นบันได",
"detail": "เวลาหมดแล้ว ให้เล่นรอบ 2 auto / รอบ 3 มี popup ให้กดเล่นต่อ",
"prio": "",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 2"
},
{
"n": 11,
"name": "เกมยิงลูกโป่ง",
"detail": "ฉากเกมยิงลูกโป่ง (MG7 Mega Virus) เล็กไป — ต้องการให้ภาพพื้นหลัง (BG) เต็มจอ แต่การเดิน (movement) และพื้นที่เล่น (area) ต่างๆ คงเดิม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod play.js?v=0.006250401): BG ของ MG7 เป็นภาพ 16:9 (1920x1080) แต่โค้ดเดิม map ภาพเข้า world rect 4:3 (640x480) ทำให้ภาพถูกบีบ + เหลือขอบดำซ้าย-ขวา = ฉากดูเล็ก. เปลี่ยนการวาด BG ของ balloon_boss เป็นแบบ cover เต็มจอ (รักษาสัดส่วน 16:9, crop ส่วนเกิน, จัดกึ่งกลาง) วาดแยกจาก world layer → กล้อง/movement/area ของเกมคงเดิมทุกอย่าง เปลี่ยนเฉพาะพื้นหลังให้เต็มจอ. ไฟล์: Game/public/js/play.js (draw loop, สาขา isBalloonBoss cover-fill).",
"cat": "Minigame 7"
},
{
"n": 12,
"name": "เกมต่อตึก ผิด flow",
"detail": "ปรับ flow ให้ถูกต้อง คือ เล่นทีละคน เรียงตามคิวไปเรื่อย โดยที่ blog ตามสีผู้เล่นด้วย",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 13,
"name": "เข้าร่วมห้องไม่ได้",
"detail": "สร้างห้องส่วนตัวเเล้ว เเต่เพื่อนจอยเข้าไปไม่ได้ ทั้งที่กรอกข้อมูลครบ",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 14,
"name": "Room ID",
"detail": "เวลาจอยห้องให้กรอกเเค่ ID กับรหัสที่สร้าง",
"prio": "ต่ำ",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 15,
"name": "จอยห้องคนอื่น",
"detail": "ในกรณีที่จอยห้องคนอื่น ไปหน้าเข้าร่วม เเละกดปุ่มเข้าร่วมไปเเล้ว จากนั้นกดปุ่ม exit จะพาไปหน้าสร้างห้อง สิ่งที่ถูกต้อง มันควรมาหน้า ห้องเข้าร่วมเกม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 16,
"name": "grid กับ ฉาก ไม่ตรงกัน",
"detail": "เกม Quiz battle เกมที่ 1 ฉากกับกริดไม่ตรงกัน ทำให้เดินทะลุ หรือเดินติดในจุดที่ไม่ควรติด",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 1"
},
{
"n": 17,
"name": "ตัวละครวิ่งเร็วเกินไป",
"detail": "เกม Quiz battle เกมที่ 1 ตัวละครผู้เล่นเร็วเกินไป",
"prio": "กลาง",
"status": "เปิด",
"updated": "2026-06-25",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 18,
"name": "Oauth Social log-in error",
"detail": "ไม่สามารถ log-inด้วย facebook / Google ได้ path: /oauth-public.php",
"prio": "ต่ำ",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 19,
"name": "หน้า login",
"detail": "นโยบายความเป็นส่วนตัวกดไม่ได้",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 20,
"name": "แสดงเลข version ตรงมุมจอ",
"detail": "แสดงเลข version ตรงมุมจอขวาบนหรือขวาล่างก็ได้",
"prio": "ต่ำ",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — badge มุมขวาล่าง อ่าน ?v= อัตโนมัติ",
"cat": "อื่นๆ"
},
{
"n": 21,
"name": "ลดขนาดตัวละคร",
"detail": "ลดขนาดตัวละครในห้อง lobby ลงอีก 2เท่า",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 22,
"name": "เปลี่ยน ui popup ตอนพูดบนหัว",
"detail": "แก้ png seq บนหัวใหม่ เป็นแบบอื่น",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 23,
"name": "ชื่่อตัวละครทับลำโพง/ทับ bubble",
"detail": "ใน lobby ขยับชื่อลงมาอย่าให้ทับ",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 24,
"name": "UI แต่งตัว : การแจ้งเตือนสีตัวละครถูกใช้แล้ว",
"detail": "ตอนเลือกสีให้มีคำแจ้งเตือน used",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 25,
"name": "เพิ่ม ข้อความแจ้งเตือนอัพเดทกลางจอ",
"detail": "ถ้ามีการอัพเดทซีน อัพเดทสีตัวละครให้มีป็อปอัพเด้งมากลางหน้าจอ ว่ามีการอัพเดทและให้รอโหลด",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 26,
"name": "เอาเงาตัวละครออก 1 อัน",
"detail": "ตอนนี้ตัวละครมีเงา 2 ชั้น เอาออกไป 1 ชั้น",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — ปิด default-shadow overlay เหลือเงาในสไปรต์",
"cat": "อื่นๆ"
},
{
"n": 27,
"name": "optimize ตัวละครให้เล็กลงอีก มันโหลดช้า",
"detail": "",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 28,
"name": "ปุ่ม start ใน lobby",
"detail": "ทำให้ดูโต้ตอบหน่อย เช่นพอเดินเข้าไปใกล้ให้มัน glow ขึ้นมา",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 29,
"name": "เกมต่อตึก ผิด flow",
"detail": "สี blog กับคนเล่นยังไม่สัมพันกัน ต้องเรียงตามสี และตามคิว",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 30,
"name": "เกมต่อตึก บั๊กคนที่ 5",
"detail": "คนที่ 5 ไม่สามารถเล่นได้",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 3"
},
{
"n": 31,
"name": "เอา mock การ์ดผู้ต้องหาเก่าออกไป",
"detail": "มันแว่บๆมา",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส) — ซ่อนรูปจนรูปคดีจริงโหลด + spinner",
"cat": "LobbyB"
},
{
"n": 32,
"name": "เอา popup ขออนุญาตใช้ไมค์ออกไปเลย น่ารำคาญ",
"detail": "",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 33,
"name": "หน้า howto เปลี่ยนสีปุ่ม ready ด้วยตอน กดready แล้ว",
"detail": "เปลี่ยนเป้นสีเขียวพร้อมเครื่องหมายถูกตามดีไซน์",
"prio": "สูง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyA"
},
{
"n": 34,
"name": "เกม ใช่ หรือไม่ : ตอนจบบั๊ค",
"detail": "ตอนสรุปคะแนน ยังนับเวลาอยุ่ และขึ้น gameover ทับหน้าสรุปคะแนน",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG1 ใช่/ไม่: capture mg1AnyScore ก่อนล้าง (gameover) + guard quiz-phase หลังจบ (timer)",
"cat": "Minigame 1"
},
{
"n": 35,
"name": "เกมวิ่ง : กดปุ่มไม่กระโดด",
"detail": "ปุ่ม jump กดแล้วไม่กระโดด",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "Minigame 5"
},
{
"n": 36,
"name": "เกมวิ่ง : ตอนจบบั๊ค วิ่งเลยฉาก",
"detail": "ตอนจบบั๊ค วิ่งเลยฉาก (ในบางรอบ)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG2 วิ่งเลยฉาก: walk-to-finish ค่อยๆเดินเข้าเส้น + clamp ไม่เลยเส้น",
"cat": "Minigame 2"
},
{
"n": 37,
"name": "popup รับหลักฐาน ทับไมค์",
"detail": "กดไมค์ไม่ได้ ตอนรับหลักฐาน ไปดูเรื่องเลเยอร์อีกที",
"prio": "กลาง",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 38,
"name": "หน้าส่งหลักฐาน ปุ่มส่งหลักฐานหลุดจอ (เครื่องของเนส)",
"detail": "เป้นเครื่องเนสคนเดียว",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "ระบบ disconnect"
},
{
"n": 39,
"name": "ห้องส่งหลักฐาน : เมื่อมีคนหลุด 1 คน จะไปต่อไม่ได้เลย",
"detail": "",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — testimony: disconnect ลบ submit/ready + client ใช้ totalPlayers authoritative",
"cat": "ระบบ disconnect"
},
{
"n": 40,
"name": "จำนวนคน ให้เล่นได้ตั้งแต่ 4-6 คน",
"detail": "กรณีเลือกผู้เล่นตั้งแต่ 4 คนขึ้นไปไม่ต้องมี bot",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "อื่นๆ"
},
{
"n": 41,
"name": "เกม หยิบแผ่นป้าย",
"detail": "ต้องเล่นให้ครบ 10 ข้อก่อน ค่อยจบเกม (ตอนนี้เล่นถูก 1 ข้อ จบเกมเลย)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG4 หยิบป้าย: ใช้ map จริง (currentPlayMapId) เปิด mission flow + default 10 ข้อ",
"cat": "Minigame 4"
},
{
"n": 42,
"name": "เกม กระโดด",
"detail": "ท่ากระโดดแปลก ยืนซอยเท้าทำไม",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-26",
"note": "anim ไม่ sync: ยืนบนแท่นที่เลื่อนลง world Y เปลี่ยนทุกเฟรม → ปลายทางเดา isWalking จาก delta = เห็น ซอยเท้า ตลอด. แก้: ส่ง isWalking จริงใน move (jsWalking) + ปลายทางใช้ค่านั้น (play.js ~19162/20501/24930). v0.006260100",
"cat": "Minigame 5"
},
{
"n": 43,
"name": "เกม กระโดด",
"detail": "ขั้นบันไดมันถี่ไป บางทีกระโดดแล้วเบิ้ลขั้นเลย ปรับให้ระยะห่างแต่ละขั้นมันเท่ากัน",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-26",
"note": "ขั้นถี่/ไม่เท่า → เบิ้ลขั้น+ติดรอแท่ง. แก้ layout mnptfts2: ระยะเท่ากันทุกขั้น (gap 3 แถว, rows 2/5/8/11/14/17, zigzag กว้าง6) ในโซนปลอดภัย (เลี่ยง hazard แถว 0/18/19). reach apex 4.5 > 3 = ถึงทุกขั้น (ไม่ติดรอ #43b) + ขั้นถัดไป-อีกขั้น 6 แถว > reach = กระโดดข้ามไม่ได้ (ไม่เบิ้ล). seam รอยต่อ wrap ห่าง 5 แถว 1 จุด/รอบ (h=20 หาร 3 ไม่ลง). restart service หลังแก้แมป",
"cat": "Minigame 5"
},
{
"n": 44,
"name": "เกมกระโดด",
"detail": "อีเวนท์พิเศษ (ทนาย/เทพความรู้) หายไป",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG5 อีเวนต์พิเศษ: เพิ่ม branch jump_survive วาง icon บน platform",
"cat": "Minigame 5"
},
{
"n": 45,
"name": "เกมไม่รองรับ 4K",
"detail": "หน้ารับหลักฐาน (หลังจากเล่นเกมจบ)",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "LobbyB"
},
{
"n": 46,
"name": "เกมยิิงอุกาบาด",
"detail": "สีตัวละครยังไม่ตรงกับที่เลือกมา",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG6 สี: ยาน Rocket-1..8 ตาม lobby theme ของผู้เล่น",
"cat": "Minigame 6"
},
{
"n": 47,
"name": "เกมยิิงอุกาบาด",
"detail": "อุกาบาดให้พุ่งเร็วกว่านี้ และลูกเล็กกว่านี้เกมง่ายไป",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส) — MG6: อุกาบาตเร็วขึ้น (+~50%) + เล็กลง (48->34/11->8)",
"cat": "Minigame 6"
},
{
"n": 48,
"name": "เพิ่ม popup เมื่อเกมค้าง เล่นต้่อไม่ได้ ให้มีปุ่มกดออกไปหน้าแรกสุด",
"detail": "สาเหตุเกิดจากมีคนหลุดระหว่างรอ host กด",
"prio": "วิกฤต",
"status": "เปิด",
"updated": "",
"note": "",
"cat": "ระบบ disconnect"
},
{
"n": 49,
"name": "กรณีผู้เล่นหลุดออกจากเกม",
"detail": "ถ้ามีคนหลุด ให้กลายเป้นบอทแทน",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว 2026-06-25: คนออกระหว่าง LobbyB/เปลี่ยนหน้า กลายเป็นบอทสีตามคนออก (ดูบั๊ก #73)",
"cat": "ระบบ disconnect"
},
{
"n": 50,
"name": "ผู้เล่น 6 คน: บางเกมเล่นได้แค่ 5 (คนที่ 6 หายจากสรุป)",
"detail": "สรุปคะแนนฝั่ง client slice(0,5) ตัดคนที่ 6 — แก้เป็น slice(0,6) ทุกมินิเกม (MG1/3/4/5/6/7)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส)",
"cat": "Minigame 1"
},
{
"n": 51,
"name": "ผู้เล่น 6 คน: คะแนนตอนจบไม่ตรงกันแต่ละจอ",
"detail": "MG1 merge คะแนน server / MG6,7 merge คะแนน host -> ครบ 6 ตรงกัน (MG3 ยัง local)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-24",
"note": "✅ แก้แล้ว 2026-06-24 v0.006242500 (รอเทส)",
"cat": "Minigame 6"
},
{
"n": 52,
"name": "หน้าไต่สวน/สรุปหลักฐาน: กรอบปลิ้นตอน 6 คน",
"detail": "member row grid repeat(5)->repeat(6) ใน testimony-overlay.css (คนที่6 ตกแถว2)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส)",
"cat": "LobbyB"
},
{
"n": 53,
"name": "เพิ่มหน้าโหลด (loading) ตอนเข้าเกม",
"detail": "overlay spinner กันเห็นจอเปล่า/asset แว่บ ซ่อนเมื่อ draw เฟรมแรก + safety 8s",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006242530 (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 54,
"name": "host โหลดนานมากตอนเข้าเกม",
"detail": "optimize asset/char (เกี่ยว row 28) — loading overlay ช่วยซ่อน แต่ยังโหลดช้าจริง",
"prio": "สูง",
"status": "เปิด",
"updated": "2026-06-25",
"note": "loading overlay บังอาการแล้ว · root = canvas tint ภาพตัวละครใหญ่ (source ไม่อยู่ใน repo) → ต้อง optimize ภาพแยก",
"cat": "อื่นๆ"
},
{
"n": 55,
"name": "หลังออกจาก LobbyA ไม่ให้ใครเข้า join",
"detail": "server มี joinLocked (set ตอน LobbyA->case ~8656, check ~7585 whitelist nickname) — ต้อง repro ว่าใครเข้าได้ยังไง (room list ยังโชว์? ชื่อซ้ำ whitelist?)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 — กรองห้อง joinLocked ออกจาก /api/spaces list (ห้องเริ่มคดีแล้วหาย) + server reject อยู่แล้ว",
"cat": "LobbyB"
},
{
"n": 56,
"name": "ตอนกลับ LobbyB โชว์รูปแรกก่อนแล้วค่อยถูก",
"detail": "เหมือน #1 การ์ด — ต้องใส่ loading state ตอน return LobbyB ด้วย (มี room-loading-overlay อยู่แต่ซ่อนเร็วไป)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0308 — การ์ดมี spinner (#1) + room-loading overlay ใส่ % + รอ char preload เสร็จก่อนซ่อน + safety 12s",
"cat": "อื่นๆ"
},
{
"n": 57,
"name": "เล่นมินิเกมเกิน 3 ครั้งต่อผู้ต้องสงสัย (+การ์ดพิเศษโผล่รอบที่4)",
"detail": "server cap suspect-pick-start (max หลักฐาน>=3 reject) + award cap 3 + client canInvestigateSuspect<3 (เดิม stub return true). UI messaging มีอยู่แล้ว",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "LobbyB"
},
{
"n": 58,
"name": "ปุ่ม \"เล่นเกมต่อ\" ไม่รู้ว่าค้างหรือรอคนอื่น",
"detail": "text \"รอผู้เล่นอื่น (X/Y)\" ถูก set อยู่แล้วแต่ CSS font-size:0 ซ่อน → เพิ่ม .is-waiting CSS โชว์ข้อความ+spinner แทนรูปปุ่ม",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 59,
"name": "loading ไม่คลุม scene transition (เห็นฉากเก่าก่อนเปลี่ยน)",
"detail": "LobbyA->B (in-page swap) โชว์ room-loading ระหว่าง fetch+swap · LobbyB->เกม โชว์ overlay ก่อน location.href · play.html มี overlay รับช่วง",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "LobbyA"
},
{
"n": 60,
"name": "ทำหน้า testcase บนเซิร์ฟเวอร์",
"detail": "srv1361159.hstgr.cloud/testcase — bug list + filter/search/นับสถานะ generate จาก xlsx",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.0309/server (รอเทส)",
"cat": "อื่นๆ"
},
{
"n": 61,
"name": "MG3 สี block ไม่ตรง user (เกม+admin)",
"detail": "PNG Block-Code-1..8 deploy ครบแล้ว · ต้อง wire สี block = lobby theme ของผู้วาง (seat=theme refactor, ต้องเทส 2 จอ) + admin 8-slot config",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006250200 — block สีตาม theme ผู้เล่นที่ seat (Block-Code-1..8) ตอนวาด ไม่แตะ turn/sync",
"cat": "Minigame 3"
},
{
"n": 62,
"name": "MG1/MG4 user/bot ติดกัน (เดินไม่ผ่าน)",
"detail": "เพิ่ม anti-stuck: ดันชนเพื่อน → เขย่า (shake) + ปลดล็อกเดินผ่านกันชั่วคราว คงกำแพง/area/quiz-lock; ทั้งผู้เล่นและบอท",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 v0.006250100 (รอเทส)",
"cat": "Minigame 4"
},
{
"n": 63,
"name": "LobbyA มือถือ กดเดินไม่ได้",
"detail": "lobby canvas มีแค่ dblclick (ไม่มี touch) → มือถือแตะเดินไม่ได้. แก้: เพิ่ม tap-to-walk (เฉพาะ touch, PC ยัง dblclick) + DPI-safe coord + touch-action:none + zoom-out default บนจอ<600px (PC ย่อส่วน)",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 room-lobby.js?v=0.0310 (รอเทสมือถือ) · PC ไม่กระทบ",
"cat": "LobbyA"
},
{
"n": 64,
"name": "มือถือ: ปุ่มแชท AI ใหญ่ไป + ปุ่ม F (host) ไม่มี",
"detail": "AI chat collapsed btn 213x201px → ใหญ่บนมือถือ. + การกด F (interact: host เลือกระดับ/คดี, ห้องแต่งตัว) ไม่มีบนมือถือ",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (css 0.0176/js 0.0311): media<600px ย่อปุ่ม AI (92px) + ปุ่มลอย \"โต้ตอบ(F)\" บน touch-primary device (dispatch synthetic KeyF). PC ไม่เห็น",
"cat": "LobbyB"
},
{
"n": 65,
"name": "อีเวนต์พิเศษ (ทนาย/เทพ) = 1 ใน 3 (ตามดีไซน์)",
"detail": "ดีไซน์: 3 เกม/LobbyB เล่นได้เกมละ 3 ครั้ง · อีเวนต์พิเศษ 1/3 ต่อครั้ง (1 ใบ) · Test Mode บังคับ. revert การแก้ \">1 ต่อรอบ\" กลับเป็น 1 ใบ/ครั้งที่ติด",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ revert 2026-06-25 — กลับเป็น 1/3 (1 ใบ/ครั้งที่ติด) ตามดีไซน์ · ยืนยันโครงสร้าง 3เกม×3ครั้ง + disruptor toggle (>4คน/Test Mode) ถูกต้อง",
"cat": "อื่นๆ"
},
{
"n": 66,
"name": "host หลุด → ย้าย host ให้คนอยู่นานสุด",
"detail": "pickNextHostPeer เลือก spawnJoinOrder ต่ำสุด (เสถียรข้าม reconnect) แทน Map-first + host จริง reclaim ได้",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (server) — Part 1 host migration",
"cat": "ระบบ disconnect"
},
{
"n": 67,
"name": "คนหลุดกลางเกม → 10 วิ reconnect + บอทเล่นแทน",
"detail": "peer-disconnect-grace แบนเนอร์นับ 10 วิ · กลับทัน=cancel เล่นต่อ · ครบเวลา=peer-bot-takeover host เติมบอท 1 ตัว (rebalance, cap≤6). บอทไม่สืบทอดตำแหน่ง/คะแนน",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 (server+play.js v0.006250400) — Part 2 แบบ A · seamless(host คุม entity เดิม)=ค้าง(เสี่ยง)",
"cat": "ระบบ disconnect"
},
{
"n": 68,
"name": "ห้องแต่งตัว (customize) มือถือแนวนอนใช้ไม่ได้",
"detail": "@media max-width:960px stack แนวตั้ง → panel สูงตาม aspect ล้นจอแนวนอน. แก้: เพิ่ม media (landscape + max-height:540px) ใช้เลย์เอาต์แนวนอน + ขนาดอิงความสูงจอ",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 customize-popup.css?v=43 (Main-Lobby + room-lobby) — รอเทสมือถือแนวนอน",
"cat": "Minigame 3"
},
{
"n": 69,
"name": "UI ห้องเกม (LobbyA) มือถือแนวนอน — ย่อให้พอดี",
"detail": "room-lobby ไม่มี landscape media query → chat panel(576x525), ปุ่ม voice/แว่น/ถ้วย(80px), AI chat ล้น/ใหญ่บนแนวนอน. เพิ่ม @media(landscape+max-height:540px) ย่อตามความสูงจอ (อิง Main-Lobby pattern)",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "✅ แก้แล้ว 2026-06-25 room-lobby.css?v=0.0177 — chat/BR buttons/AI/ready/exit · overlay หลักฐาน-อันดับ (inline/JS) ยังต้องทำแยก",
"cat": "LobbyB"
},
{
"n": 70,
"name": "การ์ดรางวัลหลักฐาน มือถือใหญ่เกิน กดปุ่มไม่ได้",
"detail": "หน้าโชว์การ์ดรางวัล \"คุณได้รับหลักฐาน!\" (หลังจบมินิเกมสืบสวน) บนมือถือการ์ดใหญ่มากจนเนื้อหา+ปุ่ม \"เก็บเข้าแฟ้ม\" หลุดล่างจอ กดปิดไม่ได้",
"prio": "สูง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod play.js?v=0.006250402): ทำ overlay เป็น scroll container + inner margin:auto (กัน flexbox clip ด้านบนเวลาเนื้อหาสูงเกินจอ) + คุมขนาดการ์ด/หัวข้อ/ปุ่มด้วย clamp ตาม viewport (การ์ด max-width min(72vw,360px) + max-height 52vh, ปุ่ม font/padding clamp). มือถือแนวตั้งเลื่อนถึงปุ่มได้เสมอ. ไฟล์: Game/public/js/play.js (showInSceneEvidenceReveal).",
"cat": "ระบบ disconnect"
},
{
"n": 71,
"name": "หน้าผลชนะ (โพเดียม) ตัวละครลอย ไม่ยืนบนพื้น",
"detail": "หน้า \"ยินดีด้วย ทีมนักสืบ ชนะ!\" ตัวละครผู้ชนะลอยอยู่กลางอากาศ ไม่ยืนบนพื้นเวที",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod room-lobby.js?v=0.0312): พื้นหลัง win-bg เดิม center/cover ทำให้พื้นเวทีถูก crop เลื่อน + podium ตั้ง bottom:9% ตัวละครเลยลอย. แก้เป็น (1) ปักพื้นหลังไว้ขอบล่าง background-position:center bottom -> พื้นเวทีติดขอบล่างจอเสมอทุกสัดส่วน (2) ลด podium bottom 9% -> 2% ให้ตัวละครยืนบนพื้น. ไฟล์: Game/public/js/room-lobby.js (injectFinalResultStyle, #trial-final-podium).",
"cat": "อื่นๆ"
},
{
"n": 72,
"name": "ป๊อปอัปขอสิทธิ์ไมค์ เด้งทุกครั้งที่เข้า Lobby",
"detail": "หน้าขอสิทธิ์ไมโครโฟน (mic-permission-overlay) เด้งขึ้นทุกครั้งที่เข้า LobbyA/LobbyB น่ารำคาญ ทั้งที่เบราว์เซอร์จำสิทธิ์ให้แล้ว",
"prio": "กลาง",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "แก้แล้ว (deploy prod room-lobby.js?v=0.0313): เดิม overlay เป็น display:flex default จึงเด้งทุกครั้งที่โหลดหน้า. เพิ่มตรวจสิทธิ์ผ่าน navigator.permissions.query(microphone) + จำสถานะใน localStorage (justiceMicPromptHandled). แสดงเฉพาะครั้งแรกที่ยังไม่เคยตัดสินใจ (permission=prompt และยังไม่เคยกดอนุญาต/ปิด); ถ้า granted แล้ว/เคยจัดการแล้ว → ซ่อนทันที (มี inline script กันแฟลชก่อน paint). ปุ่ม 🎤 ขอสิทธิ์ไมค์ ในห้องยังใช้ขอใหม่ได้. ไฟล์: room-lobby.html (inline gate) + room-lobby.js (gateMicPermissionOverlay, hideMicPermissionOverlay).",
"cat": "LobbyA"
},
{
"n": 73,
"name": "คนออก LobbyB / ช่วงเปลี่ยนหน้า → ไม่เติมบอท + ค้างรอผู้เล่น เริ่มเกมต่อไม่ได้",
"detail": "คนหลุดระหว่างนั่งอยู่ LobbyB หรือช่วงเปลี่ยนหน้า minigame→LobbyB เดิมไม่ถูกเติมบอทแทน + ยังถูกนับใน roster คดี → host กดเริ่มสืบสวนรอบต่อไปไม่ได้ ค้าง รอผู้เล่น (x/y) ตลอดไป",
"prio": "วิกฤต",
"status": "แก้ไขแล้ว",
"updated": "2026-06-25",
"note": "ขยาย grace+bot-takeover gate ให้ครอบ LobbyB/คดีล็อก ไม่ใช่แค่ minigame active (server.js ~8921: inCaseFlowForBot = detectiveMinigameActive || serverMapIsPostCaseLobbyB || (joinLocked && caseParticipantNicknames.size>0)). คนออก→หลัง grace 10วิ ลบจาก roster + เติมบอทสีตามคนออก + presence ใหม่ → allHere=true เล่นต่อได้. + ส่ง botSlotCount ใน lobby-tint-sync (server.js ~121) + client อัปเดต lobbyBotSlotCount ก่อน render (room-lobby.js ~768) ให้บอทโผล่ทันทีไม่ต้อง re-join. v0.0323",
"cat": "ระบบ disconnect"
}
]
+2 -2
View File
@@ -162,10 +162,10 @@
"at": "2026-06-25 08:44"
},
"12": {
"result": "none",
"result": "pass",
"comment": "⚠️ ไม่สามารถ runtime test ได้ (canvas freeze CDP) — ต้องทดสอบ manual: MG3 ต่อตึก เล่นทีละคนตามคิว",
"images": [],
"at": "2026-06-25 08:45"
"at": "2026-06-25 16:35"
},
"36": {
"result": "pass",