# 🚂 GaCy GO
**เซิร์ฟเวอร์จำลอง Honkai: Star Rail ประสิทธิภาพสูง พัฒนาด้วยภาษา Go** พอร์ตจาก [`GaC-hkrpg-ps`](https://github.com/CAtlas0784/GaC-hkrpg-rust) พร้อมอ้างอิงโปรโตคอลจาก `PearlSR` และ `Currency-War-SR` [English](docs/README_EN.md) | [ภาษาไทย](README.md)
--- ## 🇹🇭 ภาษาไทย เซิร์ฟเวอร์จำลอง Honkai: Star Rail ภาษา Go พอร์ตจาก `GaC-hkrpg-ps` และอ้างอิงการทำงานจาก [PearlSR](https://git.xeondev.com/HonkaiSlopRail/pearl-sr) ประกอบด้วย game server, SDK/dispatch server และ REST API สำหรับ GaCy-Srtool โปรเจกต์อยู่ระหว่างพัฒนา โดยกำลังตรวจการใช้งานกับ Client **4.6.53 OS** ตารางคำสั่งและ handler ที่แก้ล่าสุดอ้างอิง PearlSR commit `005d2c12db4781071f4b5b68e8f364587886a710` รายละเอียดอยู่ใน [บันทึกอัปเดต protocol](docs/protocol-update.md) การตรงกันของ schema ไม่ได้ยืนยันว่าเกมทุกระบบทำงานครบ [เอกสารประกอบ](docs/) • [วิธีเริ่มใช้งาน](#เริ่มใช้งานบน-windows) • [การตั้งค่า](#config-และกฎห้าม-hardcode) • [แหล่งที่มาและเครดิต](#-แหล่งที่มาและเครดิต-credits--acknowledgments) ## CW — กำลังพัฒนา เริ่มพอร์ต CW จาก [Currency-War-SR ของ xaodev](https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR) แล้ว: เพิ่ม factory ของ Encounter หลัง Supply, ตารางตัวเลือก/ความยากที่ revision ตรงกัน และ EXP ตามโหมด/บท/Boss โดยยังรักษาเส้นทาง Battle ที่จับไว้เดิม งานนี้ยังเป็นการพอร์ตบางส่วนและยังไม่ได้ยืนยันเส้นทางใหม่ใน Client อ่านสถานะ ผลทดสอบ และงานส่งต่อใน [CW xaodev handoff](docs/CW-XAODEV-CLAUDE-HANDOFF.md) คำสั่งเวลาใน GaCy-Srtool: `/faketime status` ดูเวลาเกม, `/faketime ` หรือ `/faketime <วันที่ RFC3339 พร้อม timezone>` ตั้งเวลาจำลอง และ `/faketime off` คืนเวลาจริง เวลาจำลองเดินต่อและบันทึกใน `game_clock.state_file` จาก config (override ด้วย `GAC_GAME_CLOCK_STATE_FILE`) ใช้กับ Login/heartbeat; ไม่เปลี่ยนนาฬิกา Windows, network timeout หรือเวลา log หลังเปลี่ยนให้เปิดสมุดใหม่ หรือเข้าเกมใหม่หากหน้าจอเก็บตารางเวลาไว้ ฉาก Login รถไฟ Cyrene ใช้งานได้แล้ว โดยใช้ `LoginShow_UP` เปิด manifest ชุดเต็มตั้งแต่เริ่มเกม และผูก event จบกับคลิปแอนิเมชัน UP ผู้ใช้ยืนยันทั้งภาพรถไฟสีขาว ฉากชมพูม่วง กลีบดอกไม้ และการ Logout แล้วกลับเข้าใหม่แล้ว เครื่องมือสร้างชุดสำเนาและคืนค่าต้นฉบับ รวมถึงผลตรวจในเกมอยู่ใน [บันทึกฉาก Login](docs/login-cyrene-investigation.md) ตัวเลือก `lua_overlay` ปิดอยู่ตามปกติ หากเปิดทดลองต้องกำหนด `stage_dir`, `hotfix_dir` และ `streaming_dir` ใน `server.local.json` หรือผ่านตัวแปร `GAC_LUA_OVERLAY_*`; SDK จะตรวจไฟล์ดัชนีก่อนเริ่มและส่งไฟล์ Lua จากชุดที่กำหนดผ่าน URL ของ SDK ที่ตั้งไว้ ยังไม่ได้ยืนยันกับ Client จริง ความคืบหน้าล่าสุด: เพิ่มการสร้างตัวเลือก Supply และห้อง Monster จากข้อมูลที่จับได้ พร้อมมอบรางวัล Encounter บางประเภทเมื่อผ่านเกณฑ์ โหนด Encounter หลัง Supply และ Orb หลังสู้ยังไม่ครบ และยังไม่ได้ยืนยันทางเดินใหม่นี้ในเกม จึงยังไม่ถือว่าเล่นจนจบได้ รายละเอียดอยู่ใน [CW route continuation audit](docs/cw-route-continuation-audit.md) กำลังทำ 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 และยังต้องเชื่อมกับกระดาน สถานะรอบ และแพ็กเก็ตคำสั่งจริง เพิ่มสถานะเริ่มต้นที่วิเคราะห์จากข้อมูลโครงสร้างโปรโตคอลอ้างอิง (Reference Protocol Payload) สำหรับด่านแรกของ Standard Gambit/Overclock ใน `resources/CurrencyWarsInitialRuns.json` แล้ว แต่ยังไม่ได้ต่อคำสั่งเลือกประตู ร้านค้า และการต่อสู้ครบ สถานะเริ่มต้นใช้โครงสร้าง baseline อ้างอิง ไม่ใช่การสุ่มรอบใหม่ทั้งหมด โปรไฟล์เริ่ม CW ใช้นโยบาย `freeplay_unlocked` ใน resource ดังกล่าว ตามข้อกำหนดการข้ามขั้นตอนฝึกสอนของโปรโตคอลอ้างอิง เพื่อไม่ให้ Client ส่งคำขอฝึกสอน Division 0 มายัง handler รอบปกติ นโยบายนี้ข้ามบทฝึกสอนเพื่อเข้า Freeplay โดยไม่แจกผลรางวัลบทฝึกสอน ผู้ใช้ยืนยันว่าเข้า Investment Environment และผ่านไปหน้า Preparation ได้แล้ว ผู้ใช้ยืนยันแล้วว่าเข้า CW ถึงหน้า Investment Environment ได้ เพิ่มการตอบ Confirm จาก `GridFightHandleBonfireHealHpCsReq` ไปยัง `GridFightHandlePendingActionScRsp` พร้อม sync สถานะและบันทึกรอบก่อนตอบกลับ ขณะนี้ใช้ผลเริ่มต้นที่จับจาก reference สำหรับสามตัวเลือกของด่านแรกทั้งสองโหมดใน `resources/CurrencyWarsEntryTransitions.json` ยังเป็นผลเริ่มต้นคงที่ และยังไม่ได้ต่อคำสั่งรอบถัดไป/ร้าน/การต่อสู้ครบ ค่าเอฟเฟกต์บางอย่างของ reference ต่างจาก Client ปัจจุบัน จึงยังไม่ถือว่าเอฟเฟกต์หรือ CW เล่นครบแล้ว ### Unstuck จาก GaCy 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. สำหรับ binary ปกติ ให้เก็บไฟล์ข้อมูลและโฟลเดอร์ `resources` ไว้ใน root ของโปรเจกต์; ถ้าต้องการแพ็กส่ง ให้ใช้ binary ใน `for ship` ซึ่งฝัง runtime resources ไว้แล้ว 4. เปิด `run.bat` แล้วเลือกเมนูที่ต้องการ | เมนู | การทำงาน | | --- | --- | | `1` หรือ Enter | Build ทั้งสองเซิร์ฟเวอร์ แล้วเปิดเซิร์ฟเวอร์และเกม | | `2` | เปิดทั้งสองเซิร์ฟเวอร์จาก `.exe` ที่มีอยู่ | | `3` | เปิดเฉพาะ SDK server | | `4` | เปิดเฉพาะ game server | | `5` | เปิดทั้งสองเซิร์ฟเวอร์จาก `.exe` ที่มีอยู่ และเรียก Gay Proxy | | `6` | Build ทั้งสอง `.exe` อย่างเดียว | | `7` | ออก | | `8` หรือ `stop` | หยุดเซิร์ฟเวอร์ของโปรเจกต์นี้ | | `9` หรือ `requirements` | ตรวจ Go สำหรับ build และถามก่อนติดตั้งเมื่อไม่พบรุ่นที่รองรับ | เมนู 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 - เมื่อไม่ได้เชื่อมต่อ GaCy-Srtool เซิร์ฟเวอร์จับคู่ EventID ของมอนในฉากกับ PlaneEvent ของ Client แล้วใช้ StageConfig ของด่านนั้น ทั้งตอนโจมตีมอนและตอนมอนโจมตีผู้เล่น ไม่มีการใช้รายการมอน Custom จากไฟล์เก่าเป็น fallback หากไม่มีข้อมูลตรงรุ่นจะคืน error พร้อม log - `resources/WorldBattleConfig.json` เก็บ PlaneEvent/Cocoon ที่อ่านจาก Client พร้อมเวอร์ชันและ hash ของ assembly; `world_level` เป็นระดับบัญชีที่ใช้งานร่วมกันใน login/ฉาก/Battle ตารางนี้ต้องส่งออกใหม่เมื่อเปลี่ยน Client หรือชุด resource - GaCy-Srtool ส่ง `POST /api/tool-session` พร้อม `client: "GaCy-Srtool"` และ `connected: true` แล้วต่ออายุตาม `srtool.lease_seconds` จึงเปิดใช้ Custom monsters ได้ การปิดเว็บ/ขาดการเชื่อมต่อทำให้ lease หมดอายุ โดยเก็บไฟล์ชั่วคราวตาม `srtool.lease_file` ต้องใช้เว็บฉบับที่รองรับการต่ออายุนี้และ reload เว็บหลังอัปเดต - หน้า Monster Setting แสดงชื่อกลุ่ม/มอนสเตอร์และรูปจาก Nanoka โดยดึงข้อมูลตาม `client_version` ใน runtime config พร้อม stage และ wave จาก `challenge_data.json`/`resources/StageConfig.json`; metadata จะรีเฟรชเป็นระยะและไม่สลับไปใช้ข้อมูล Client เวอร์ชันอื่นเมื่อแหล่งข้อมูลไม่พร้อม - รายการ Monster Setting เป็นแบบอ่านอย่างเดียวสำหรับ Memory of Chaos, Pure Fiction, Apocalyptic Shadow และ Anomaly Arbitration; Aha's Help ตั้งค่าผ่าน API แยกในแท็บของตัวเอง ไม่ใช้ custom monster waves - ตัวเลือกรอบ AA เก็บกลุ่มที่เลือกไว้ใน `persistent.aa_round_selection` จนกว่าจะเลือกกลุ่มอื่นหรือล้างค่า; heartbeat ขอข้อมูล Anomaly Arbitration ใหม่ผ่าน `ChallengePeakModule.SendGetChallengePeakDataRequest()` ของ Client เมื่ออยู่นอก Battle/รอบที่เล่นค้างไว้ และรอให้ `CurrentPeakGroupID` ตรงก่อนถือว่าซิงก์แล้ว ห้อง/บอสภายในรอบยังเลือกจาก UI ในเกม โดยไม่เปลี่ยน PeakID ของคำขอเก่าไปเป็นห้องอื่น - `battle_config.monsters` จาก Srtool ใช้ได้เมื่อ tool-session lease ยัง active และ request เป็น CocoonType Calyx (Golden/Crimson) เท่านั้น Stagnant Shadow, Cavern of Corrosion, weekly bosses และ challenge stages ใช้ stage resources ของ Client ตามปกติ - 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 อีกครั้ง Ping colors อยู่ใน `scripts/ping_rainbow.lua` เปลี่ยนสีทั้งแท่งสัญญาณและตัวเลขโดยใช้ค่า ping จริง ภาพหลอดใช้ alpha จาก sprite เดิมและสีพื้นขาวเพื่อไม่ให้สีเพี้ยน ส่วนตัวเลขลงสีที่ glyph โดยไม่เขียนข้อความทับ จึงไม่กะพริบเมื่อค่าเปลี่ยน ผู้ใช้ยืนยันผลหน้าเดินแล้ว รายละเอียดและขอบเขตอยู่ใน `docs/ping-colors.md` Freecam และ FaceCam เลือกได้จากหน้า **สวิตช์ระบบพิเศษของ GaCy** ใน [GaCy Srtool](https://gacy-srtool.vercel.app/) โดยตั้งระยะเริ่มต้นของ FaceCam แล้วกด Tab ในเกมเพื่อเปิดหรือปิด Freecam ใช้ปุ่มลูกศรและ Page Up / Page Down เคลื่อนกล้อง พร้อมคลิกขวาค้างเพื่อหมุน ส่วน FaceCam ยึดกล้องกับหัวตัวละครที่ควบคุมหรือกำลังแอ็กชันใน Battle ลูกกลิ้งปรับระยะชั่วคราว และเมื่อปิด–เปิดใหม่จะกลับเป็นระยะที่ตั้งไว้ ข้อความสถานะกล้องแสดงเมื่อปรับและหายหลังหยุดปรับ 3 วินาที ผู้ใช้ยืนยันมุม Battle และร่าง Khaslana แล้ว `scripts/fps_settings_120.lua` เพิ่มตัวเลือก 120 FPS ลงในรายการ Graphics ของเกมเมื่อ Client ซ่อนตัวเลือกตามเงื่อนไข `IsSupport120FPS` โดยใช้ TextID และตัวจัดการค่าของ Client เอง การเลือกค่ายังขึ้นกับจอและฮาร์ดแวร์ที่รองรับ; ต้องเปิดหน้า Graphics ใหม่หลังสคริปต์ถูกโหลด `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` ปกติจะอยู่ใน root ของโปรเจกต์และอ่านไฟล์จาก root นั้น ## Build และรันเอง รันคำสั่งจาก root ของโปรเจกต์: ```powershell go build -o gameserver-go.exe ./cmd/gameserver go build -o sdkserver-go.exe ./cmd/sdkserver ``` การ build ผ่าน `run.bat` จะสร้าง binary ปกติไว้ที่ root และ binary แบบแพ็กส่งไว้ที่ `for ship/` ทุกครั้ง ส่วน build แบบแพ็กส่งด้วยมือใช้ `-tags ship`: ```powershell New-Item -ItemType Directory -Force 'for ship' | Out-Null go build -tags ship -o 'for ship/gameserver-go.exe' ./cmd/gameserver go build -tags ship -o 'for ship/sdkserver-go.exe' ./cmd/sdkserver ``` โฟลเดอร์ `for ship` มีเฉพาะ `.exe` หลัง build; resource, Lua ที่ดูแลในโปรเจกต์ และไฟล์ตั้งต้น `server.json`, `freesr-data.json`, `persistent`, `hotfix.json`, `versions.json` ถูกฝังไว้ใน binary เมื่อเปิด executable จะใช้โฟลเดอร์ที่วาง executable เป็น working directory และสร้างไฟล์ตั้งต้นที่ยังไม่มี โดยไม่เขียนทับ config หรือเซฟที่มีอยู่แล้ว `server.local.json` ไม่ได้ฝังและยังใช้ override เฉพาะเครื่องได้ ไฟล์ resource ที่วางข้าง executable มีสิทธิ์ override ข้อมูลที่ฝังไว้ การตั้งค่าใน `server.json` ที่สร้างขึ้นมาจะยังเป็นค่าเริ่มต้นของโปรเจกต์ ให้แก้ host ที่ Client ต้องเชื่อมต่อก่อนเปิดให้เครื่องอื่นใช้ เปิดคนละ 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; ship build ฝังชุดนี้ไว้ | | `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 / GaCy-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 โหลดข้อมูลไม่สำเร็จ | HideUI และ FaceCam เขียนข้อมูลวินิจฉัยลง `logs/hide-ui.log` และ `logs/facecam.log` ใน working directory ของ Game server (สำหรับ build `ship` คือโฟลเดอร์ข้าง executable) และยังส่งข้อความเข้า `Player.log` ด้วย หาก Client เขียนโฟลเดอร์นั้นไม่ได้จะบันทึก error ใน `Player.log` สคริปต์ทั้งสองรวมใน build `ship`; การเขียนไฟล์จริงยังต้องตรวจจาก 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; ship build ฝัง resource นี้ทั้งไฟล์ ไม่ได้ฝัง ID ของกฎเหล่านี้เป็น fallback ใน source - 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 เว็บ GaCy 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 ตามการกดปลดจริง ผลนี้ไม่ได้รับรองว่าเว็บ GaCy 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 protocol/proto/ungrouped.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: หน้าตัวละครและ GaCy-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 ให้เต็ม - GaCy-Srtool บันทึก Traces ผ่านการส่ง `freesr-data` ทั้งชุด ไม่ใช่ `/api/freesr/avatars` ซึ่งรับเฉพาะข้อมูลทั่วไปของตัวละคร โค้ดเว็บที่แก้ในโปรเจกต์ GaCy-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; FaceCam; ลูกศร: เลื่อนกล้อง; กดเมาส์ขวาค้างแล้วขยับเมาส์เพื่อหันกล้อง - `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 ใหม่ GaCy-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) AA round sync (2026-10-30): การเลือกรอบจาก Srtool รีเฟรชรายการผ่าน native `ChallengePeakModule.SendModuleInitRequest` หลังออกจาก Battle/โหมดที่กำลังเล่น โดยไม่แก้ PeakID ของห้องที่กด Client log ยืนยันรับรอบ 3 แล้ว แก้ key ด่านใน scene override ที่ผิดจาก resource และการอ่าน `peak.stages`; ตรวจข้อมูลฉากครบ 44 ด่านผ่าน และผู้ใช้ยืนยันว่าเปลี่ยนรอบจาก Srtool และเข้าเล่นได้แล้ว ดูหลักฐานและข้อจำกัดใน [docs/aa-round-sync.md](docs/aa-round-sync.md) `GetFriendRecommendLineup` ส่งทีม Prydwen สำหรับ MoC, Pure Fiction, Apocalyptic Shadow และ Anomaly Arbitration โดย resolve AvatarID จาก `resources/AvatarConfig.json` ของ Client ปัจจุบัน และอ่าน Peak IDs ของ AA จาก `ChallengePeakGroupConfig.json` ข้อมูล snapshot แหล่งที่มา และข้อจำกัดของ enum ที่ยัง obfuscate อยู่ระบุใน [docs/prydwen-endgame-recommendations.md](docs/prydwen-endgame-recommendations.md) อัปเดตทุกโหมดด้วย `python tools/update_prydwen_endgame_teams.py` หรือเลือกโหมดด้วย `--mode` ต้องตรวจ route กับหน้าใน Client จริงก่อนยืนยันผล ### CW Preparation และคำสั่งจัดการรอบ (2026-10-20) Client ยืนยันแล้วว่าเริ่มรอบ เปิดร้าน และลากตัวเริ่มลงสนามได้ เพิ่ม handler ซื้อ/รีเฟรช/ล็อกร้านและห้ามลง avatar ซ้ำในสนามแล้ว รุ่นแก้ชุดหลังยังรอตรวจจาก Client การซื้อเก็บตัวเพิ่มใน bench; การรวมดาวอัตโนมัติและเอฟเฟกต์เฉพาะตัวละครยังไม่ครบ พิมพ์ใน GM Console ของ GaCy srtool เมื่อเชื่อมต่อเซิร์ฟเวอร์: - `/cw status` อ่านรอบ เงิน HP และเลเวลที่เซฟอยู่ - `/cw gold <จำนวนรวม>` ตั้งเงิน CW เช่น `/cw gold 1000` - `/cw hp <จำนวนรวม>` ตั้ง HP ภายในค่าสูงสุดของรอบ - `/cw exp ` เพิ่ม EXP และคำนวณเลเวล/ความจุ/น้ำหนักร้านค้าตามกฎที่ดึงมา - `/cw list role` แสดงรายการ RoleID, AvatarID และจำนวนดาวที่รองรับ - `/cw role [count]` เพิ่มตัวละครลงม้านั่งสำรอง พร้อมรวมดาวหากมีตัวซ้ำ; หากม้านั่งเต็มจะยกเลิกคำสั่งทั้งหมด - `/cw list equip` แสดงรายการรหัสอุปกรณ์ที่ดึงมา - `/cw equip [count]` เพิ่มอุปกรณ์ลงในกระเป๋า CW - `/cw help` แสดงรูปแบบคำสั่งทั้งหมด การมอบของและการตอบรับคิวคำสั่งจะคอมมิตพร้อมกับการทำ snapshot CAS และการแทนที่ไฟล์แบบ atomic เพื่อป้องกันการมอบของซ้ำซ้อนหลังหลุดการเชื่อมต่อ หากคำสั่งถูกปฏิเสธจะบันทึก log `CW_GM rejected` โดยไม่บันทึกการมอบของบางส่วน รายการเหล่านี้ครอบคลุมเฉพาะข้อมูลที่ดึงมา ไม่ใช่ไอเทมทุกชิ้นใน Client - `/cw list orb|consumable|forge` แสดงรหัสรางวัลที่ดึงมา - `/cw orb|consumable|forge [count]` มอบไอเทมเหล่านั้นในระหว่างช่วง Preparation - ขณะนี้ Forge รองรับเฉพาะข้อเสนออุปกรณ์ ส่วน Role/Expert forge จะถูกปฏิเสธจนกว่าจะทำ pending action ที่เกี่ยวข้อง - การเปิด Orb, การดรอป Orb อัตโนมัติในการต่อสู้ และผลของไอเทมกดใช้ (consumable) ยังไม่สมบูรณ์ การมอบไอเทมไม่ได้แปลว่าการใช้งานใน Client จะทำงานได้จริง ### ระบบช่วยเหลือของ Aha (Aha's Help) การตั้งค่า Persistent ใน GaCy-Srtool มีตัวเลือกเลเวลช่วยเหลือของ Aha แยกอิสระ โดยอ่านระดับเลเวลจาก `resources/AhaHelpConfig.json` ตามตารางบัฟที่กำหนดไว้ API `GET /api/aha-help` จะส่งรายการเลเวลที่เลือกได้ และ `POST /api/aha-help` รองรับรูปแบบ `{"level":1}`, ค่า 0 เพื่อปิดการใช้งาน หรือ `null` เพื่อใช้ค่าการสนับสนุน Invasion ของ FreeSR เดิม การตั้งค่านี้ถูกบันทึกแบบ atomic ลงใน `persistent.aha_help_level` โดยไม่กระทบต่อสถานะของ CW และ EX บัฟที่เลือกจะเริ่มทำงานเมื่อเข้าสู่การต่อสู้ถัดไปที่มีบัฟ Corruption จริงตามการแมป invasion ใน `BattleRules.json` ซึ่งครอบคลุมการต่อสู้แบบกำหนดเอง (custom battle) ใน srtool ที่นำเข้าบัฟ Corruption ด้วย ส่วนความท้าทายทางการยังคงอ่าน Corruption จาก `StageInvasionConfig.json` เช่นเดิม การนำ Stage ID มาใช้ซ้ำเพียงอย่างเดียวจะไม่ใส่ Corruption เข้าไปในการต่อสู้แบบกำหนดเอง เลเวลที่เลือกจะเข้ามาแทนที่ระดับช่วยเหลืออื่น ๆ ของ Aha และหากตั้งเป็น 0 อย่างชัดเจนจะปิดการช่วยเหลือในการต่อสู้ Corruption นั้นทันที ค่า `null` จะอ้างอิงตาม `enable_invasion_support` ของ FreeSR ซึ่งหน้าเว็บจะแสดงให้เห็นว่าสวิตช์เดิมนี้เปิดอยู่หรือไม่ การตั้งค่านี้แยกจากการตั้งค่า Aha Global และไม่มีผลกับการต่อสู้ที่กำลังดำเนินอยู่ บันทึก `AHA_HELP` ใน `server_debug.log` จะแสดง ID ของ corruption และ support ที่ถูกส่ง ค่า `BuffLevel` จากตารางใช้ระบุ Buff ID เฉพาะ โดยคงการเข้ารหัส buff-level ตามโปรโตคอลเดิมไว้ ข้อมูลเกณฑ์ความคืบหน้า (Progress thresholds) เป็นเพียง metadata ไม่ใช่ dynamic key ที่สร้างขึ้นเอง พฤติกรรมและแอนิเมชันใน Client ยังต้องรอการยืนยันจากการเล่นจริง ### Stella Jump, พรีวิวอีเวนต์ และตู้กาชาพิเศษ (Event Previews & Featured Gacha) ไฟล์ `resources/StellaJumpConfig.json` บรรจุข้อมูลด่าน ChenLing GameBoy ทั้ง 16 ด่านที่ดึงมาจาก Client ปัจจุบัน โดยระบบ Sandbox จะแสดง Challenge Pack ของแต่ละด่าน พร้อมทั้งบันทึกเวลาที่ดีที่สุด/สถานะการผ่านด่านจริงลงใน `persistent.stella_jump` การเปิดดูเมนูเป็นแบบอ่านอย่างเดียว (read-only) คำสั่งช่วยเล่น (cheat) มีแฟล็กบันทึกแยกต่างหาก โดยชื่อคำขอ/การตอบกลับที่ถูก obfuscate ยังคงยึดตาม revision ของคำสั่งในคลังโค้ดและยังต้องรอการตรวจสอบกับ Client ไฟล์ `resources/ActivityPreviewPolicy.json` ใช้เลือกพรีแฟบหน้าต่างอีเวนต์จาก `PreviewGameplayCatalog.json` สำหรับพรีวิวอื่น ๆ ขณะนี้ถูกระงับไว้ก่อน เนื่องจากบันทึกจาก Client พบปัญหา asset รางวัลสูญหายและการค้นหาไอเทมผิดพลาดใน ActiveActivity จึงควรเปิดใช้งานแต่ละหน้าต่างใหม่เมื่อตรวจสอบ asset ที่ติดตั้ง, identity ของโมดูล และความเกี่ยวโยงของรางวัลเรียบร้อยแล้วเท่านั้น ส่วนการต่อสู้เฉพาะอีเวนต์และการกดรับรางวัลยังทำไม่เสร็จสมบูรณ์ รายชื่อตู้กาชาพิเศษอ้างอิงจากแถวพรีแฟบแบนเนอร์ตัวละครที่ดึงมา ปัจจุบันมี 2 ตู้ แต่ Client ที่ติดตั้งไม่สามารถโหลดพรีแฟบของ Seele ได้ ทำให้การเปลี่ยนหน้า UI ค้าง การตรวจสอบ `AssetMgr.Exists` ขณะรันพบเฉพาะ asset ของตู้ถาวร (Standard) และตู้เริ่มต้น (Beginner) แต่ไม่พบ asset ตู้ลิมิเต็ดทั้ง 4 ตู้ที่ดึงมา ปัจจุบัน `GachaSandboxPolicy.json` จึงเปิดให้เรียกดูได้เฉพาะหน้าต่าง Standard ที่ยืนยันแล้วเท่านั้น โดยการสุ่มจะตอบกลับเป็น `RET_GACHA_NOT_SUPPORT` จนกว่าจะยืนยัน asset ตู้พิเศษที่ใช้งานได้จริง และหากรายการที่ยืนยันว่างเปล่าจะปฏิเสธการเข้าหน้านี้ด้วย แถวในตารางเพียงอย่างเดียวไม่ถือเป็นหลักฐานว่ามี asset ติดตั้งอยู่จริง ต้องตรวจสอบ bundle ใน Client ก่อนเพิ่ม path ลงในรายการ สำหรับการแจกตัวละครจะให้ตัวละครตามภาพพร้อมไอเทม Eidolon ซ้ำตามที่ตั้งค่าไว้ โดยคงสถานะ Rank/EX/Traces เดิม และยังไม่ได้ยืนยันผลใน Client หากตัวละครไม่มีใน roster ที่นำเข้าจะถูกปฏิเสธ นโยบาย Sandbox นี้ใช้การสุ่มฟรีและไม่ได้จำลองเรทหรือกฎการแปลงไอเทมอย่างเป็นทางการ ข้อมูลตารางที่ดึงมาระหว่างรันและข้อจำกัดการตรวจสอบในปัจจุบันถูกบันทึกไว้ใน [docs/offline-gameplay-continuation.md](docs/offline-gameplay-continuation.md) การคอมไพล์ handler เหล่านี้ผ่านไม่ได้ยืนยันว่าหน้าจอหรือระบบการเล่นใน Client จะทำงานได้สมบูรณ์ คำสั่งเปลี่ยนค่าถูกคิวไว้ตาม 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 เพิ่มทางเข้า Plot Armor/Spotlight: ส่งรายการให้เลือกเป็น pending action, ตรวจตัวละครที่มีและความเข้ากันได้, เซฟผลกับ UID ตัวในรอบ และปิดจุดเลือกของโหนดหลังเลือกสำเร็จ การรวมดาวย้าย binding ไปตัวใหม่ นโยบายสุ่มทางเลือกอยู่ใน `resources/CurrencyWarsSpotlightPolicy.json` เป็นนโยบายเซิร์ฟเวอร์ที่ระบุไว้ชัดเจน ยังไม่ได้ยืนยันน้ำหนักสุ่ม/ค่า reroll ของต้นฉบับ ส่วนผล Spotlight ใน Battle และ progression ห้องถัดไปยังทำต่ออยู่ ผู้ใช้ยืนยันว่าเลือก Protagonist ได้แล้ว ส่วน 6900 ตอน Battle ที่ตามมาพบว่าเป็นตัวพิเศษ CW ซึ่งไม่มีใน Adventure roster เพิ่ม resolver ตาม `SpecialAvatarID` และแถว SpecialAvatar ตรงรุ่นแล้ว ขณะนี้ capture ครอบคลุม fake-avatar trial ที่ world level ตาม resource ที่ใช้ โดยยังรอผล Battle จาก Client การปลดตัวใน Adventure ไม่ใช่เงื่อนไขของตัวพิเศษนี้ CW ล่าสุด: ผู้ใช้ยืนยัน Battle ห้อง 1-2 หลังเลือก Protagonist ผ่านแล้ว เพิ่ม handler เลือก Supply แจกตัว/อุปกรณ์จาก offer จริงพร้อมรวมดาวและเซฟก่อน sync; ยังรอผล Supply ใน Client ส่วน EliteBranch/การสร้างศัตรูตาม camp และโหมดตลอดรอบยังทำไม่ครบ --- ## 💖 แหล่งที่มาและเครดิต (Credits & Acknowledgments) โปรเจกต์ `GaCy GO` พัฒนาขึ้นโดยต่อยอดและนำองค์ประกอบ/ข้อมูลอ้างอิงจากโครงการและชุมชนวิจัยเกมต่าง ๆ ขอขอบคุณเจ้าของผลงานและทีมพัฒนาต้นทางทุกท่านดังนี้:
🏛️ สถาปัตยกรรมเซิร์ฟเวอร์และโครงสร้างโปรโตคอล (Server Architecture & Protocol) - **PearlSR ([HonkaiSlopRail/pearl-sr](https://git.xeondev.com/HonkaiSlopRail/pearl-sr))**: - โครงสร้าง Dispatch / Game Gateway ภาษา Go และรูปแบบการจัดการ Connection - ตารางนิยามชื่อคำสั่งและหมายเลข Command ID อ้างอิง (`protocol/proto/upstream_cmdids.go`) - โครงสร้างพื้นฐานของ Packet Handlers ใน `internal/gameserver/` - **LunarCore ([Melledy/LunarCore](https://github.com/Melledy/LunarCore))**: - โครงการเซิร์ฟเวอร์จำลอง Honkai: Star Rail ภาษา Java โดย Melledy และผู้ร่วมพัฒนา — โครงการอ้างอิงมาตรฐานด้านกลไกการต่อสู้, นิยามโครงสร้างแพ็กเก็ต และสูตรความน่าจะเป็นของระบบกาชา - **FreeSR Team**: - โครงสร้าง Schema ของฐานข้อมูลตัวละครและไอเทมผู้เล่นแบบ JSON (`freesr-data.json`) ที่ใช้ในการโหลดและบันทึก Roster / Inventory ผู้เล่น - **March7thHoney ([Mar7thLover/March7thHoney-OpenSource](https://github.com/Mar7thLover/March7thHoney-OpenSource))**: - โครงสร้างและอัลกอริทึมจำลองระบบสุ่มกาชา (Gacha Pulls & Pity System), รูปแบบตารางร้านค้า, และข้อกำหนดโปรโตคอลระบบจัดการ Muip Admin - **FFGO ([git.kain.io.vn](https://git.kain.io.vn/))**: - งานวิจัยโครงสร้างโปรโตคอลเครือข่ายและการจำลองเซิร์ฟเวอร์ สำหรับการศึกษาลำดับการเปลี่ยนผ่านสถานะเกม (Workflow Transitions) และความเข้ากันได้ของแพ็กเก็ต
🎮 โหมดการเล่น ระบบจำลอง และเนื้อหา Endgame (Game Modes & Simulation) - **Currency-War-SR ([xaodev/Currency-War-SR](https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR))**: - อ้างอิงและนำเข้าบางส่วนภายใต้สัญญาอนุญาต AGPL-3.0 (เก็บอยู่ที่ `third_party/currency-war-sr`) - โค้ดต้นแบบระบบจำลอง Currency Wars (Grid Fight / Autochess) 4.6.53 - ตารางจับคู่ความสัมพันธ์ Role/Traits, การสร้าง Encounter หลังเลือก Supply, การคำนวณความยากและ EXP ต่อรอบ (`internal/common/currency_wars_xaodev.go`, `internal/gameserver/currency_wars_xaodev.go`, `resources/CurrencyWarsXaodev.json`) - **minatoSR ([haltinggoalies/minatoSR](https://github.com/haltinggoalies/minatoSR))**: - ข้อมูลตำแหน่ง NPC และ Overrides ด่าน Challenge Peak / Anomaly Arbitration (AA) 40 โหนด ใน `resources/ChallengePeakSceneOverrides.json`
📦 ข้อมูลทรัพยากรเกมและเครื่องมือวิเคราะห์ (Game Data & Tooling) - **Dimbreath ([DimbreathBot/TurnBasedGameData](https://github.com/DimbreathBot/TurnBasedGameData))**: - ตารางข้อมูลเกมหลัก (Excel Output / `ExcelBinOutput`), ค่า Hash อ้างอิง และดัชนี ID ตัวละคร, ไอเทม, รีลิกส์ และสกิลที่นำมาใช้ในโฟลเดอร์ `resources/` - **Prydwen Institute ([Prydwen.gg](https://www.prydwen.gg/star-rail/))**: - ข้อมูลคำแนะนำการปั้นตัวละคร, Main Stat / Sub-Stat ที่เหมาะสมของ Relic, และเทียร์ลิสต์ตัวละครสำหรับคำแนะนำการจัดทีมใน `resources/PrydwenRecommendations.json` - **AstralOS ([Ma3-OS/AstralOS](https://gaytea.cod-algieba.ts.net/AstralOS/AstralOS))**: - ชุดเครื่องมือวิเคราะห์โปรโตคอล, การถอดรหัส IL2CPP Metadata / Dumper, การ Hook เครือข่าย และเครื่องมือ Reverse Engineering ที่เคยนำมาใช้ในการวิเคราะห์และศึกษาโครงสร้างระบบ