pre cw with probe and a bunch script to read game behavior

This commit is contained in:
CAtlas0784 committed 2026-10-21 20:40:13 +07:00
1 parent 5120219ae2
commit da8a32f60d
310 files changed
+219693 -872

No files matched your search

+126 -4
View File
@@ -4,10 +4,42 @@
โปรเจกต์อยู่ระหว่างพัฒนา โดยกำลังตรวจการใช้งานกับ 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
1. ติดตั้ง **Go 1.27.0 ขึ้นไป** ตาม `go.mod` และตรวจด้วย `go version`
### ข้อมูลตัวละครหายจนค้างหน้าโหลด
หาก 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` แล้วเลือกเมนูที่ต้องการ
@@ -42,16 +74,22 @@ SDK และ game server เปิดหน้าต่างคอนโซล
| ค่าใน 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 โดยไม่เปิดเกมหรือเซิร์ฟเวอร์:
@@ -60,6 +98,26 @@ SDK และ game server เปิดหน้าต่างคอนโซล
.\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 สองตัว
| ไฟล์ | หน้าที่ | พอร์ต |
@@ -114,7 +172,7 @@ go test ./...
สำรอง `freesr-data.json` และ `persistent` ก่อนเปลี่ยนชุดข้อมูล `res.json` ใน root กับ `resources/res.json` มีโครงสร้างต่างกันและ loader ใช้ร่วมกัน จึงควรเก็บไว้ตามตำแหน่งเดิม
**Calyx ใช้ `battle_config` ใน `freesr-data.json` เป็นข้อมูลจำลองการต่อสู้** จึงอาจได้ StageID และมอนสเตอร์ต่างจากดอกไม้ที่กดในเกม
**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
@@ -124,9 +182,11 @@ go test ./...
| --- | --- | --- |
| 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 ตัวละคร |
@@ -137,6 +197,8 @@ go test ./...
รูปแบบ 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
@@ -189,6 +251,8 @@ go test ./...
แก้โปรไฟล์เพิ่มเติม: 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:
@@ -202,6 +266,10 @@ go test ./...
ก้อนปกติแจกเท่ากับต้นทุน 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
@@ -210,7 +278,7 @@ go test ./...
สถานะรอบ progression: build ทั้งสองตัวผ่าน ผู้ใช้ยืนยันว่าก้อนขึ้น ปลด E1/EX ได้ และยังเข้า Calyx ได้ ตรวจไฟล์ระหว่างลองพบ Dan Heng IL rank=1 และ EX=true ต่อมาผู้ใช้เปิด EX ทั้งสามคนแล้ว จึงเก็บค่า true ตามการกดปลดจริง ผลนี้ไม่ได้รับรองว่าเว็บ GaC srtool ที่แก้แยกอยู่ได้ทดสอบ integration แล้ว
รอบโหลดกระเป๋า 12:18 ส่ง GetBagScRsp ขนาด 56663 ไบต์แล้ว ไม่พบ TryCreateItemData/Inventory error ใน log ณ เวลาตรวจ และอ่าน API status แล้ว hash ของ persistent ไม่เปลี่ยน EX ยัง false ทั้งสามคน หลังจากนั้นรับ RankUpAvatar เวลา 12:18:23 และคำขอ EX เวลา 12:18:28 สำเร็จ พร้อม PlayerSyncScNotify ขนาด 234/237 ไบต์ แสดงว่าใช้การอัปเดตเฉพาะส่วนและไม่ได้ล้าง inventory ทั้งชุด
รอบโหลดกระเป๋าเดิม 12:18 ก่อนเพิ่ม item catalog ส่ง GetBagScRsp ขนาด 56663 ไบต์ ไม่พบ TryCreateItemData/Inventory error ใน log ณ เวลาตรวจ หลังจากนั้นรับ RankUpAvatar เวลา 12:18:23 และคำขอ EX เวลา 12:18:28 สำเร็จ พร้อม PlayerSyncScNotify ขนาด 234/237 ไบต์; ขนาดและผล Client ของ catalog ใหม่ยังต้องตรวจในเกม
## โครงสร้างซอร์ส
@@ -296,4 +364,58 @@ python tools/audit_protocol_handlers.py "<path-to-pearl-sr-checkout>"
`Backslash` ซ่อนเฉพาะ HUD ที่ระบุชื่อไว้ใน `scripts/hide_ui.lua` เช่น HUD เดินโลก/ต่อสู้ ตัวเลขลอยและ waypoint โดยไม่ปิด UIRoot, กล้อง UI หรือ Canvas ทั้งฉาก จึงเหลือหน้าตัวละคร หน้ารายละเอียด EX Eidolon และหน้าต่างรางวัลไว้ ปิดโหมดแล้วคืนค่า CanvasGroup เดิมของ HUD ที่แก้ ถ้าเคยใช้สคริปต์ซ่อนทั้ง UI รุ่นเก่า แนะนำเปิดเกมใหม่หนึ่งครั้ง เพราะรุ่นเก่าไม่ได้เก็บค่า alpha/interactable เดิมทั้งหมดไว้ ผลหน้า EX ยังรอยืนยันจาก Client
Hide UI ซ่อนเมนู รายละเอียด รายชื่อตัวละคร และปุ่มปิด/แถบบนที่ตรวจพบในหน้า Character เพิ่มด้วย โดยเว้น AvatarRankPanel และ Dialog ไว้เพื่อรักษาภาพ Eidolon; ส่วนควบคุมภายในหน้า Rank ยังไม่ได้แยกซ่อนและรอตรวจ 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 "<directory-containing-the-ExcelOutput-files>" --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