# GaC hkrpg GO เซิร์ฟเวอร์จำลอง Honkai: Star Rail ภาษา Go พอร์ตจาก `GaC-hkrpg-ps` และอ้างอิงการทำงานจาก [PearlSR](https://git.xeondev.com/HonkaiSlopRail/pearl-sr) ประกอบด้วย game server, SDK/dispatch server และ REST API สำหรับ GaC-Srtool โปรเจกต์อยู่ระหว่างพัฒนา โดยกำลังตรวจการใช้งานกับ Client **4.6.53 OS** ตารางคำสั่งและ handler ที่แก้ล่าสุดอ้างอิง PearlSR commit `005d2c12db4781071f4b5b68e8f364587886a710` รายละเอียดอยู่ใน [บันทึกอัปเดต protocol](docs/protocol-update.md) การตรงกันของ schema ไม่ได้ยืนยันว่าเกมทุกระบบทำงานครบ ## CW — กำลังพัฒนา กำลังทำ Currency Wars ให้ครบวงจร รายละเอียดและขอบเขตที่ยังไม่เสร็จอยู่ใน [บันทึก CW](docs/currency-wars-investigation.md) ขณะนี้เพิ่ม catalog จาก Client, ข้อมูลเริ่มต้น `GridFightGetData` และรายการกิจกรรมแล้ว ยังไม่ถือว่า CW เล่นได้ครบ และระบบเซฟรอบ/ร้าน/เส้นทาง/การต่อสู้/ผลจบ CW ยังอยู่ระหว่างทำ ทางเข้า CW ใช้ start anchor จาก MapEntrance และ RuntimeGroup ของ Client แทนการเลือกเสาวาร์ปแรกในแมพ ผู้ใช้ยืนยันว่าเข้าห้อง เห็นโฮโลแกรม และเปิด UI ค้างไว้ได้แล้ว ข้อมูลอยู่ใน `resources/CurrencyWarsCatalog.json` พร้อมที่มา ส่วนเริ่มรอบและเล่น CW ให้จบยังอยู่ระหว่างทำ Catalog มีตารางเส้นทางจาก Client ครบชุดที่จับได้ 493 แถว ตัวอ่านตรวจรหัสเส้นทาง/บท/ห้องซ้ำและเส้นทางฝึกสอนที่ constant อ้างถึง โดยไม่ใช้ NodeTemplateID แทน StageID ของ Battle การส่งผลเริ่มรอบยังต้องต่อข้อมูลห้อง ร้านค้า และสถานะผู้เล่น CW ให้ครบก่อน โครงสร้างเซฟ CW แยกตาม UID อยู่ใน `persistent.currency_wars` พร้อม revision ป้องกัน session เก่าเขียนทับ และตรวจ Client/assembly ก่อนคืนข้อมูล `GridFightGetData` จะอ่านเซฟเมื่อมี โดยไม่เขียนไฟล์ระหว่างอ่าน StartGamePlay บันทึกสถานะเริ่มรอบก่อนตอบกลับแล้ว พร้อม Resume/QuitLeave แต่ยังไม่ได้ยืนยันการเล่นต่อจากเซฟใน Client ส่วนร้านค้ามีโค้ดกองการ์ดและแผนซื้อ/รีเฟรชแล้ว โดยอ่านจำนวนการ์ดกับกฎพื้นฐานจาก catalog และยังต้องเชื่อมกับกระดาน สถานะรอบ และแพ็กเก็ตคำสั่งจริง เพิ่มสถานะเริ่มต้นที่จับจาก March7 PS สำหรับด่านแรกของ Standard Gambit/Overclock ใน `resources/CurrencyWarsInitialRuns.json` แล้ว แต่ยังไม่ได้ต่อคำสั่งเลือกประตู ร้านค้า และการต่อสู้ครบ สถานะเริ่มต้นใช้ตัวอย่างที่จับไว้ ไม่ใช่การสุ่มรอบใหม่ทั้งหมด โปรไฟล์เริ่ม CW ใช้นโยบาย `freeplay_unlocked` ใน resource ดังกล่าว ตามข้อมูล tutorial ของ March7 PS เพื่อไม่ให้ Client ส่งคำขอฝึกสอน Division 0 มายัง handler รอบปกติ นโยบายนี้ข้ามบทฝึกสอนเพื่อเข้า Freeplay โดยไม่แจกผลรางวัลบทฝึกสอน ผู้ใช้ยืนยันว่าเข้า Investment Environment และผ่านไปหน้า Preparation ได้แล้ว ผู้ใช้ยืนยันแล้วว่าเข้า CW ถึงหน้า Investment Environment ได้ เพิ่มการตอบ Confirm จาก `GridFightHandleBonfireHealHpCsReq` ไปยัง `GridFightHandlePendingActionScRsp` พร้อม sync สถานะและบันทึกรอบก่อนตอบกลับ ขณะนี้ใช้ผลเริ่มต้นที่จับจาก reference สำหรับสามตัวเลือกของด่านแรกทั้งสองโหมดใน `resources/CurrencyWarsEntryTransitions.json` ยังเป็นผลเริ่มต้นคงที่ และยังไม่ได้ต่อคำสั่งรอบถัดไป/ร้าน/การต่อสู้ครบ ค่าเอฟเฟกต์บางอย่างของ reference ต่างจาก Client ปัจจุบัน จึงยังไม่ถือว่าเอฟเฟกต์หรือ CW เล่นครบแล้ว ### Unstuck จาก GaC srtool ปุ่ม **Unstuck** ใน GM Command Console ส่ง `POST /api/client/unstuck` ให้เกมเรียกฟังก์ชันปลดการบล็อกขณะรอแพ็กเก็ตใน heartbeat ถัดไป เว็บรอผลยืนยันจาก Client ผ่าน `/api/client/unstuck/result` ก่อนแสดงว่าสำเร็จ ใช้คำสั่ง `/unstuck` ใน console ได้ด้วย การกู้ UI นี้ไม่ลบเซฟ CW ไม่ย้ายแมพ และไม่สร้างผลสำเร็จปลอมให้คำสั่งเล่นเกม หากเซิร์ฟเวอร์หรือเกมไม่เชื่อมต่อจะยังปลดไม่ได้ และยังต้องแก้ handler ที่ทำให้ค้างด้วย ตัวอ่าน catalog รองรับตัวละครเริ่มต้นจากสมุด 60 รายการและค่าด่านจากโปรไฟล์ใหม่ 71 รายการ พร้อมตรวจราคาขายเริ่มต้นกับตารางดาว/ความหายาก ข้อมูลนี้ยังไม่ครอบคลุมตัวแปรทั้งหมดของร้าน และค่าด่านยังต้องคำนวณผลของ talents/modifiers เพิ่ม จึงยังไม่ใช่ระบบร้านค้าหรือผลรางวัล CW ที่เสร็จแล้ว ข้อมูลเริ่มต้นของช่วงคะแนน `CycleScoreRewardGetData` ใช้เวลาจบจาก `activity_schedule.end_time` เพื่อไม่ให้ตัวนับเวลาได้ค่าเริ่มต้นปี 1970 ขณะนี้ยังเป็นช่วงเปิดเล่นตาม config ระบบรีเซ็ตรายสัปดาห์ คะแนนสะสม และการรับรางวัลยังอยู่ระหว่างพัฒนา ช่วงเวลากิจกรรมใช้ `activity_schedule.begin_time` และ `activity_schedule.end_time` ใน `server.json` (Unix seconds) override ด้วย `server.local.json` หรือ `GAC_ACTIVITY_BEGIN_TIME` / `GAC_ACTIVITY_END_TIME` ได้ ต้องมีค่าทั้งคู่ โดยเริ่มไม่น้อยกว่า 0 และสิ้นสุดมากกว่าเริ่ม SDK, gateway และ launcher ตรวจค่าเดียวกัน ช่วงเวลานี้เป็นนโยบายเปิดเล่นของเซิร์ฟเวอร์ ไม่ใช่กำหนดการทางการของเกม ## เริ่มใช้งานบน Windows ### ข้อมูลตัวละครหายจนค้างหน้าโหลด หาก Player.log มี `TeamManager._CreateMemberEntity` / `IndexOutOfRangeException` ให้ตรวจว่า `freesr-data.json` ยังมี `avatars` และครบทุกรหัสในทีมปัจจุบัน การนำเข้าเฉพาะ relic/light cone ต้องไม่ลบส่วนตัวละครออก เซิร์ฟเวอร์จะปฏิเสธ roster ว่างและทีมที่อ้างถึงตัวละครที่ไม่มี พร้อมเก็บข้อมูล session เดิมไว้เมื่อ hot reload พบไฟล์ผิด `POST /api/freesr/data` รวมเฉพาะฟิลด์ที่ส่งมาและรวมรายการ avatar ตามรหัส ส่วน `PUT` ใช้แทนข้อมูลทั้งชุดและต้องมี `avatars`, `lightcones`, `relics`, `battle_config` ครบ การบันทึกตรวจ roster/ทีมก่อนเขียนแบบ atomic และไม่เขียน `persistent` กลับจากการอ่านข้อมูล ใช้ SDK และ Game server build ที่มีการป้องกันนี้ทั้งคู่ 1. ใช้ Windows กับ PowerShell และอินเทอร์เน็ตสำหรับ build ครั้งแรก Launcher ตรวจ Go ตามเวอร์ชันขั้นต่ำใน `go.mod`; ถ้าไม่มีหรือเก่าเกินไป จะดาวน์โหลด ZIP จากรายการรุ่นทางการ ตรวจ SHA-256 แล้วติดตั้งใน `launcher.go_install.directory` โดยไม่ต้องใช้สิทธิ์ผู้ดูแล ดู [การติดตั้ง Go](https://go.dev/doc/install) และ [รายการไฟล์ทางการ](https://go.dev/dl/?mode=json) 2. เตรียม Client, launcher/proxy และการ redirect dispatch ให้เชื่อมต่อเซิร์ฟเวอร์ที่กำหนดใน `server.json` 3. เก็บไฟล์ข้อมูลและโฟลเดอร์ `resources` ไว้ใน root ของโปรเจกต์ และตั้งค่า launcher ตามหัวข้อ config ด้านล่าง 4. เปิด `run.bat` แล้วเลือกเมนูที่ต้องการ | เมนู | การทำงาน | | --- | --- | | `1` หรือ Enter | Build ทั้งสองเซิร์ฟเวอร์ แล้วเปิดเซิร์ฟเวอร์และเกม | | `2` | เปิดทั้งสองเซิร์ฟเวอร์จาก `.exe` ที่มีอยู่ | | `3` | เปิดเฉพาะ SDK server | | `4` | เปิดเฉพาะ game server | | `5` | เปิดทั้งสองเซิร์ฟเวอร์จาก `.exe` ที่มีอยู่ และเรียก Gay Proxy | | `6` | Build ทั้งสอง `.exe` อย่างเดียว | | `7` | ออก | | `8` หรือ `stop` | หยุดเซิร์ฟเวอร์ของโปรเจกต์นี้ | เมนู 1 และ 6 หยุดเมื่อ build ไม่ผ่าน ส่วนเมนู 2–5 ใช้ binary ที่มีอยู่ หลังแก้ซอร์สจึงต้อง build ใหม่ก่อนใช้เมนูเหล่านั้น `run.bat` เรียก `run.ps1` ผ่าน PowerShell โดยอ่าน path เกม/Proxy จาก config ไม่มีการเดา Desktop, ไดรฟ์ หรือชื่อโฟลเดอร์ตามเวอร์ชัน ต้องตั้ง `launcher.client_path` เป็นไฟล์ launcher/เกมที่จะเปิด และ `launcher.proxy_path` เป็นไฟล์ Proxy ที่ต้องการ ถ้าเว้นว่างจะเปิดเซิร์ฟเวอร์และแสดงวิธีตั้งค่า SDK และ game server เปิดหน้าต่างคอนโซลแยกกันให้เห็น log โดยตรง หลังทำเมนูที่เลือกเสร็จ หน้าต่าง `run.bat` จะปิดเองใน 5 วินาที ส่วนหน้าต่างเซิร์ฟเวอร์ยังเปิดอยู่ ใช้ `run.bat stop` เพื่อหยุด หรือปิดหน้าต่างเซิร์ฟเวอร์ที่ต้องการ สคริปต์หยุดเฉพาะ process ที่ path executable ตรงกับ root นี้ ก่อนเริ่มใหม่หรือแทนที่ binary ที่ build สำเร็จ การเรียกด้วย argument เช่น `run.bat build` จะคืนค่าทันทีเมื่อทำงานเสร็จโดยไม่รอ 5 วินาที ## Config และกฎห้าม hardcode `AGENTS.md` กำหนดให้ค่าที่เปลี่ยนตามเครื่อง/deployment/เวอร์ชันต้องมาจาก config, environment หรือ resource ห้ามฝัง path, IP, port หรือ fallback เฉพาะเครื่องใน source และ startup scripts ลำดับความสำคัญ: **environment > local config > config หลัก** - `server.json`: ค่าเครือข่ายและเวอร์ชันที่แชร์กันระหว่าง launcher, game server และ SDK server - `server.local.json`: override เฉพาะเครื่อง ไฟล์นี้ถูก ignore โดย Git คัดลอก `server.local.example.json` แล้วใส่ path ของเครื่องได้ - `GAC_CONFIG`: เลือกไฟล์หลักอื่น โดย local override ใช้ชื่อเดียวกันลงท้าย `.local.json` - path แบบ relative ใน launcher อ้างอิง root ของโปรเจกต์ | ค่าใน config | Environment override | ใช้สำหรับ | | --- | --- | --- | | `account_name` | `GAC_ACCOUNT_NAME` | username ในคำตอบ SDK login และชื่อผู้เล่นในเกม ใช้ค่าเดียวกัน | | `player_uid` | `GAC_PLAYER_UID` | UID ผู้เล่น ใช้ร่วมกันทั้ง SDK และ game server; ต้องเป็นจำนวนเต็ม 1–4294967295 | | `client_version` | `GAC_CLIENT_VERSION` | เวอร์ชันที่แสดงใน log/API | | `game_host`, `game_port` | `GAC_GAME_HOST`, `GAC_GAME_PORT` | ที่อยู่ bind ของ game server | | `sdk_host`, `sdk_port` | `GAC_SDK_HOST`, `GAC_SDK_PORT` | ที่อยู่ bind ของ SDK server | | `public_game_host` | `GAC_PUBLIC_GAME_HOST` | ที่อยู่ game server ที่ gateway แจ้ง Client | | `public_sdk_url` | `GAC_PUBLIC_SDK_URL` | Base URL ที่ dispatch แจ้ง Client | | `battle_diagnostics.live_report_stale_seconds` | `GAC_LIVE_BATTLE_STALE_SECONDS` | อายุสูงสุดของ snapshot Live ก่อนกลับไปใช้รายงานที่บันทึกแล้ว | | `launcher.client_path` | `GAC_CLIENT_PATH` | ไฟล์ launcher/เกมที่จะเปิด | | `launcher.proxy_path` | `GAC_PROXY_PATH` | ไฟล์ Proxy ที่จะเปิด | | `launcher.go_command` | `GAC_GO_COMMAND` | คำสั่ง Go จาก PATH หรือ path ที่กำหนด | เปลี่ยน username และ UID ได้ที่เดียวใน `server.json`: `account_name` คือชื่อทั้งบัญชีและในเกม ส่วน `player_uid` คือ UID (ปัจจุบัน `52022`) ถ้าต้องการค่าเฉพาะเครื่อง ให้ใส่สองคีย์นี้ใน `server.local.json` แทน หลังแก้ให้รีสตาร์ตทั้ง SDK และ game server แล้วออกจากบัญชีและ login ใหม่เพื่ออัปเดตชื่อที่ Client cache ไว้ ไม่ต้องแก้ซอร์สหรือไฟล์เซฟเพื่อเปลี่ยนสองค่านี้ `run.bat requirements` เตรียม Go อย่างเดียว ส่วนเมนู build เรียกขั้นตอนนี้อัตโนมัติ การรัน executable ที่ build แล้วไม่ต้องมี Go ติดตั้งในเครื่อง ตั้ง `launcher.go_install.enabled=false` เพื่อใช้ Go ที่ติดตั้งเองเท่านั้น URL ของ manifest/ดาวน์โหลดและโฟลเดอร์ติดตั้งอยู่ใน `server.json` และ override ผ่านไฟล์ local ได้ ไม่มี path โปรแกรมเฉพาะเครื่องฝังใน launcher โฟลเดอร์ `.runtime/` และ receipt ของ toolchain ไม่ถูก commit เมื่อเปลี่ยนเครือข่ายให้ตั้งทั้ง bind และค่าที่ประกาศให้ Client เข้าถึงได้ โดยเฉพาะ port ใน `public_sdk_url` เมื่อ SDK รับการเชื่อมต่อโดยตรง ค่าเหล่านี้มีค่าเริ่มต้นอยู่ในไฟล์ config และไม่มี fallback เครือข่ายในโค้ด ต้องรีสตาร์ตเซิร์ฟเวอร์หลังเปลี่ยน config การเปลี่ยน `client_version` ไม่ได้แปลง protocol หรือทรัพยากรของเกมให้อัตโนมัติ ตรวจ config โดยไม่เปิดเกมหรือเซิร์ฟเวอร์: ```powershell .\run.bat check ``` ## มอนในฉากและการเชื่อมต่อ Srtool - เมื่อไม่ได้เชื่อมต่อ GaC-Srtool เซิร์ฟเวอร์จับคู่ EventID ของมอนในฉากกับ PlaneEvent ของ Client แล้วใช้ StageConfig ของด่านนั้น ทั้งตอนโจมตีมอนและตอนมอนโจมตีผู้เล่น ไม่มีการใช้รายการมอน Custom จากไฟล์เก่าเป็น fallback หากไม่มีข้อมูลตรงรุ่นจะคืน error พร้อม log - `resources/WorldBattleConfig.json` เก็บ PlaneEvent/Cocoon ที่อ่านจาก Client พร้อมเวอร์ชันและ hash ของ assembly; `world_level` เป็นระดับบัญชีที่ใช้งานร่วมกันใน login/ฉาก/Battle ตารางนี้ต้องส่งออกใหม่เมื่อเปลี่ยน Client หรือชุด resource - GaC-Srtool ส่ง `POST /api/tool-session` พร้อม `client: "GaC-Srtool"` และ `connected: true` แล้วต่ออายุตาม `srtool.lease_seconds` จึงเปิดใช้ Custom monsters ได้ การปิดเว็บ/ขาดการเชื่อมต่อทำให้ lease หมดอายุ โดยเก็บไฟล์ชั่วคราวตาม `srtool.lease_file` ต้องใช้เว็บฉบับที่รองรับการต่ออายุนี้และ reload เว็บหลังอัปเดต - Challenge ที่กำลังเล่นใช้ config ของโหมดและห้องนั้น มีลำดับเหนือ Custom monsters - Quick Cocoon ที่ส่งจำนวน wave เป็นศูนย์ตีความเป็นหนึ่งชุดด่านตามพฤติกรรม request ที่เก็บได้ ยังต้องตรวจผลใน Client สำหรับ Calyx แต่ละประเภทหลังปรับเส้นทางนี้ `resources/PropInteractionConfig.json` ระบุวัตถุทำลายได้จาก Prop ของ Client ส่วนก้อนฟื้น Technique MP ใช้นโยบายเพิ่ม 2 จาก reference server ที่ระบุในไฟล์และจำกัดไม่เกินเพดานทีม ระบบฟื้น HP/สุ่ม buff จากวัตถุยังรอข้อมูลกราฟของ Client จึงยังไม่อ้างว่ารองรับครบ Hide UI ใช้ปุ่ม Backslash เพื่อซ่อน HUD รวม CanvasGroup ย่อยใน Battle และเก็บชื่อเซิร์ฟเวอร์/UID ไว้ หน้าต่าง Eidolon ซ่อน controls โดยไม่ปิดกล้องหรือภาพประกอบ การเปลี่ยน Lua มีผลผ่าน heartbeat; ผลใน Battle และหน้าต่าง EX ต้องตรวจจาก Client อีกครั้ง `resources/PlayerCosmetics.json` ใช้รายการ PlayerOutfit และ AvatarSkin ที่อ่านจาก Client การปลดของแต่งผู้บุกเบิกส่งไอเท็มเป็นเจ้าของครั้งละหนึ่งชิ้นใน GetBag ส่วนชุดที่เลือกผ่าน SetPlayerOutfit เก็บใน `persistent.player_outfits` และส่งกลับเมื่อ login ใหม่ ค่า Outfit ID ที่สวมรับจาก selector ของ Client โดยตรงและ sync ตามโครงสร้าง PearlSR; ยังไม่ได้ export ตารางจับคู่ OutfitID/ItemID จึงไม่แปลงเลขด้วยสูตร ข้อมูลฉากที่ปรับรอบนี้จำกัดที่ Starfroster กับ Moment of Joy ใน `resources/SceneMetadata.json`: อัปเดตกลุ่ม/prop ที่ยังมีอยู่และเป็นของ Server พร้อม SavedValues จาก `RuntimeDimension.DefaultValue` จึงไม่ใช้ MaxValue เป็นค่าเริ่มต้นของสองแมพนี้ กลไกรถไฟเป็น prop ฝั่ง Client และมีตัวแปร FSV_Coaster ในตารางปัจจุบัน ผลการแสดงฉากและปุ่มรถไฟยังต้องยืนยันในเกม ส่วนแมพอื่นยังใช้ข้อมูลเดิม ไม่ได้อ้างว่าอัปเดตทั้ง resource แล้ว สองแมพนี้ยังส่ง GroupPropertyConfig ให้ GetSceneMapInfo และกลุ่มฉากของ Server โดยใช้ชื่อ/ค่าเริ่มต้น/ขอบเขตจาก Client ค่าที่เปลี่ยนเก็บใน `persistent.scene_group_properties` ข้อมูลที่เก็บครอบคลุม Main dimension ที่ระบุใน resource; handler จะปฏิเสธ dimension หรือ property ที่ยังไม่มีข้อมูล ข้อสันนิษฐานของ schema: field 15 ชนิด int32 ใน UpdateGroupPropertyCsReq ใช้เป็นค่าที่ต้องการตั้ง ส่วน response ยืนยันเฉพาะชื่อ/floor/group/dimension ตาม PearlSR โดยยังไม่ระบุความหมายของ field ค่าเก่า/ค่าใหม่ที่ obfuscated ต้องยืนยันผลจาก Client ต่อ เทคนิคฟันทิ้งมอนใช้รายการ avatar จาก reference PearlSR และ Rank ของ NPCMonsterData ที่ส่งออกจาก Client มอนระดับ Minion/MinionLv2 จึงข้าม Battle ได้ ส่วนบอส/Challenge และช่วงใช้ Custom encounter คงเส้นทาง Battle ของโหมดนั้น ไม่มีการตัดสินชนิดมอนด้วยช่วงเลข ID ## ทำไมมี executable สองตัว | ไฟล์ | หน้าที่ | พอร์ต | | --- | --- | --- | | `gameserver-go.exe` | รับการเชื่อมต่อเกมผ่าน KCP และจัดการผู้เล่น ฉาก และ battle | UDP ตาม `game_port` | | `sdkserver-go.exe` | Dispatch, login, ข้อมูล hotfix และ REST API | HTTP ตาม `sdk_port` | การเปิดระบบครบต้องรันทั้งสองตัว `.exe` ที่ build เสร็จจะอยู่ใน root ของโปรเจกต์ ให้เปิดจาก root เพื่อให้เซิร์ฟเวอร์หาไฟล์ข้อมูลและบันทึก log ได้ถูกตำแหน่ง ## Build และรันเอง รันคำสั่งจาก root ของโปรเจกต์: ```powershell go build -o gameserver-go.exe ./cmd/gameserver go build -o sdkserver-go.exe ./cmd/sdkserver ``` เปิดคนละ terminal: ```powershell .\sdkserver-go.exe ``` ```powershell .\gameserver-go.exe ``` ซอร์สมีไฟล์ protobuf ที่ generate แล้ว การ build ปกติไม่ต้องใช้ `protoc` หากแก้ schema จึงต้องติดตั้ง `protoc` และ `protoc-gen-go` เพื่อ generate ใหม่ สำหรับนักพัฒนาที่ต้องการรันชุดทดสอบ: ```powershell go test ./... ``` ผลทดสอบฝั่ง Go และการ build สำเร็จไม่ได้ยืนยันว่า Client โหลดฉากหรือเข้า battle สำเร็จ ต้องตรวจในเกมและอ่าน `Player.log` ประกอบด้วย ## ไฟล์ข้อมูล | ไฟล์หรือโฟลเดอร์ | ใช้สำหรับ | | --- | --- | | `freesr-data.json` | ตัวละคร, lightcone, relic และ `battle_config` | | `persistent` | ทีม, ตำแหน่ง, ฉาก และค่าผู้เล่นที่บันทึกไว้ | | `res.json` | ข้อมูลฉากหลักในรูปแบบที่ Go loader ใช้ | | `resources/` | ข้อมูลฉากและตารางประกอบ เช่น MapEntrance, MazePlane, Anchor และ StageConfig | | `teleports.json` | ตำแหน่ง teleport แยกตาม floor | | `challenge_data.json` | ข้อมูล challenge | | `mission_unlocks.json` | ข้อมูลภารกิจและการปลดล็อก | | `hotfix.json`, `versions.json` | ข้อมูลเวอร์ชันและทรัพยากร Client สำหรับ SDK server | | `scripts/` | Lua ที่ส่งให้ Client ผ่าน heartbeat | สำรอง `freesr-data.json` และ `persistent` ก่อนเปลี่ยนชุดข้อมูล `res.json` ใน root กับ `resources/res.json` มีโครงสร้างต่างกันและ loader ใช้ร่วมกัน จึงควรเก็บไว้ตามตำแหน่งเดิม **FreeSR custom battle ใช้ได้เฉพาะ Golden/Crimson Calyx** ตาม `CocoonType` จาก Client resource; Stagnant Shadow และ Cavern of Corrosion จะใช้ Stage/monster จาก Client ตามเวอร์ชันเสมอ ส่วน Echo of War ใช้ `StartRaid` ซึ่งยังไม่มี handler สร้างฉากและ BattleInfo ใน server ปัจจุบัน จึงยังต้องเติม Raid resource/handler แยก โดยห้ามอ่าน `battle_config` ของ FreeSR ## REST API / GaC-Srtool เมื่อ SDK server ทำงาน เปิด URL ที่ตั้งใน `public_sdk_url` เพื่อดูรายการ endpoint หน้า root และ `/dashboard` ตอบข้อมูล JSON ของ API gateway | Method | Endpoint | หน้าที่ | | --- | --- | --- | | GET | `/api/status` | อ่านสถานะและข้อมูลผู้เล่น | | GET, POST | `/api/freesr` | อ่านหรืออัปเดต FreeSR data | | GET, POST | `/api/inventory/materials` | อ่าน catalog Item ID ของ Client หรือแก้จำนวนไอเทมหลายรายการโดยไม่เขียนทับ FreeSR data ส่วนอื่น | | GET, POST | `/api/freesr/avatars` | อ่านหรืออัปเดตข้อมูลตัวละคร | | GET, POST | `/api/persistent` | อ่านหรืออัปเดตข้อมูล persistent | | GET, POST | `/api/battle_report` | อ่านสถิติดาเมจ/การต่อสู้ล่าสุด หรือบันทึกรายงานการต่อสู้ | | POST | `/api/battle_report/live` | รับ snapshot สถิติ protobuf ระหว่าง Battle จาก Client ที่ใช้ Lua collector | | POST | `/api/battle_report/reset` | ล้างสถิติรายงานการต่อสู้ | | POST | `/api/exrank/toggle` | สลับการปลดล็อก EX rank | | POST | `/api/avatar/rank` | เปลี่ยน rank ตัวละคร | | POST | `/api/avatar/max_all` | ปรับข้อมูลตัวละครตามการทำงานของ handler | | POST | `/api/teleport` | อัปเดตข้อมูลจุดหมาย teleport | | POST | `/api/command` | ส่งคำสั่ง GM ไปยังเซิร์ฟเวอร์ | | GET | `/api/data-in-game` | อ่านข้อมูลเกมที่แคชไว้ในเซิร์ฟเวอร์ | รูปแบบ request และเงื่อนไขการอัปเดตอยู่ใน `internal/sdkserver/api.go` การอัปเดตข้อมูลผ่าน API ยังต้องตรวจผลการโหลดข้อมูลของ Client ด้วย Damage Analyst รับ snapshot ระหว่างสู้จาก `BattleModule.LastBattleStatistic` ผ่าน Lua ใน `scripts/` แล้ว Srtool อ่าน snapshot ล่าสุดจาก `/api/battle_report` โดยอัตโนมัติ รายงานสุดท้ายยังมาจาก `PVEBattleResult`/`QuitBattle` ตามเดิม หากไม่มี snapshot ใหม่เกิน `battle_diagnostics.live_report_stale_seconds` จะกลับไปแสดงรายงานล่าสุดที่บันทึกไว้ ค่าเริ่มต้นอยู่ใน `server.json` และ override ได้ด้วย `server.local.json` หรือ `GAC_LIVE_BATTLE_STALE_SECONDS` การอัปเดตนี้ยังต้องยืนยันการทำงานจริงจาก Client ## สถานะและปัญหาที่กำลังตรวจ มี handler สำหรับ login, ทีม, ตัวละคร, ไอเทม, ฉาก, teleport, ภารกิจ, Calyx และ challenge แต่ความครบของ handler ไม่ได้หมายถึงรองรับทุกกรณีใน Client | รายการ | สถานะ | | --- | --- | | Error 2000 ตอน login | แก้ `GetCurBattleInfo` ให้ตอบสำเร็จพร้อมข้อมูลว่างเมื่อไม่มี battle; รอยืนยันในเกม | | Calyx | ผู้ใช้ยืนยันว่าเข้า Battle ได้แล้ว 17 ต.ค. หลังแก้เส้นทาง BattleInfo | | ตีมอนแล้วค้างหน้า Weakness | ผู้ใช้ยืนยัน Stagnant Shadow ที่เรียกมอนออกมาให้ตีเข้าสู้ได้แล้ว 17 ต.ค.; log มี `PVEBattleResult` ส่วนมอนทั่วไปยังไม่ได้ยืนยันแยก | | เปิดหน้าตัวละครแล้วค้าง | ผู้ใช้ยืนยันว่าหายหลังเปิดเกมใหม่; ไม่พบ PlayerBoard/Inventory error ใน log รอบใหม่ | | วาร์ปแมพใหม่ | ผู้ใช้ยืนยัน Starfroster/Ferry Relay Station และ Moment of Joy วาร์ปได้แล้ว 17 ต.ค.; กรองกลุ่มที่ Client ระบุ `IsDelete=true` ด้วย SceneMetadata จากตาราง Client | | Endgame | ตรวจข้อมูลทุกด่านที่ติดตั้ง 1,747 รายการห้อง/node บน 34 floors; เพิ่มจุดเกิดจริง, PF 4 cycles และ flow เปลี่ยนทีม/ห้อง/สรุปผลสำหรับด่านหลัก, Tierce และ Peak แล้ว รอผลใน Client ดู [ผล audit และข้อจำกัด](docs/endgame-audit.md) | | `SceneCastSkill` | ใช้รายการ monster entity ที่ server สร้างเพื่อตรวจเป้าหมายก่อนส่ง battle | ข้อมูลล่าสุดที่ตรวจในซอร์สและเอกสาร: **17 ตุลาคม 2026** ## เก็บข้อมูลเมื่อพบปัญหา | ไฟล์ | ข้อมูลที่ช่วยวิเคราะห์ | | --- | --- | | `gameserver.log` | คำขอและแพ็กเก็ตตอบกลับ | | `server_debug.log` | รายละเอียด battle, scene และ teleport | | `battle_last.json` | Payload battle ล่าสุดที่สร้างสำหรับ Calyx หรือการตีมอน | | `battle_last_response.bin`, `battle_last.bin` | ไบต์ response ที่ส่งจริง และ nested battle_info ที่ตัดจาก response นั้น | | `battle_last_capture.json`, `battle_diagnostics/` | CmdID, เวลา, SHA-256 และ snapshot ของแต่ละรอบ | | `Player.log` ของ Client | Exception และจุดที่ Client โหลดข้อมูลไม่สำเร็จ | ตำแหน่ง `Player.log` บน Windows สำหรับ Client ที่ใช้ในงานนี้: ```text %USERPROFILE%\AppData\LocalLow\Cognosphere\Star Rail\Player.log ``` เมื่อรายงานปัญหาให้ระบุเวอร์ชัน Client, เวลาที่เกิด, การกระทำก่อนค้าง และรหัส Cocoon/Entry/Teleport ถ้ามี พร้อม log ช่วงเดียวกันทั้ง server และ Client ข้อความ `sent packet` ยืนยันเพียงว่า server ส่งแพ็กเก็ตแล้ว ส่วนการโหลดสำเร็จต้องดูฝั่ง Client สำหรับการตรวจ battle ทีละส่วน ดู [บันทึกเทียบ upstream](docs/battle-investigation.md) ค่า `battle_diagnostics.client_battle_type` ใน local config เปิด probe ที่อ่าน payload ด้วย protobuf parser ของ Client และเขียน `[GAC_BATTLE_WIRE]` ลง Player.log ชื่อ class ต้องอ้างอิง stack trace ของ Client รุ่นนั้น ตั้งค่าว่างเพื่อปิด probe ประวัติรอบ 16 ต.ค. พบ main affix ผิดใน inventory ชุดเดิมและแก้ข้อมูลไปแล้ว ผลนั้นไม่ใช่หลักฐานว่า inventory ที่นำเข้าภายหลังหรือการเข้า Battle ปัจจุบันผ่าน ดูผลแต่ละรอบในบันทึกตรวจสอบ ### การแก้ Battle รอบ 17 ตุลาคม 2026 - `SceneCastSkill` อ่านทั้ง hit targets, assist IDs และ assist groups และตอบ `MonsterBattleType=TRIGGER_BATTLE` ตาม enum ของต้นฉบับ; ค่าเริ่มต้นศูนย์หมายถึง `NONE` - โหลดธาตุตัวละครจาก `resources/AvatarConfig.json` เพิ่มเติม เพราะ `avatarConfigs` ใน `res.json` อาจว่าง - กฎบัฟเฉพาะตัวละคร, global passive, entry attack และ invasion อยู่ใน `resources/BattleRules.json` พร้อม upstream revision ต้องนำไฟล์นี้ไปด้วยเมื่อแจก binary; ไม่มีการฝัง ID ของกฎเหล่านี้เป็น fallback ในตัว executable - Battle SP แปลงจากอัตราส่วนในข้อมูลที่นำเข้าเป็นสเกล 10000 และ HP มอนที่เป็นศูนย์คงศูนย์ตามรูปแบบ upstream - Invasion ใช้สถานะ challenge ที่โหลดจริง `battle_config.enable_invasion_support` เปิดบัฟ support เพิ่มได้ (ค่าเริ่มต้น false) - เลิกสร้าง Material ID ด้วยสูตร `130000 + AvatarID` ซึ่ง Client หาไม่พบ และไม่ตอบข้อมูลว่างให้โมดูล RogueTourn/ChenLing ที่ยังไม่รองรับ สถานะรอบนี้: build ผ่าน วาง binary ที่ root แล้ว ผู้ใช้ยืนยันทั้ง Calyx และ Stagnant Shadow เข้า Battle ได้ โดย log ของเส้นทางตีมอนเวลา 11:53:08 มี BattleInfo และเวลา 11:53:16 มี `PVEBattleResult` ผลนี้ยังไม่ใช่การรับรองโหมดต่อสู้ทุกประเภท แก้โปรไฟล์เพิ่มเติม: field 4 ของ GetPlayerBoardDataScRsp คือการ์ดส่วนตัว, field 8 คือรูปโปรไฟล์ และ field 9 คือตัวละครช่วยเหลือ โค้ดเดิมใส่ ID ผิดประเภทและไม่มี head frame/display data ปรับให้ใช้ `resources/AvatarPlayerIcon.json`, `resources/ProfileConfig.json` และทีมจริง พร้อมเติม ArchiveData ทั้งสอง response ผู้ใช้ยืนยันหน้าตัวละครหายค้างหลังเปิดเกมใหม่ การสลับ Path ของ Trailblazer อ่าน path/เพศจาก `persistent` ส่งข้อมูล avatar และทีมตามลำดับ protocol ที่อ้างอิง พร้อมบันทึกค่าที่เปลี่ยน โปรไฟล์เก็บค่ารูป, การ์ด, signature, ตัวละครแสดงผล/ช่วยเหลือ และมือถือ (theme, case, chat bubble) ไว้ใต้ `persistent.profile` และตรวจ ID กับ resource ของ Client ก่อนรับค่า จึงยังคงค่าที่เลือกหลังเปิดเกมใหม่ ## Eidolon และ EX: การแจกไอเท็มและซิงก์ `resources/EidolonConfig.json` กำหนดรายการแจกของ Jing Yuan, Dan Heng IL และ Acheron: | ตัวละคร | ก้อน E1–E6 | ก้อน EX | | --- | --- | --- | | Jing Yuan | 11204 | 131204 | | Dan Heng IL | 11213 | 131213 | | Acheron | 11308 | 131308 | ก้อนปกติแจกเท่ากับต้นทุน rank ที่ยังไม่ปลด (E0 ได้ 6 ชิ้น) อ้างอิง `AvatarRankConfig.UnlockCost` และ `ItemConfigAvatarRank` จาก [ชุดข้อมูล revision ที่บันทึกไว้](https://gitlab.com/Dimbreath/turnbasedgamedata/-/tree/312b4597691e0a29cf9828a15b592757dba86949/ExcelOutput) และเทียบ RankIDList กับ AvatarConfig ในโปรเจกต์ ก้อน EX ใช้สาม ID ที่ผู้ใช้ระบุ แจกหนึ่งชิ้นเมื่อ EX ปิดอยู่ ไม่สร้าง ID ด้วยสูตรจาก AvatarID ผู้ใช้ยืนยันรายการใหม่และการกดปลดใน Client แล้ว - `GetBag` ส่งรายการแจกเมื่อเข้าเกม ก้อนลดตาม rank/EX ที่บันทึกไว้ ไม่รีเซ็ต progression เมื่อเปิดเกมใหม่ - เมื่อนำเข้า FreeSR ระหว่างออนไลน์ การซิงก์เต็มส่งจำนวนวัสดุแบบเดียวกับ `GetBag` ด้วย การแก้เฉพาะตัวละครหรือวัสดุส่งเฉพาะจำนวนที่เปลี่ยน รวมค่า 0 เมื่อปลดครบ โดยยังเลื่อนการนำเข้าจนจบ Battle ตามเดิม - API นำเข้า `PUT /api/freesr/data` เก็บ `materials` เดิมไว้เมื่อไฟล์นำเข้าไม่ได้ส่งฟิลด์นี้มา ถ้าส่ง `materials` มาเองจะใช้ค่าที่ส่ง รวมจำนวน 0; ส่ง `{}` เพื่อล้าง override กลับไปใช้จำนวนจาก resource/progression กฎนี้ไม่ครอบคลุมโปรแกรมภายนอกที่เขียนทับไฟล์โดยตรง - ตามคำขอเก็บก้อนแม้ปลดครบแล้ว ให้ตั้งจำนวนแยกใน `materials` ผ่าน `/api/inventory/materials` โดย API รวม ID จาก `EidolonConfig.json` ด้วย จำนวนที่ตั้งไว้จะใช้แทนการแจกตาม rank/EX และไม่เปลี่ยน E6 หรือสถานะ EX ของตัวละคร - `GetBag` โหลด catalog จาก `resources/InventoryItemConfig.json`: Material/Usable/Mission 2,612 รายการ, Light Cone 170 ใบ และ Relic BASIC ที่ Client มองเห็นตาม ItemConfigRelic โดย join กับ RelicConfig และกลุ่ม affix ของแต่ละชิ้น ของ catalog ที่ไม่มีใน save จะเพิ่มเป็นชิ้นยังไม่สวมและบันทึกลง `freesr-data.json`; อุปกรณ์เดิมไม่ถูกลบ - RankUpAvatar เซฟ rank ลง freesr-data.json โดยแก้เฉพาะค่า rank และเก็บฟิลด์นำเข้าอื่นไว้ E1–E6 ไม่เปิด EX ให้เอง - EX ทั้งสามคนเริ่มปิดไว้ตามคำขอ `false` เก็บชัดเจนใน persistent; การเปิดจากในเกมบันทึกและส่ง AvatarSync/MaterialList เฉพาะส่วน - เกมอ่าน EX จาก persistent ทุกประมาณ 1 วินาที แล้วส่งข้อมูลตัวละครที่เปลี่ยน ใช้เส้นทางนี้ร่วมกับ API เดิมโดยไม่แก้ source เว็บ GaC srtool - ระหว่าง Battle เลื่อนการนำเข้า EX และ freesr-data ไปหลัง PVEBattleResult เพื่อไม่เปลี่ยนอุปกรณ์หรือข้อมูล actor กลางการต่อสู้ - โหลดข้อมูลไม่เซฟกลับอัตโนมัติอีกแล้ว SavePersistent รวมเฉพาะค่าที่เปลี่ยนจาก snapshot ของผู้เขียน รักษาฟิลด์อื่นที่ API แก้ พร้อม lock ข้าม process และเขียนไฟล์แบบ atomic ต้องใช้ SDK/Game server build ใหม่ทั้งคู่ สถานะรอบ progression: build ทั้งสองตัวผ่าน ผู้ใช้ยืนยันว่าก้อนขึ้น ปลด E1/EX ได้ และยังเข้า Calyx ได้ ตรวจไฟล์ระหว่างลองพบ Dan Heng IL rank=1 และ EX=true ต่อมาผู้ใช้เปิด EX ทั้งสามคนแล้ว จึงเก็บค่า true ตามการกดปลดจริง ผลนี้ไม่ได้รับรองว่าเว็บ GaC srtool ที่แก้แยกอยู่ได้ทดสอบ integration แล้ว รอบโหลดกระเป๋าเดิม 12:18 ก่อนเพิ่ม item catalog ส่ง GetBagScRsp ขนาด 56663 ไบต์ ไม่พบ TryCreateItemData/Inventory error ใน log ณ เวลาตรวจ หลังจากนั้นรับ RankUpAvatar เวลา 12:18:23 และคำขอ EX เวลา 12:18:28 สำเร็จ พร้อม PlayerSyncScNotify ขนาด 234/237 ไบต์; ขนาดและผล Client ของ catalog ใหม่ยังต้องตรวจในเกม ## โครงสร้างซอร์ส ```text cmd/gameserver/ จุดเริ่ม game server cmd/sdkserver/ จุดเริ่ม SDK/dispatch server internal/common/ โมเดลข้อมูล, resource loader และ protobuf converters internal/gameserver/ Gateway, session, dispatch และ game handlers internal/sdkserver/ HTTP dispatch, login และ REST API protocol/proto/ Schema, generated protobuf และตาราง CmdID pkg/kcp/ KCP transport resources/ ข้อมูลประกอบ scripts/ Client Lua ``` ## อัปเดต protocol และ handler ใช้ checkout ของ PearlSR ที่ตรวจ revision แล้วเป็นแหล่งข้อมูล ไม่แก้ generated Go ด้วยมือ: ```powershell python tools/sync_protocol_commands.py "" protoc --go_out=. --go_opt=paths=source_relative protocol/proto/StarRail.proto protocol/proto/current_handlers.proto python tools/audit_protocol_handlers.py "" ``` คำสั่ง sync อัปเดตตารางชื่อ/CmdID ส่วนการแก้ field ต้องตรวจจาก schema ต้นฉบับแล้วแก้ `.proto` ก่อน generate ใหม่ ตัว audit ตรวจ CmdID, คู่ request/response และชุดหมายเลข field ของ message ที่มีชื่อร่วมกัน ไม่ตรวจชนิด field, default values, asset หรือผลใน Client `ClientDownloadDataLuaVersion = 51` เป็นค่า version ของ payload ที่ upstream ปัจจุบันยังใช้ส่ง Lua จึงไม่เปลี่ยนเป็น `53` ตาม `client_version` โดยไม่มีหลักฐานรองรับ ส่วน Tutorial อ่าน ID จาก `resources/TutorialData.json` และ `resources/TutorialGuideGroup.json` หลังเปลี่ยน resource ให้รีสตาร์ต server ## ที่มา พอร์ตจาก `GaC-hkrpg-ps` ภาษา Rust โดยใช้แนวทางและข้อมูลประกอบจาก [HonkaiSlopRail/pearl-sr](https://git.xeondev.com/HonkaiSlopRail/pearl-sr) ภาษา Zig การแก้ใน Go ต้องตรวจเทียบทั้ง handler, payload และเวอร์ชัน Client ที่ใช้งานจริง ### Traces: หน้าตัวละครและ GaC-Srtool - `AvatarPathData.avatar_path_skill_tree.point_id` สำหรับหน้า Traces ใช้เลข `AnchorType` จาก `AvatarSkillTreeConfig.json` ส่วน `BattleAvatar.skilltree_list.point_id` ใช้ `PointID` เต็ม อ้างอิง PearlSR commit `005d2c12db4781071f4b5b68e8f364587886a710` ใน `avatar_mgr.createSkillTree` และ `battle_mgr.createBattleAvatar` - แปลง `data.skills` ในเซฟเป็นเลข Anchor เฉพาะตอนส่งข้อมูลหน้าตัวละคร ไม่เปลี่ยน ID ในเซฟหรือ Battle และไม่บังคับอัป Traces ให้เต็ม - GaC-Srtool บันทึก Traces ผ่านการส่ง `freesr-data` ทั้งชุด ไม่ใช่ `/api/freesr/avatars` ซึ่งรับเฉพาะข้อมูลทั่วไปของตัวละคร โค้ดเว็บที่แก้ในโปรเจกต์ GaC-srtool เพิ่มพารามิเตอร์ `closeModal = true` ให้ `saveAvatarDetails` เพื่อแก้ ReferenceError ก่อนถึงขั้นส่งเซฟ ต้องใช้เว็บฉบับที่มีการแก้นี้ด้วย - การแก้เซฟจากเว็บถูกนำเข้าเกมโดย watcher; ระหว่าง Battle จะรอจนจบก่อนส่ง sync การยืนยันผลหน้า Traces และการบันทึกจากเว็บยังต้องตรวจใน Client ### Technique points และอาหาร - `resources/TechniqueRules.json` เก็บค่า MP, cap ของทีม, ค่าใช้ Technique ตามตาราง `MazeSkill` ของ Client และอาหารที่ฟื้น MP โดยไม่มีผลลด HP มี revision ต้นทางและ hash ของ GameAssembly ที่อ่านข้อมูล - Phainon / Himeko Nova ใช้ cap 7 ตาม `lineup_mgr.buildLineup` ของ PearlSR revision ที่ระบุใน resource ตัวโหลดตรวจสมาชิกจาก **ค่า AvatarID ในช่องทีม** ไม่ใช่ key ของช่อง ไม่มีการบวก cap ทั้งสองตัวทับกันตาม upstream - แจกอาหารใน resource ชนิดละ `initial_food_count` หนึ่งครั้งเมื่อเปิดกระเป๋า บันทึกจำนวนคงเหลือใน `persistent.technique_food_stock` กินด้วย `UseItem` หรือกินอัตโนมัติเมื่อแต้มไม่พอใช้ Technique; ห้ามกินระหว่าง Battle - `persistent.auto_technique_food` เปิดโหมด auto; ค่าเริ่มต้นมาจาก `auto_food_default` ใน resource ปิด/เปิดได้ผ่าน `POST /api/technique` ด้วย `{"auto_food":false}` / `{"auto_food":true}` และอ่านสถานะด้วย `GET /api/technique` ใช้ SDK URL จาก config ตัวเฝ้าดูนำสวิตช์ใหม่เข้า session หลัง Battle โดยไม่ล้าง inventory - MP ที่ใช้แล้วเก็บใน session; เริ่มห้อง Challenge ใหม่คืนค่าเริ่มต้นตาม resource การเปลี่ยนตัวละคร/วาร์ปไม่เติม MP เต็มโดยอัตโนมัติ - Technique ที่ไม่เปิด Battle อ้างอิง `IgnoreBattle` และ `getBattleType` ของ PearlSR ตอบผล `NO_BATTLE` ให้มอนที่โดน ไม่บังคับเข้า Battle จากการแปะ debuff สถานะ: ผู้ใช้ยืนยัน **PF ปกติเข้าสู้และเปลี่ยนห้องได้** ก่อนรอบแก้ Technique/อาหาร ส่วนรอบนี้ยังต้องยืนยัน Traces, แต้ม, อาหาร/auto และ Starward ห้อง 3 จาก Client ห้ามถือว่า build ผ่านเป็นผลยืนยันการเล่น ### Starward: ทีมและคะแนนที่เซฟไว้ - ผู้ใช้ยืนยันว่า PF Starward เล่นจบครบ 3 ห้องได้แล้ว - เก็บทีมและบัฟรายห้องใน `persistent.tierce_progress` ตอนตั้งทีม/เริ่มเล่น อ่านกลับใน GetChallengeTierceData และเริ่มห้องที่เลือกด้วยทีมของห้องนั้น ไม่ใช้ทีมแรกแทนเมื่อเล่นแยกห้อง - บันทึกคะแนนจริงจาก PVEBattleResult แยกแต่ละ node พร้อมผลที่ดีที่สุด การลองห้องเดียวใช้ผลที่เซฟของห้องอื่นประกอบ; การเล่นครบชุดเริ่มคะแนนของรอบใหม่จากศูนย์ - `resources/ChallengeTargetConfig.json` เป็นข้อมูลเกณฑ์จาก Client: TOTAL_SCORE ใช้ตัดสินดาวสำหรับ PF/AS Starward พร้อมส่ง FinishedTargetList ทั้งหน้าสรุปและข้อมูลที่อ่านกลับ คะแนน 97,000 ได้เกณฑ์ PF 60,000 / 75,000 / 90,000 แต่ยังไม่ถึงเกณฑ์พิเศษ 99,000 - รอบนี้ยังไม่เพิ่มการแจก reward, การจัดอันดับออนไลน์ หรือการประเมิน target ประเภท ROUNDS_LEFT/DEAD_AVATAR ของ MoC ส่วนข้อมูลประวัติ Endgame ปกติ/Peak เดิมยังมีรายการสำเร็จรูปตามข้อจำกัดในเอกสาร audit - ต้องตรวจใน Client ว่ากลับมาแล้วทีมยังอยู่ เลือกเล่นห้องเดียวได้ และหน้าสรุปดาวตรงกับคะแนน Build ไม่ใช่การยืนยันผลเล่น ### วาร์ป MoC จากสมุดผ่านฉากรถไฟ แก้ GetPamSkinData ที่เคยส่งสกินปอมปอม ID0 ซึ่งไม่มีในตาราง Client และทำให้ NPC โหลดล้ม อ่าน default/รายการที่ปลดจาก ProfileConfig และตรวจ ConfigEntityPath กับ PamSkinConfig.json แทน ค่าต้นทางมี revision/assembly hash ตรวจสอบได้ ยังรอผู้ใช้ยืนยันการวาร์ปรอบแก้สกินนี้ ### Preset ทีมในเกม - เปิดไว้ 20 ช่องผ่าน `resources/TeamPresetConfig.json` (`preset_count` เป็นนโยบายจำนวนทีมที่ผู้ใช้ขอเพิ่ม; ขอบเขตสมาชิก 4 และชื่อ 14 ตัวอักษรอ้างอิง Client) - GetAllLineupData ส่งรายการครบพร้อม index ปัจจุบัน รองรับสลับทีม ตั้งชื่อ เพิ่ม/ถอด/สลับสมาชิก และ MarkPresetLineup สำหรับทีมโปรด/Quick Lineup - บันทึกใน `persistent.team_presets` และ `active_team_preset` เก็บ `lineups` เดิมเป็นสมาชิกทีมที่กำลังใช้อยู่เพื่อใช้กับ API เดิม ไม่เขียนไฟล์ตอนอ่านเพื่อสร้าง preset เปล่า - เริ่มใช้ด้วยทีมเดิมในช่องแรก ช่องอื่นยังว่างจนจัดสมาชิกครั้งแรก จากนั้นเลือกทีมที่เซฟไว้ได้โดยไม่ต้องจัดใหม่ทุกครั้ง ปฏิเสธการสลับไปทีมว่างและการแก้ทีมระหว่าง Battle - เมื่อมีการเซฟ preset การสู้ทั่วไป/Calyx ใช้สมาชิกทีมที่เลือก แต่คง stage/waves จากข้อมูลเดิม ห้อง Endgame ใช้ทีมประจำห้องของ ChallengeRun - Build ผ่านยังไม่ยืนยันว่า UI ของ Client แสดงครบ 20 ช่องและใช้งาน Quick Lineup ได้ทุกหน้า ต้องตรวจเพิ่มในเกม การจัดทีม Endgame โดย request ExtraLineupType ยังคงเป็นคนละเส้นทางกับการแก้ preset ทีมเดินโลก ### Freecam - `Tab`: เปิด/ปิด Freecam; ลูกศร: เลื่อนกล้อง; กดเมาส์ขวาค้างแล้วขยับเมาส์เพื่อหันกล้อง - `Page Up` / `Page Down`: ขึ้น/ลง; `+` / `-`: เพิ่ม/ลดความเร็ว ไม่ใช้ E/Q หรือ I/J/K/L คุมกล้อง - ขณะเปิด Freecam ใน Battle ซ่อนการรับคลิก UI ด้วย `InControlInputModule.allowMouseInput` และพักเฉพาะ mouse binding ซ้าย/ขวาของคำสั่งเกมในหน่วยความจำ คีย์บอร์ดยังกดสกิลได้ ส่วนกล้องอ่านเมาส์ผ่าน Unity Input โดยตรง - คืนค่าตอนปิด Freecam/ออก Battle หลังปล่อยปุ่มเมาส์ และก่อนปิด Client ไม่มีการลบ/เพิ่ม binding ผ่าน OnBindingsChanged ชื่อ field ตรวจจาก Client โดยตรง ผลป้องกันคลิกใน Battle ยังรอยืนยันการเล่นจริง - สคริปต์อัปเดตผ่าน heartbeat โดยไม่ต้อง build หรือรีสตาร์ตเซิร์ฟเวอร์ ### Hide UI `Backslash` ซ่อนเฉพาะ HUD ที่ระบุชื่อไว้ใน `scripts/hide_ui.lua` เช่น HUD เดินโลก/ต่อสู้ ตัวเลขลอยและ waypoint โดยไม่ปิด UIRoot, กล้อง UI หรือ Canvas ทั้งฉาก จึงเหลือหน้าตัวละคร หน้ารายละเอียด EX Eidolon และหน้าต่างรางวัลไว้ ปิดโหมดแล้วคืนค่า CanvasGroup เดิมของ HUD ที่แก้ ถ้าเคยใช้สคริปต์ซ่อนทั้ง UI รุ่นเก่า แนะนำเปิดเกมใหม่หนึ่งครั้ง เพราะรุ่นเก่าไม่ได้เก็บค่า alpha/interactable เดิมทั้งหมดไว้ ผลหน้า EX ยังรอยืนยันจาก Client Hide UI ซ่อนเมนู รายละเอียด รายชื่อตัวละคร และปุ่มปิด/แถบบนที่ตรวจพบในหน้า Character เพิ่มด้วย ภายในหน้า Rank/Eidolon ซ่อนข้อความ ปุ่ม และรูปไอคอน SkillIcon โดยคงภาพประกอบไว้ รวมถึงเว้นชื่อเซิร์ฟเวอร์และ UID การตรวจชื่อองค์ประกอบนี้ยังต้องยืนยันทั้งหน้า EX และ Battle ใน Client การตรวจรอบล่าสุดยืนยันจากผู้ใช้ว่า prop ของ Moment of Joy โหลดได้แล้ว ส่วน Starfroster พบว่า RuntimeFloor มี 175 instance แต่มี GroupID ไม่ซ้ำ 167 กลุ่ม จึงแก้ `group_count` ใน SceneMetadata ให้นับ ID ไม่ซ้ำ มิฉะนั้นตัวตรวจจะไม่ใช้ข้อมูลใหม่และ Client ยังได้รับกลุ่มที่ถูกลบ รายการ checkpoint ที่ส่งออกของสองแมพใช้สถานะ CheckPointEnable ตามคำขอปลดล็อก โดยอ้างอิง enum จาก Client ผลวาร์ป/ปุ่ม M หลังแก้รอบนี้ยังรอยืนยัน ### Town / Story ในแผนที่ M - `MapEntrance.json` มี Mission `2057101` และ Town `2057102` ซึ่งใช้ Plane/Floor เดียวกัน รายการ Starfroster เดิมใช้ Town และเพิ่ม `Starfroster — Story` ไว้ถัดลงมา โดยกำหนด Floor, Entry และชื่อที่ `resources/NavigationProfiles.json` แล้วรีสตาร์ต Game server/Client เมื่อเปลี่ยนรายการ - `scripts/navigation_profiles.lua` ขยายรายการจริงใน `SwitchAreaPage` ผ่าน `SingleTabData`/`SubTabData` ที่ตรวจจาก Client และคัดลอกแท็บในหน่วยความจำ ชื่อ Story แยกจาก localized title เดิม เครื่องหมายตำแหน่งผู้เล่นตรวจ Entry ที่ Server ส่งด้วย จึงไม่แสดงทั้งคู่เพียงเพราะอยู่ Floor เดียวกัน ไม่สร้าง Plane/Floor/Entry ใหม่ - Client เลือกแท็บซ้ำโดยใช้ FloorID แม้สองแถวมี Entry ต่างกัน สคริปต์จึงคง SubTabData ของตัวเลือกหลัง callback เลือกแถว พร้อมแก้ทั้งข้อความหัวข้อและข้อความแถวที่ LoopListView นำกลับมาใช้ ตรวจเฉพาะหน้าที่เปิดอยู่เป็นช่วง ๆ และ cache reflection ไม่สแกน assembly - ตัวโหลดตรวจเวอร์ชัน resource และชนิด Town/Mission ให้ตรงตาราง MapEntrance รวมถึง Plane/Floor และ scene config ก่อนส่งข้อมูลใน heartbeat คำขอที่ระบุ Entry ตรง ๆ จะคง Entry นั้น เพื่อให้ Story ไม่ถูกเปลี่ยนกลับเป็น Town; คำขอที่ระบุเพียง Floor/teleport ใช้ค่าเริ่มต้นจาก `map_teleport_entry_id` ใน SceneMetadata - ผู้ใช้ยืนยันว่าชื่อ Story เปลี่ยนแล้ว และ log หลังแก้ callback ยืนยัน request/response ของ Story เป็น Entry `2057101` และ Town เป็น `2057102` โดย Server ไม่เปลี่ยน Entry ระหว่างทาง ทั้งคู่ยังใช้ Floor `20571001` ตามตาราง Client จึงยังไม่ถือว่าแยกสภาพฉาก/แสง/สถานะเนื้อเรื่องได้ ประเภท Mission/Town ของทางเข้าเพียงอย่างเดียวไม่ยืนยันว่าภาพฉากจะต่างกัน การคืนตำแหน่งจาก Starfroster ไปจุดเดิมช่วยแก้กรณี login หน้าดำได้ตามผู้ใช้ยืนยัน แต่ไม่ถือว่าแก้ฉาก Starfroster ครบแล้ว - เพิ่มการทดลองสถานะฉากแยก Entry ใน `SceneMetadata.entry_saved_values`: Town ใช้ MaxValue จาก RuntimeDimension ตามนโยบาย freeplay ของ PearlSR revision ที่ระบุ ส่วน Story ใช้ DefaultValue ตัวโหลดตรวจชื่อ/ขอบเขตจาก `saved_value_rules` และคัดลอกค่าแต่ละ Entry โดยไม่เขียนทับ defaults ของ Floor ร่วมกัน GetSceneMapInfo ใช้ Entry ที่ผู้เล่นอยู่เมื่อขอ Floor ปัจจุบัน จึงไม่ส่งสถานะ Town ทับ Story เพียงเพราะใช้ Floor เดียวกัน ผลแสงและกลไกหลังแยกสถานะรอบนี้ยังรอตรวจจาก Client และไม่อ้างว่า Story เป็น snapshot เควสต์อย่างเป็นทางการ - หลังเปลี่ยนชุดสถานะ ผู้ใช้ยืนยันว่า Town กลับมาปกติแล้ว และ log ยืนยัน Entry `2057102` พร้อมชุดค่าฉาก Town การเล่นเนื้อเรื่องเต็มและกลไกของ Story ยังไม่ได้ยืนยัน ### Inventory ใน Client `GetBag` ใช้ `resources/InventoryItemConfig.json` ซึ่งสร้างจาก ItemConfig/EquipmentConfig/RelicConfig revision ที่ระบุในไฟล์ เพื่อแสดงรายการ Material, Usable, Mission, Light Cone และ Relic BASIC ที่ Client มองเห็น อุปกรณ์ที่เพิ่มจาก catalog จะถูกเก็บเป็นรายการที่ยังไม่สวมใน `freesr-data.json`; main affix และ sub-affix ของ Relic เลือกจากกลุ่มที่ระบุใน RelicConfig ของชิ้นนั้น ID Relic ใน save ที่ไม่มี config ฝั่ง Client จะไม่ถูกส่งใน Bag, AvatarSync หรือ Battle แต่ยังเก็บไว้ใน save และมี log จำนวนรายการที่ถูกข้าม หากแก้ catalog ให้ใช้ตารางทั้งสี่ไฟล์จาก revision เดียวกันตาม `source` ใน JSON แล้วรัน `python tools/generate_inventory_item_config.py --source-dir "" --inventory-config resources/InventoryItemConfig.json` เพื่อสร้าง Relic templates ใหม่ GaC-Srtool แก้จำนวน Material/Usable/Mission ผ่าน `POST /api/inventory/materials`; จำนวนที่บันทึกใช้กับเกมหลังสร้าง session ใหม่ เพราะ game server เก็บ snapshot ผู้เล่นแยกจาก SDK API process. ### ห้องรถไฟค้างโหลด Floor `10000003` ใช้ RuntimeGroup/prop และ SavedValues defaults ที่ capture จาก Client revision ใน `SceneMetadata.json` แทนชุด MaxValue เดิมซึ่งเปิด `Utility_IsDebug` และกลุ่ม DesignerBackup ที่ Client ไม่มี graph asset กลุ่มที่ส่งให้ Client กรองด้วย GroupIndexList ของ Dimension ที่ capture มา เพื่อไม่ส่งกลุ่มจาก Dimension อื่นปนกัน การเข้าแมปโดยไม่ระบุ Teleport ใช้ StartAnchorID ของ Entry ก่อน checkpoint แรกของ Floor เพื่อให้ลงตรงห้อง ส่วน Entry ที่ไม่มี StartAnchorID ใช้ floor_default_anchor ที่ capture จาก Client โดยรักษา Anchor แยกตามทางเข้า รายละเอียดและขอบเขตการยืนยันอยู่ใน [docs/scene-challenge-investigation.md](docs/scene-challenge-investigation.md) ผู้ใช้ยืนยันว่า Party Car ลงถูกห้องและวาร์ป/เข้าหน้า AA ได้แล้ว ส่วนการเล่นต่อสู้และ settlement ของ AA ยังไม่ได้ทดสอบ ### Endgame — ทีมแนะนำ Damage Analyst ใช้ `StatisticsComponent` ของ Battle ปัจจุบันสำหรับข้อมูลสด พร้อมรายละเอียดรายตัวและช่องทีมจริง เก็บรายละเอียด native ตอนจบโดยตรวจให้ตรงกับผลที่ server ได้รับก่อนบันทึก ดู [docs/damage-analyst-live.md](docs/damage-analyst-live.md) สำหรับหลักฐาน Client และขอบเขตการตรวจ AA cycle: แยกเวลาจำกัดของ Peak ออกจากตาราง MoC แล้ว โดยใช้ค่าที่ capture จาก Client ใน `resources/PeakBattleConfig.json` ทั้ง Start และ Restart; เก็บแพ็กเก็ตผลเต็มเพื่อจับคู่ตัวนับ AA เพิ่มเติม การแก้เลข cycle/ดาวในหน้าผลยังรอตรวจ Client รอบใหม่ ดู [docs/peak-cycle-investigation.md](docs/peak-cycle-investigation.md) `GetFriendRecommendLineup` ส่งทีม Prydwen สำหรับ MoC floor 12 ลงใน response ของหน้าแนะนำทีม โดย resolve AvatarID จาก `resources/AvatarConfig.json` ของ Client ปัจจุบัน ข้อมูล snapshot/แหล่งที่มาและข้อจำกัดของ enum ที่ยัง obfuscate อยู่ระบุใน [docs/prydwen-endgame-recommendations.md](docs/prydwen-endgame-recommendations.md) อัปเดต snapshot ได้ด้วย `python tools/update_prydwen_endgame_teams.py --floor 12 --stage-count 3`; ต้องตรวจหน้าใน Client จริงก่อนยืนยันผล ### CW Preparation และคำสั่งจัดการรอบ (2026-10-20) Client ยืนยันแล้วว่าเริ่มรอบ เปิดร้าน และลากตัวเริ่มลงสนามได้ เพิ่ม handler ซื้อ/รีเฟรช/ล็อกร้านและห้ามลง avatar ซ้ำในสนามแล้ว รุ่นแก้ชุดหลังยังรอตรวจจาก Client การซื้อเก็บตัวเพิ่มใน bench; การรวมดาวอัตโนมัติและเอฟเฟกต์เฉพาะตัวละครยังไม่ครบ พิมพ์ใน GM Console ของ GaC srtool เมื่อเชื่อมต่อเซิร์ฟเวอร์: - `/cw status` อ่านรอบ เงิน HP และเลเวลที่เซฟอยู่ - `/cw gold <จำนวนรวม>` ตั้งเงิน CW เช่น `/cw gold 1000` - `/cw hp <จำนวนรวม>` ตั้ง HP ภายในค่าสูงสุดของรอบ คำสั่งเปลี่ยนค่าถูกคิวไว้ตาม UID/รอบ และนำเข้าใน heartbeat ตอนอยู่หน้า Preparation ไม่แก้เงิน/กระเป๋า Adventure และไม่แทรกตอน Battle ข้อความ queued ยังไม่แปลว่าเกมใช้ค่าแล้ว; ตรวจ log `CW_GM applied` หรือ `/cw status` อีกครั้ง ผู้ใช้ยืนยันว่าเข้าต่อสู้ห้องแรก Standard ได้แล้ว และ log ยืนยันการบันทึกผลจบรอบ ไปห้อง 1-2 เลือก Investment และกลับหน้า Preparation ได้ ทีมและเงินมาจากรอบที่เซฟอยู่ ไม่แทนด้วยทีม reference ผู้ใช้ยืนยันการขายตัวที่ไม่มีอุปกรณ์ติดอยู่และซื้อ EXP แล้ว การซื้อ EXP บันทึกเงิน/ระดับ/จำนวนช่อง/อัตราร้านค้าพร้อมกัน ใช้ราคาจากรอบและจำนวน EXP จาก resource; ระดับสูงสุดไม่หักเงินเพิ่ม ห้อง 1-2 ใน Client ปัจจุบันมี Plot Armor/เลือก Protagonist ผู้ใช้ยืนยันว่าไม่ใช่การกด Battle ธรรมดา ทางจำลอง Battle ห้อง 1-2 ที่ทำไว้ก่อนหน้านี้จึงยังไม่ใช่เส้นทางที่ยืนยันว่าเล่นได้ ต้องต่อ Spotlight selection และ effects ก่อน ส่วน Supply, ห้องถัดไป, Overclock, trait/equipment effects และ progression ตลอดรอบยังไม่ครบ เพิ่มการรวมตัวชื่อเดียวกันและดาวเท่ากันครบ 3 ตัว โดยใช้แถวดาวของ Client ที่ตรวจ identity แล้ว สร้าง UID ใหม่และเก็บตำแหน่ง/อุปกรณ์ของตัวที่คงไว้ ตัวครบชุดที่ค้างจาก build เก่าจะรวมเมื่อกด Continue; การอ่านเซฟอย่างเดียวไม่แก้ไฟล์ ระบบพื้นฐานนี้ยังไม่ครอบคลุมตัวสลับร่างและข้อยกเว้น Spotlight/trait ที่เปลี่ยนกฎรวมดาว เพิ่มการเซฟ/ซิงก์ติดตามอุปกรณ์และลำดับติดตามจากหน้า Preparation แล้ว ใช้ข้อมูลอุปกรณ์ตรงรุ่น ทั้งการรวมดาวและการติดตามอุปกรณ์ยังรอผลยืนยันจาก Client หลังเปลี่ยน build