Files
Gacy-Srtool/docs/FIREFLY_ENDGAME.md
T
CAtlas0784 1593581729
Deploy GaC-Srtool to GitHub Pages / deploy (push) Canceled after 0s
FireflyGO api support
2026-11-04 17:14:50 +07:00

35 KiB
Raw Blame History

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

ปัญหาที่ตามมา:

  1. ต้องเพิ่มโหมด FireflyGO โดยไม่ทำให้โหมด GaCy เดิมพัง
  2. ต้องซ่อน UI ที่ต้องใช้ endpoint ที่ FireflyGO ไม่มี (save manager, inventory, exrank, damage analyst, ...)
  3. ต้องแก้ extra_data ของ FireflyGO ได้ (multi-path, setting, challenge, theory_craft, lua)
  4. ต้องนำเข้าข้อมูลจาก UID เกมจริง (Enka) ได้ทุกโหมด
  5. ต้องมีหน้า 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, modeSupports
  • js/app.js → applyModeDisplay() — ซ่อน/แสดง tab และ card ตาม capability
  • js/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 ของ id
  • monsters เป็น array ของ wave และแต่ละตัวเป็น object
  • amount ไม่บังคับ แต่เราเติม 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() แล้ว return
  • loadMonsterSettings() → ถ้าโหมด firefly เรียก loadFireflyEndgame() แล้ว return
  • setConnectionStatus() → โหลด Monster Setting ใหม่เฉพาะโหมด gac
  • applyTranslations() → เรียก renderMonsterSettings() ใหม่เมื่อสลับภาษา
  • updateDocumentTitle() → ใช้ ffEndgameTitle เมื่ออยู่โหมด firefly

3.9 Apply ทำอะไรบ้าง

applyFireflyEndgame({ sync }):

  1. buildFireflyBattleConfig() จาก selection ปัจจุบัน
  2. ถ้าไม่มีข้อมูลหรือเลือกไม่ครบ → แจ้ง error และไม่เขียนอะไร
  3. เขียน state.fullFreesrData.battle_config = config
  4. ถ้าโหมดเป็น peak:
    • extra.challenge = buildFireflyChallenge(selection, extra.challenge)
    • เก็บ state.fireflyExtraData + persistFireflyExtra()
    • รีเฟรชการ์ด extra_data
  5. ถ้า sync = false → saveFreesrCache() (IndexedDB + localStorage)
  6. ถ้า sync = true → saveFreesrChangesToServer() ซึ่งในโหมด firefly จะไปเรียก pushFireflySync() → POST /sync
  7. อัปเดตสถานะ + 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.js capability matrix ครบ 3 โหมด
  • fireflySyncUrl() แปลง base URL → /sync
  • fireflyRequest() ไม่ส่ง custom header, มี timeout, parse error body
  • probeFirefly() = 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.json rewrite)
  • 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

เมื่อมีเซิร์ฟเวอร์:

  1. ตั้ง API URL เป็น http://localhost:21000
  2. กด Connect → ต้องได้ extra_data กลับมา
  3. Sync → ตรวจไฟล์/สถานะฝั่งเซิร์ฟเวอร์
  4. ตรวจว่า 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. ข้อจำกัด / ข้อควรระวัง

  1. แคตตาล็อกเป็น snapshot: ถ้า Firefly SrTools อัปเดตข้อมูล ต้องรันสคริปต์ใหม่
  2. ไอคอนมอนเป็น CDN ภายนอก (cdn.punklorde.org) — ถ้า CDN ล่มจะเห็นอิโมจิ fallback
  3. Simulated Universe ไม่อยู่ในขอบเขต — ต้นฉบับมี แต่รอบนี้ยังไม่ทำ
  4. amount ถูกเซ็ตเป็น 1 เสมอ สำหรับห้องปกติ (custom แก้ได้)
  5. extra_data.challenge อัปเดตเฉพาะโหมด peak — โหมดอื่นไม่แตะค่าเดิม
  6. เขียนทับ battle_config ใน local cache: ถ้าสลับกลับไปโหมด GaCy แล้วใช้ custom wave editor ค่า amount จะไม่ถูกอ่าน (GaCy ใช้ max_hp) — แต่ GaCy จะโหลด catalog จากเซิร์ฟเวอร์ใหม่เมื่อเชื่อมต่อ
  7. normalizeBattleConfig() ตัด key ที่ไม่รู้จัก รวมถึง custom_battle_lineup
  8. ห้ามใส่ 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 /sync happy path กับ FireflyGO จริง (ต้องมีเซิร์ฟเวอร์)