35 KiB
FireflyGO Endgame Room Picker — สถานะงานและคู่มือเทคนิค
อัปเดต: 2026-11-04 ขอบเขต: งานเพิ่มโหมดเชื่อมต่อ FireflyGO (Firefly_Srtools) ใน GaCy-Srtool และหน้าตั้งค่าห้อง Endgame ให้เหมือนต้นฉบับ
https://srtools.punklorde.org
1. สรุปสั้น
โจทย์
เว็บ GaCy-Srtool เดิมออกแบบมาสำหรับเซิร์ฟเวอร์ GaCy GO ซึ่งมี REST API ครบ (/api/status, /api/freesr, /api/persistent, /api/monster-settings, ...) แต่ผู้ใช้ต้องการเชื่อมกับ FireflyGO (Firefly_Srtools) ซึ่งเป็นแบบ push-only คือมีแค่ POST <base>/sync
ปัญหาที่ตามมา:
- ต้องเพิ่มโหมด FireflyGO โดยไม่ทำให้โหมด GaCy เดิมพัง
- ต้องซ่อน UI ที่ต้องใช้ endpoint ที่ FireflyGO ไม่มี (save manager, inventory, exrank, damage analyst, ...)
- ต้องแก้
extra_dataของ FireflyGO ได้ (multi-path, setting, challenge, theory_craft, lua) - ต้องนำเข้าข้อมูลจาก UID เกมจริง (Enka) ได้ทุกโหมด
- ต้องมีหน้า Monster Setting / เลือกห้อง Endgame แบบต้นฉบับ Firefly (MoC, PF, AS, Anomaly Arbitration, Custom Enemy)
สถานะปัจจุบัน (สรุป)
| ส่วน | สถานะ |
|---|---|
โหมด FireflyGO + POST /sync |
✅ เสร็จและ commit แล้ว (8eb5190) |
extra_data form |
✅ เสร็จ |
| Enka UID → FreeSR import | ✅ เสร็จ |
| ซ่อน/แสดง UI ตามโหมด | ✅ เสร็จ |
| แคตตาล็อกห้อง Endgame (moc/pf/as/peak) | ✅ สร้างเสร็จ (assets/firefly-endgame.json) |
ตัวช่วยคำนวณ battle_config (js/endgame.js) |
✅ เสร็จ |
| UI หน้า Monster Setting โหมด Firefly | ✅ เขียนเสร็จ (เรียง season/floor ใหม่สุดก่อนแล้ว) |
| ปรับปรุง Auto-init FreeSR เมื่อ Apply ห้อง | ✅ เสร็จ (ไม่ติดบล็อก ffEndgameNoData) |
ดักจับ player data is not loaded yet & Mixed Content |
✅ เสร็จ (มีข้อความแจ้งเตือนชัดเจน) |
| Unit tests | ✅ ผ่าน 44/44 |
| ทดสอบมือถือ 360–768px ของหน้าใหม่ | ✅ ผ่าน (responsive styles พร้อม) |
ทดสอบ POST /sync กับ FireflyGO จริง |
⏳ รอเปิดเซิร์ฟเวอร์และตัวละครล็อกอินในเกม |
| Commit งานรอบนี้ | ⏳ พร้อม commit |
2. สถาปัตยกรรมโหมดเชื่อมต่อ
ระบบมี 3 โหมด เก็บใน localStorage['gac_srtool_mode'] และนิยามความสามารถไว้ที่ js/modes.js
| ความสามารถ | GaCy GO (gac) |
FireflyGO (firefly) |
RobinSR (robin) |
|---|---|---|---|
saveManager |
✅ | — | — |
exranks |
✅ | — | — |
inventory |
✅ | — | — |
monsterSettings (อ่าน catalog จาก Client) |
✅ | — | — |
fireflyEndgame (picker จากแคตตาล็อกในเครื่อง) |
— | ✅ | — |
damageAnalyst |
✅ | — | — |
sceneCard / gmConsole / battleSummary |
✅ | — | — |
toolSession |
✅ | — | — |
extraData |
— | ✅ | — |
ownedAvatarsOnly |
— | ✅ | — |
jsonModes |
freesr, persistent |
freesr |
freesr |
ไฟล์ที่เกี่ยวข้อง:
js/modes.js— capability matrix,normalizeMode,modeSupportsjs/app.js→applyModeDisplay()— ซ่อน/แสดง tab และ card ตาม capabilityjs/app.js→setupServerModes()— ฟัง event เปลี่ยนโหมด (ผูกครั้งเดียว)
กฎสำคัญ
- FireflyGO เป็น push-only: การ "เชื่อมต่อ" คือ
POST <base>/syncด้วย{ username:'', password:'' }แล้วอ่าน{ status, message, extra_data, warnings, stats } - การซิงค์คือ
POST <base>/syncด้วย{ username, password, data: FreeSRJson, extra_data } - ห้ามใส่ custom header เพราะ FireflyGO ไม่รองรับ CORS preflight
- สถานะการเชื่อมต่อของ FireflyGO เก็บใน
state.fireflyConnected(ไม่ใช้state.isConnectedร่วมกับ GaCy) refreshStatus()ในโหมด Firefly เป็น local-only (ไม่อ่าน/api/status)
3. FireflyGO Endgame Room Picker
3.1 ภาพรวมการทำงาน
sequenceDiagram
autonumber
actor User as ผู้ใช้
participant UI as app.js (Monster Setting)
participant Cat as assets/firefly-endgame.json
participant Save as state.fullFreesrData
participant Srv as FireflyGO /sync
User->>UI: เปิดแท็บ Monster Setting (โหมด FireflyGO)
UI->>Cat: fetch แคตตาล็อก (ครั้งเดียว)
Cat-->>UI: modes.{moc,pf,as,peak} + monsters
UI->>Save: อ่าน battle_config เดิม
UI->>UI: syncFireflyEndgameSelectionFromSave()
UI-->>User: แสดง season / floor / side / stage / preview
User->>UI: เลือกห้อง + กด "ใช้กับ battle_config"
UI->>UI: buildFireflyBattleConfig()
UI->>Save: เขียน battle_config
opt Anomaly Arbitration
UI->>UI: buildFireflyChallenge() → extra_data.challenge
end
UI->>UI: saveFreesrCache()
User->>UI: กด "ใช้และซิงค์"
UI->>Srv: POST /sync { data, extra_data }
Srv-->>UI: { status, warnings }
3.2 แหล่งข้อมูลแคตตาล็อก
ต้นฉบับ Firefly SrTools เสิร์ฟตารางดิบของ Client ที่:
https://srtools.punklorde.org/api/data/moc
https://srtools.punklorde.org/api/data/pf
https://srtools.punklorde.org/api/data/as
https://srtools.punklorde.org/api/data/peak
https://srtools.punklorde.org/api/data/monster
ไฟล์ดิบมีขนาด 2–14 MB จึงทำสคริปต์ย่อข้อมูล:
node scripts/build-firefly-endgame-data.cjs [cacheDir]
- ถ้ามี
cacheDirและมีไฟล์อยู่แล้วจะใช้แคช (ไม่ยิงเน็ตซ้ำ) - ผลลัพธ์:
assets/firefly-endgame.json(~604 KB) - ข้อมูล ณ วันที่สร้าง:
| โหมด | จำนวนกลุ่ม (season/round) |
|---|---|
moc (Memory of Chaos) |
58 |
pf (Pure Fiction) |
28 |
as (Apocalyptic Shadow) |
23 |
peak (Anomaly Arbitration) |
11 |
| มอนสเตอร์ (unique) | 355 |
โครงสร้างไฟล์:
{
"version": "2026-11-03",
"source": "https://srtools.punklorde.org/api/data",
"modes": {
"moc": { "groups": [ { "id": 1037, "name": {"en":"..."}, "begin": "...", "end": "...",
"levels": [ { "id": 5612, "floor": 12, "turnLimit": 30,
"buffs": [ { "id": 1001, "name": {"en":"..."} } ],
"sides": [ { "side": "firstNode", "stageId": 30127111, "level": 92,
"name": {"en":"..."}, "waves": [[4033010, 4033020],[4044010]],
"invasionBuffId": 3034002 } ] } ] } ] },
"pf": { "groups": [] },
"as": { "groups": [] },
"peak": { "groups": [ { "id": 9, "name": {"en":"..."},
"stages": [ { "kind": "pre|boss|hard", "stageId": 4001, "level": 80,
"name": {"en":"..."}, "waves": [[...]], "buffs": [...], "turnLimit": 30 } ] } ] }
},
"monsters": {
"4033010": { "name": {"en":"...","th":"..."}, "rank": 1, "stance": 1,
"weak": ["Fire"], "icon": "https://cdn.punklorde.org/asbres/spriteoutput/mostericon/monster_4033010.webp" }
}
}
หมายเหตุ: path ไอคอนของข้อมูลต้นทางสะกดว่า
mostericon(ตกn) จึงต้องใช้ตามนั้น
3.3 กติกาการแปลงข้อมูลจากต้นฉบับ
| โหมด | แหล่งห้อง | กติกา |
|---|---|---|
| MoC | Level[].EventList1[0] (ครึ่งแรก), EventList2[0] (ครึ่งหลัง), Tierce.EventList[0] (โหนดที่สาม) |
stage_id = stage.ID, cycle_count = Level.TurnLimit, blessings = Level.MazeBuff + Invasion.MazeBuff.ID |
| PF | เหมือน MoC | เหมือน MoC |
| AS | เหมือน MoC | เหมือน MoC |
Anomaly Arbitration (peak) |
กลุ่ม peak[event_id] → เลือก stage จาก [...PreLevel, BossLevel] ตาม index |
ถ้า boss_mode === 'Hard' ใช้ BossConfig; stage_id = EventList[0].ID; ต้องส่ง extra_data.challenge.challenge_peak_group_id = group id |
Custom Enemy (custom/CE) |
ผู้ใช้กรอกเอง | stage_id = 0, ใช้ monsters ที่กรอก |
3.4 Schema ที่ FireflyGO ตรวจ (zod)
จากการแกะ bundle ต้นฉบับ:
battle_config = {
battle_type: string, // MOC | PF | AS | CE | PEAK | DEFAULT
blessings: { id: number, level: number, dynamic_key?: { key: any, value: number } }[],
custom_stats: any[],
cycle_count: number,
stage_id: number,
path_resonance_id?: number, // default 0
monsters: { monster_id: number, level: number, amount?: number }[][]
}
จุดที่พลาดง่าย:
blessingsต้องเป็น object{id, level}ไม่ใช่ array ของ idmonstersเป็น array ของ wave และแต่ละตัวเป็น objectamountไม่บังคับ แต่เราเติม1ให้เสมอเพื่อความชัดเจน
js/firefly.js → normalizeBattleConfig() จึงแปลงค่าดิบให้เป็น shape นี้ก่อนส่งทุกครั้ง (รองรับข้อมูลเก่าที่เป็น id ล้วนด้วย)
3.5 ตัวช่วยคำนวณ (js/endgame.js)
ฟังก์ชันทั้งหมดเป็น pure function (ทดสอบด้วย node:test ได้):
| ฟังก์ชัน | หน้าที่ |
|---|---|
battleTypeForMode(mode) |
แปลง moc/pf/as/peak/custom → MOC/PF/AS/PEAK/CE |
groupList(catalog, mode) |
คืนกลุ่มของโหมด |
findGroup / findLevel |
ค้นหากลุ่ม/ชั้น |
defaultEndgameSelection(catalog, mode) |
ค่าเริ่มต้น: ซีซันใหม่สุด + ชั้นสูงสุด + ฝั่งแรก; peak = stage บอส |
resolveEndgameSelection(catalog, selection) |
คืน {group, level, sides, stage} หรือ null |
findSelectionByStageId(catalog, mode, stageId, groupId) |
เปิด picker ที่ห้องเดียวกับ battle_config เดิม |
buildFireflyBattleConfig(catalog, selection) |
สร้าง battle_config พร้อมส่ง |
buildFireflyChallenge(selection, current) |
อัปเดต extra_data.challenge (เฉพาะ peak) |
summarizeEndgameSelection |
สรุปจำนวน wave/มอน/บัฟ/รอบ ให้ UI |
monsterInfo(catalog, id) |
ข้อมูลมอน (ชื่อ/ไอคอน) |
3.6 State ของ UI
เพิ่มใน state.fireflyEndgame (js/app.js):
{
activeMode: 'moc', // moc | pf | as | peak | custom
catalog: null, // แคตตาล็อกที่โหลดแล้ว
catalogState: 'idle', // idle | loading | ready | error
catalogError: '',
catalogPromise: null, // กัน fetch ซ้ำ
selections: {}, // แยก selection ตามโหมด
statusKey: '', statusVars: null, statusState: 'info'
}
selections เก็บ object ที่ผูกกับ UI โดยตรง เช่น
// moc/pf/as
{ mode:'moc', groupId:1037, levelId:5612, side:'firstNode', useCycle:true, useBuff:true }
// peak
{ mode:'peak', groupId:9, stageIndex:1, useCycle:true, useBuff:true }
// custom
{ mode:'custom', customWaves:[[{monster_id:'4033010', level:'90', amount:'1'}]], customCycle:0 }
3.7 UI ที่เพิ่มใน index.html
#tab-monster-settings ถูกแบ่งเป็น 2 ส่วน:
<section id="tab-monster-settings" class="tab-content">
<div id="monster-settings-gac"> ... ของเดิมทั้งหมด ... </div>
<div id="monster-settings-firefly" hidden>
<div class="card monster-settings-intro"> ... คำอธิบาย ... </div>
<div class="monster-settings-tabs" id="ff-endgame-tabs">
<button data-ff-endgame-mode="moc">Memory of Chaos</button>
<button data-ff-endgame-mode="pf">Pure Fiction</button>
<button data-ff-endgame-mode="as">Apocalyptic Shadow</button>
<button data-ff-endgame-mode="peak">Anomaly Arbitration</button>
<button data-ff-endgame-mode="custom">Custom Enemy</button>
</div>
<div id="ff-endgame-status" class="monster-settings-status"></div>
<div id="ff-endgame-view" class="monster-settings-view"></div>
<div class="ff-endgame-actions">
<button id="ff-endgame-reset">รีเซ็ตค่าเริ่มต้น</button>
<button id="ff-endgame-apply">ใช้กับ battle_config</button>
<button id="ff-endgame-apply-sync">ใช้และซิงค์</button>
</div>
</div>
</section>
การแสดงผล:
applyModeDisplay()ตั้งhiddenให้#monster-settings-gac/#monster-settings-fireflyตามโหมด- แท็บ Monster Setting แสดงเมื่อ
caps.monsterSettings || caps.fireflyEndgame - ถ้าแท็บถูกซ่อนอยู่และโหมดเปลี่ยน ระบบจะพากลับไป Dashboard อัตโนมัติ
3.8 ฟังก์ชันใน js/app.js
| ฟังก์ชัน | หน้าที่ |
|---|---|
loadFireflyEndgame() |
โหลด snapshot (memory → IndexedDB → localStorage) แล้วโหลดแคตตาล็อก |
ensureFireflyEndgameCatalog() |
fetch แคตตาล็อกครั้งเดียว + seed ค่าเริ่มต้นทุกโหมด |
syncFireflyEndgameSelectionFromSave() |
อ่าน battle_config เดิมมาเลือกห้องให้ตรง |
renderFireflyEndgame() |
entry point ของการวาด UI |
renderFireflyRoomEditor() |
วาด season/floor/side หรือ stage cards + preview |
renderFireflyCustomEditor() |
วาด editor ของ Custom Enemy |
renderFireflyEndgamePreview() |
แสดง battle_config ที่จะส่งจริง |
setupFireflyEndgameEvents() |
ผูก event ทั้งหมด (ผูกครั้งเดียวตอน init) |
applyFireflyEndgame({sync}) |
เขียน battle_config (+challenge) แล้วบันทึก/ซิงค์ |
resetFireflyEndgame() |
รีเซ็ตโหมดปัจจุบันเป็นค่าเริ่มต้น |
updateFireflyEndgameControls() |
เปิด/ปิดปุ่มตามความพร้อม |
setFireflyEndgameStatus() |
ข้อความสถานะ (i18n) |
จุดเชื่อมกับระบบเดิม:
renderMonsterSettings()→ ถ้าโหมด firefly เรียกrenderFireflyEndgame()แล้ว returnloadMonsterSettings()→ ถ้าโหมด firefly เรียกloadFireflyEndgame()แล้ว returnsetConnectionStatus()→ โหลด Monster Setting ใหม่เฉพาะโหมดgacapplyTranslations()→ เรียกrenderMonsterSettings()ใหม่เมื่อสลับภาษาupdateDocumentTitle()→ ใช้ffEndgameTitleเมื่ออยู่โหมด firefly
3.9 Apply ทำอะไรบ้าง
applyFireflyEndgame({ sync }):
buildFireflyBattleConfig()จาก selection ปัจจุบัน- ถ้าไม่มีข้อมูลหรือเลือกไม่ครบ → แจ้ง error และไม่เขียนอะไร
- เขียน
state.fullFreesrData.battle_config = config - ถ้าโหมดเป็น
peak:extra.challenge = buildFireflyChallenge(selection, extra.challenge)- เก็บ
state.fireflyExtraData+persistFireflyExtra() - รีเฟรชการ์ด extra_data
- ถ้า
sync = false→saveFreesrCache()(IndexedDB + localStorage) - ถ้า
sync = true→saveFreesrChangesToServer()ซึ่งในโหมด firefly จะไปเรียกpushFireflySync()→POST /sync - อัปเดตสถานะ + toast
pushFireflySync()จะบังคับextra_data.theory_craft.modeจากstate.creativeModeเสมอ เพื่อให้ Creative Mode กับ theory craft เป็นสวิตช์เดียวกัน
3.10 i18n
- เพิ่มคีย์
ffEndgame*46 คีย์ ในMODAL_AND_TOOL_TRANSLATIONSทั้งthและen - ภาษาอื่น fallback เป็นอังกฤษผ่าน
t()ตามกติกาเดิม - ข้อความที่วาดจาก JS ทุกจุดใช้
t(...)ไม่มีข้อความ hardcode applyTranslations()รีเรนเดอร์หน้า Monster Setting ทำให้ชื่อห้อง/มอนเปลี่ยนตามภาษา
ตัวอย่างคีย์:
ffEndgameTitle, ffEndgameDescription, ffEndgameRule, ffEndgameModePeak, ffEndgameSeason, ffEndgameFloor, ffEndgameSideFirst/Second/Third, ffEndgameStageKindPre/Boss/Hard, ffEndgameUseCycle, ffEndgameUseBuff, ffEndgameApply, ffEndgameApplySync, ffEndgameReset, ffEndgameSaved, ffEndgameSynced, ffEndgameSyncError, ffEndgameMonsterId/Level/Amount, ...
3.11 CSS
เพิ่มบล็อก .ff-endgame-* ใน css/styles.css:
.ff-endgame-picker— grid 2 คอลัมน์อัตโนมัติ.ff-endgame-sides/.ff-endgame-side— ปุ่มเลือกฝั่ง.ff-endgame-toggles— สวิตช์ TurnLimit / Buff.ff-endgame-preview— preview การ์ด.ff-endgame-custom-row— แถวกรอกมอน (ยุบเป็น 2 คอลัมน์ที่ ≤640px).ff-endgame-actions— ปุ่มล่าง (เต็มความกว้างบนมือถือ)
ค่าเป้าหมายมือถือ: ปุ่มสูง ≥44–46px, ไม่มี horizontal scroll ระดับหน้า
3.12 Cache busting (สำคัญ)
เปลี่ยนไฟล์แล้วต้อง bump ?v=:
| ไฟล์ | เวอร์ชันใหม่ |
|---|---|
css/styles.css |
2.36 |
js/app.js |
3.32 |
js/i18n.js |
2.26 (ทุก importer ต้องตรงกัน) |
js/endgame.js |
1 (ไฟล์ใหม่) |
Importer ของ i18n.js ที่ถูกแก้: index.html (ทาง app.js), js/app.js, js/avatars-data.js, js/equipment-data.js, js/item-catalog.js, js/unstuck.js
4. ไฟล์ที่เพิ่ม / แก้ในรอบนี้
เพิ่มใหม่
| ไฟล์ | รายละเอียด |
|---|---|
js/endgame.js |
pure helpers สำหรับเลือกห้อง + สร้าง battle_config |
scripts/build-firefly-endgame-data.cjs |
สร้างแคตตาล็อกจาก API ต้นฉบับ |
assets/firefly-endgame.json |
แคตตาล็อกพร้อมใช้ (~604 KB) |
tests/endgame.test.cjs |
unit tests ของ endgame helpers (8 tests) |
แก้ไข
| ไฟล์ | สิ่งที่แก้ |
|---|---|
index.html |
แบ่ง #monster-settings-gac / #monster-settings-firefly, เพิ่ม UI picker, bump ?v= |
js/app.js |
import endgame, state ใหม่, ฟังก์ชัน picker ทั้งหมด, branch ตามโหมด, เปลี่ยน title |
js/modes.js |
เพิ่ม capability fireflyEndgame |
js/firefly.js |
normalize blessings / monsters ให้ตรง zod |
js/i18n.js |
เพิ่ม 46 คีย์ th/en, bump version |
css/styles.css |
สไตล์ .ff-endgame-* + responsive |
js/avatars-data.js, js/equipment-data.js, js/item-catalog.js, js/unstuck.js |
bump i18n.js?v=2.26 (และ bump version ของ module ปลายทางใน app.js) |
tests/firefly-payload.test.cjs |
อัปเดต expected shape + เพิ่ม test normalize |
tests/mode-capabilities.test.cjs |
เพิ่ม fireflyEndgame, คีย์ i18n, DOM hooks |
5. สิ่งที่เสร็จแล้ว (ละเอียด)
5.1 โหมดเชื่อมต่อ + transport
js/modes.jscapability matrix ครบ 3 โหมดfireflySyncUrl()แปลง base URL →/syncfireflyRequest()ไม่ส่ง custom header, มี timeout, parse error bodyprobeFirefly()= connect probe (ไม่ส่งdata)pushFireflySync()= sync เต็มrefreshStatus()แยกพฤติกรรม firefly (local-only)- กัน
tool-session,pagehide,fetchLatestBattleReportในโหมดที่ไม่รองรับ
5.2 extra_data
normalizeExtraData()ตรวจ/เติมทุกบล็อก- ฟอร์มใน
#card-firefly-extras(multi_path, setting, challenge, theory_craft, lua) readFireflyExtrasForm()/renderFireflyExtras()- Creative Mode =
theory_craft.modeใช้ state เดียว (state.creativeMode) - ทุกจุดที่เปลี่ยน Creative Mode วิ่งผ่าน
setCreativeMode() - factory reset ล้าง
state.fireflyExtraData
5.3 Enka import
js/enka-import.jsแปลง Enka → FreeSR (avatars, lightcones, relics)- UI ในแท็บ JSON ใช้ได้ทุกโหมด
- proxy
/api/enka/:uid(dev server +vercel.jsonrewrite) - paste JSON สำรองเมื่อ proxy ใช้ไม่ได้
5.4 UI ตามโหมด
- ซ่อน tab/card ที่ backend ไม่รองรับ
- ไม่ค้างอยู่แท็บที่ถูกซ่อน
- JSON editor จำกัด mode ตาม backend
- badge persistence / API URL hint เปลี่ยนตามโหมด
setupServerModes()ผูก event ครั้งเดียว
5.5 Firefly Endgame Picker
- ดึง logic แปลงข้อมูลจาก bundle ต้นฉบับครบทุกโหมด
- สร้างแคตตาล็อกแบบ static (ไม่ต้องพึ่ง API ต้นทางตอนใช้งาน)
- UI เลือก season / floor / side / stage
- UI Custom Enemy (wave + monster + level + amount)
- preview
battle_configก่อนใช้ - เปิด picker ที่ห้องเดิมจาก
battle_config - Apply → เขียน
battle_config(ทดสอบครบ MOC/PF/AS/PEAK/CE) - Peak → อัปเดต
extra_data.challenge - Apply & sync →
POST /sync(error path ทดสอบแล้ว, happy path ติดที่ยังไม่มีเซิร์ฟเวอร์) - i18n th/en + รีเรนเดอร์เมื่อเปลี่ยนภาษา (แก้บั๊กสถานะค้างเป็น loading แล้ว)
- responsive CSS (ทดสอบจริง 360px / 768px)
- เรียง season/floor ใหม่สุด–สูงสุดขึ้นก่อน
5.6 Tests
node --test tests/*.test.cjs
ℹ tests 44
ℹ pass 44
ℹ fail 0
ครอบคลุม:
- โครงสร้าง payload
/sync(avatars/lightcones/relics/battle_config) - การ normalize
blessings/monsters(รวมdynamic_key) - capability matrix + i18n keys + DOM hooks
- endgame helpers: default/resolve/build/challenge/find-by-stage/custom
6. สิ่งที่ยังเหลือ (ละเอียด + วิธีทำ)
6.1 ✅ ทดสอบ Apply / Apply & sync ในเบราว์เซอร์
สถานะ: ทดสอบครบทุกโหมดแล้วเมื่อ 2026-11-04 ที่ http://localhost:5173 โดยโหลด FreeSR JSON ขั้นต่ำผ่านแท็บ JSON ก่อน
ผลทดสอบจริง:
- MoC →
battle_type: "MOC",stage_id: 30127121,cycle_count: 30,monstersเป็น array ของ array ของ{monster_id, level, amount} - PF →
battle_type: "PF",stage_id: 30326041,cycle_count: 4,blessings: 1, 3 waves - AS →
battle_type: "AS",stage_id: 420564,blessings: 2, 1 wave - Anomaly Arbitration →
battle_type: "PEAK",stage_id: 30511021,blessings: 3, และextra_data.challenge.challenge_peak_group_id = 11ถูกเขียนลงlocalStorage['gac_srtool_firefly_extra'] - Custom Enemy → เพิ่ม wave/มอนแล้ว Apply ได้
battle_type: "CE",stage_id: 0,cycle_countตามที่กรอก - รีโหลดหน้า → picker กลับมาที่ห้องเดิมจาก
syncFireflyEndgameSelectionFromSave(ทดสอบกับ Custom: กลับมาเป็น CE และ wave ตรงกับที่บันทึก) - Apply & sync ตอนไม่มี FireflyGo → สถานะเป็น
Sync failed: Failed to fetch(data-state = error) ไม่มี crash และ cache ถูกบันทึกก่อน sync - เปลี่ยนภาษาไทย→อังกฤษขณะอยู่หน้า picker → สถานะเปลี่ยนเป็น
battle_config saved locally(ไม่ค้างที่ข้อความ loading) หลังแก้บั๊กในข้อ 6.1.1
6.1.1 🐞 บั๊กที่พบและแก้แล้วระหว่าง audit
#ff-endgame-status มี data-i18n="ffEndgameLoading" ทำให้ applyTranslations() เขียนทับข้อความสถานะเป็น "กำลังโหลด…" ทุกครั้งที่เปลี่ยนภาษา แต่ renderFireflyEndgame() ไม่คืนค่าสถานะที่เก็บไว้
แก้โดย: เพิ่ม applyFireflyEndgameStatus() ที่อ่าน state.fireflyEndgame.statusKey/statusState/statusVars แล้วเรียกที่ท้าย renderFireflyEndgame() ทั้งสอง branch (ทั้งตอน catalog ยังไม่โหลดและตอนเรนเดอร์ปกติ) — ตอนนี้สถานะถูกต้องหลังสลับภาษา
6.2 ✅ ทดสอบมือถือ 360–768px
สถานะ: ทดสอบด้วย iframe ขนาด 360×800 และ 768×1024 (media query ภายใน iframe ทำงานเหมือน viewport จริง)
ผลทดสอบจริง:
| ตัวชี้วัด | 360px | 768px |
|---|---|---|
documentElement.scrollWidth <= clientWidth |
345 = 345 (ไม่มี horizontal scroll) | 753 = 753 |
ความสูง tab #ff-endgame-tabs button |
46px | 46px |
| ความสูงปุ่ม Apply ทั้ง 3 | 46px | 46px |
ความสูง .ff-endgame-side |
56px | 56–71px |
| ความสูง select | 44px | — |
.ff-endgame-picker |
1 คอลัมน์ (319px) | 2 คอลัมน์ (340px + 340px) |
.ff-endgame-custom-row |
2 คอลัมน์ (138px + 138px) | — |
โหมด GaCy เดิมยังทำงาน: #monster-settings-gac แสดง, #monster-settings-firefly ถูกซ่อน, แท็บ save manager กลับมา, #card-firefly-extras ถูกซ่อน
6.3 🚫 ทดสอบ POST /sync กับ FireflyGO จริง
ติดเพราะ: ไม่มีเซิร์ฟเวอร์ Firefly_Srtools รันที่ localhost:21000
เมื่อมีเซิร์ฟเวอร์:
- ตั้ง API URL เป็น
http://localhost:21000 - กด Connect → ต้องได้
extra_dataกลับมา - Sync → ตรวจไฟล์/สถานะฝั่งเซิร์ฟเวอร์
- ตรวจว่า
battle_configที่ส่งผ่าน zod (ไม่มี warning เรื่อง schema)
6.4 ✅ เรียงลำดับ season และ floor ให้ใหม่สุด/สูงสุดอยู่บน
สถานะ: ดำเนินการเรียบร้อยแล้ว ใน renderFireflyRoomEditor() ทั้ง groups และ levels ถูกเรียงลำดับจากมาก→น้อย (descending) ทำให้ซีซันใหม่สุดและชั้น 12 อยู่ด้านบนสุดของ Dropdown สะดวกต่อการเลือกใช้งานทันที
6.5 ⏳ ตรวจ docs/ และ commit
docs/เป็น untracked ตามAGENTS.md— ไฟล์นี้ถูกสร้างตามคำขอของผู้ใช้- ยังไม่ commit งานรอบนี้
- ต้องตัดสินใจว่าจะ push ขึ้น
origin/test-gayหรือไม่ (ต้องถามก่อน)
6.6 ⏳ (อนาคต) อัปเดตแคตตาล็อกเมื่อเกมออก season ใหม่
node scripts/build-firefly-endgame-data.cjs
แล้ว bump ?v= ของ assets/firefly-endgame.json ถ้ามีการอ้างอิงในอนาคต (ปัจจุบัน fetch ตรงด้วย cache: 'force-cache')
7. ข้อจำกัด / ข้อควรระวัง
- แคตตาล็อกเป็น snapshot: ถ้า Firefly SrTools อัปเดตข้อมูล ต้องรันสคริปต์ใหม่
- ไอคอนมอนเป็น CDN ภายนอก (
cdn.punklorde.org) — ถ้า CDN ล่มจะเห็นอิโมจิ fallback - Simulated Universe ไม่อยู่ในขอบเขต — ต้นฉบับมี แต่รอบนี้ยังไม่ทำ
amountถูกเซ็ตเป็น 1 เสมอ สำหรับห้องปกติ (custom แก้ได้)extra_data.challengeอัปเดตเฉพาะโหมด peak — โหมดอื่นไม่แตะค่าเดิม- เขียนทับ
battle_configใน local cache: ถ้าสลับกลับไปโหมด GaCy แล้วใช้ custom wave editor ค่าamountจะไม่ถูกอ่าน (GaCy ใช้max_hp) — แต่ GaCy จะโหลด catalog จากเซิร์ฟเวอร์ใหม่เมื่อเชื่อมต่อ normalizeBattleConfig()ตัด key ที่ไม่รู้จัก รวมถึงcustom_battle_lineup- ห้ามใส่ header เพิ่มใน
/syncเพราะจะทำให้ preflight fail
8. คู่มือรัน/ตรวจสอบ
Unit tests
node --test tests/*.test.cjs
Dev server (มี Enka proxy)
npm run dev:proxy
# หรือ
node scripts/dev-server.cjs
เปิด http://localhost:5173
ถ้าเจอ
EADDRINUSEให้ปิด process ที่ถือพอร์ต 5173 ก่อน (Get-NetTCPConnection -LocalPort 5173)
ตรวจ payload ก่อนซิงค์
- เปิด DevTools → Network → กรอง
/sync - ดู Request Payload: ต้องมี
data.battle_config.blessingsเป็น object และmonstersเป็น array ของ array ของ object
ตรวจ i18n
- สลับภาษาไทย/อังกฤษ ขณะอยู่หน้า Monster Setting
- ชื่อ season, ชื่อมอน, สถานะ, ปุ่ม ต้องเปลี่ยนตามภาษา
- ภาษาที่ไม่มีการแปลจะ fallback เป็นอังกฤษ
9. Checklist ก่อน commit
node --test tests/*.test.cjsผ่านทั้งหมด- เปิดเว็บโหมด firefly → Monster Setting แสดง picker
- Apply เขียน
battle_configถูกต้อง (MOC/PF/AS/PEAK/CE) - Apply & sync แสดง error ที่แปลแล้วเมื่อไม่มีเซิร์ฟเวอร์
- เปลี่ยนภาษาแล้วหน้า picker รีเรนเดอร์ และสถานะไม่ค้างที่ loading
- มือถือ 360px ไม่มี horizontal scroll และปุ่ม ≥44px
- โหมด GaCy เดิมยังทำงาน (Monster Setting เดิม, Aha's Help, custom waves)
?v=ถูก bump ครบ (styles.css 2.36,app.js 3.32,i18n.js 2.26ทุก importer)- ไม่มีข้อความ hardcode ใหม่ใน HTML/JS
- ไม่แก้
README.md - ทดสอบ
POST /synchappy path กับ FireflyGO จริง (ต้องมีเซิร์ฟเวอร์)