Files
GaCy-GO/docs/battle-investigation.md
T

18 KiB
Raw Blame History

Battle investigation — 2026-10-16

หลักฐานที่ใช้อ้างอิง

  • Upstream: HonkaiSlopRail/pearl-sr
  • ตรวจ remote ใหม่แล้วที่ commit 005d2c12db4781071f4b5b68e8f364587886a710
  • ไฟล์หลัก: gameserver/src/services/battle.zig, gameserver/src/manager/battle_mgr.zig, gameserver/src/data/game_config.zig, protocol/src/protocol.pb.zig
  • Client ล่าสุดยัง NRE ที่ CreateBattleServerCheckResult ผ่านตัวแปลง BattleAvatar/รายการ relic และ NMBMHCNGCFG.BDNFEMGIEIH
  • Payload ที่บันทึกเวลา 21:34 มีตัวละคร 4 ตัว แต่ละตัวมี relic 6 ชิ้นและ sub-affix 24 ค่า แม้เลิกส่ง inventory UniqueId ใน BattleRelic แล้ว Client ก็ยังล้ม

เทียบทีละส่วน

ส่วน สิ่งที่ตรวจพบ ข้อสรุปที่ใช้ได้
Quick Calyx response Field numbers: retcode=1, battle_info=5, wave=8, cocoon_id=11 ตรง upstream ยังไม่พบ field-number mismatch ใน wrapper
SceneBattleInfo ตรวจชุด field numbers ของ 20 fields ตรงกัน ชื่อ JAOIOKBBMFC ของ Go คือ field 7 ซึ่ง upstream เรียก monster_wave_length
BattleAvatar ตรวจชุด field numbers ของ 18 fields ตรงกัน ยังต้องดูค่าที่ parser ของ Client ถอดได้จริง
Relic/lightcone BattleRelic, RelicAffix และ BattleEquipment มีชุด field numbers ตรงกัน จำนวน sub-affix ฝั่ง Go อย่างเดียวไม่ยืนยันว่า Client ถอดได้ครบ
Monster/wave SceneMonster, SceneMonsterWave, SceneMonsterWaveParam มีชุด field numbers ตรงกัน ยังไม่มีหลักฐานให้สลับ WaveParam เป็น nil อีก
Buff Go ใช้ชื่อ OwnerId แต่ upstream ใช้ owner_index บน field 3 เหมือนกัน ชื่อ Go ไม่ใช่หลักฐานว่า field บน wire ผิด
แหล่งข้อมูล Go ใช้ freesr-data.json/persistent; PearlSR ใช้ config.json/selected avatars Payload ทั้งสองยังไม่เท่ากันในระดับค่าข้อมูล
EX rank Upstream append รายการ ExRankAvatarID ให้ทุก battle avatar; Go ส่งรายการตามตัวละครที่ปลดล็อก เป็นข้อแตกต่างที่บันทึกไว้ ยังไม่ชี้ว่าเป็นสาเหตุ NRE
Global/technique buffs เจ้าของบัฟ, target list และ dynamic values บางตัวต่างกัน ต้องตรวจแยกหลังระบุตำแหน่งที่ parser/ตัวตรวจล้ม

การตรวจ field numbers ข้างต้นไม่ได้ยืนยัน default values, Client schema ของรุ่นที่รันจริง หรือผลการคำนวณค่าสถานะใน Client

ขั้นแรกที่เพิ่มในรอบนี้: เก็บ wire และอ่านกลับบน Client

บันทึกหลังส่ง response สำเร็จจาก Session.Send แทนการบันทึกระหว่างสร้าง battle:

  • battle_last_response.bin: protobuf body ของ response ที่ส่งจริง
  • battle_last.bin: bytes ของ nested battle_info ที่ตัดออกจาก response เดิมโดยตรง ไม่ marshal ใหม่
  • battle_last.json: ข้อมูลสำหรับอ่านด้วยตา
  • battle_last_capture.json: CmdID, ชื่อ response, StageID, เวลา UTC และ SHA-256
  • battle_diagnostics/: snapshot ของแต่ละรอบ เก็บทั้งข้อมูลเดิมและรอบต่อมา

เปิด read-only Client probe ด้วย battle_diagnostics.client_battle_type ใน local config ชื่อ class ต้องเอาจาก stack trace ของ Client รุ่นที่กำลังใช้งาน ไม่ฝังชื่อ class ที่เปลี่ยนตามเวอร์ชันใน Go/Lua

Probe ถูกส่งครั้งเดียวใน heartbeat หลัง response battle โดยใช้ protobuf parser ของ Client อ่าน bytes เดียวกับ capture จากนั้นเขียน [GAC_BATTLE_WIRE] ลง Player.log พร้อม decoded JSON และ descriptor field numbers ไม่แก้ตัวละคร relic บัฟ หรือ callback การต่อสู้

หาก parser/Reflection ไม่ถูกเปิดให้ Lua ใช้ จะมี probe_error ใน Player.log แทน ห้ามตีความว่า capture ฝั่ง Go ผ่านแล้วแปลว่า Client decode ผ่าน

ข้อสรุปที่ถอนออก

Stack trace เดิมเพียงอย่างเดียวไม่ได้พิสูจน์ว่า WaveParam=nil หรือ SceneMonster.ExtraInfo=nil เป็นสาเหตุ การใส่ ExtraInfo หรือการลบ UniqueId ยังไม่ได้ทำให้การเข้า battle สำเร็จ จึงไม่ถือเป็นการแก้ที่ยืนยันผลแล้ว

ผลตรวจรอบ 21:47: main affix ที่ไม่มีในตาราง

Calyx และ overworld ตายที่ stack เดียวกัน ส่วน probe รุ่นแรกทำงานแล้วแต่รายงาน protobuf parser property not found จึงไม่มี Client readback และยังใช้ผลของ probe ยืนยันการ decode ไม่ได้

แก้การอ่านตำแหน่ง exception: 0xc4eb77c เป็น return address หลังคำสั่ง call ที่ 0xc4eb777 เช่นเดียวกับ frame 0x166b23d2 ที่อยู่หลัง call 0xc4eb0c0 ดังนั้นจุดนี้มาจาก branch หลัง lookup main affix ไม่พบ (0xc4eb2b0–0xc4eb2b3) ไม่ใช่ branch ตรวจ sub-affix array ที่กระโดดไป 0xc4eb77c แล้วมี return address 0xc4eb781 การตีความ sub-affix array เป็น null ในรอบก่อนจึงถูกถอนออก

ตรวจ payload จริงและ inventory พบรายการนี้:

Inventory UID Avatar Relic main_affix_id เดิม ผลตรวจ
483 1213 (Dan Heng IL) 63095 11 ไม่มีใน main affix group 55 ของลูกแก้ว

เทียบกับ relics.json และ relic_main_affixes.json: group 55 มี ID 1–10 โดย ID 10 คือ ImaginaryAddedRatio และ resources/AvatarConfig.json ระบุ Avatar 1213 เป็น Imaginary

แก้ ข้อมูล inventory เท่านั้น จาก 11 เป็น 10 ใน freesr-data.json ไม่มีเงื่อนไขพิเศษสำหรับ Avatar/Relic นี้ใน converter สำรองข้อมูลและ log ก่อนแก้ไว้ใน battle_diagnostics/main-affix-repair/ แล้ว และปิด probe ใน local config ขณะลองผลการแก้ข้อมูล

ข้อจำกัดของชุดข้อมูลอ้างอิง: ไม่พบ relic ใหม่ 63295/63296 ของ Avatar 1511 จึงไม่ได้รับรอง inventory ทุกชิ้น แต่ชิ้นที่อยู่ในทีมของ payload รอบนี้มีครบและพบ main affix ผิดหนึ่งชิ้นข้างต้น

ขั้นต่อไปหลังแก้ main affix

  1. เปิดเกมใหม่ให้ Client โหลด inventory ที่แก้แล้ว และลอง Calyx/overworld ด้วยทีมเดิม
  2. หากยังล้ม ให้เก็บ stack ใหม่และเทียบ capture ว่าส่ง main affix 10 แล้ว ก่อนเปลี่ยนส่วนอื่น

แนวทางตรวจเพิ่มเติมหากยังล้ม

  1. เทียบ SHA-256 ให้ตรงกับรอบที่ Client ล้ม
  2. เทียบ schema/ข้อมูล BattleAvatar และ nested relic ที่ Client ถอดได้กับ capture ฝั่ง Go
  3. หากข้อมูลตรงกัน ให้แยกตัวละครและอุปกรณ์ที่ทำให้ตัวตรวจค่าสถานะล้มโดยมี control payload ก่อนแก้ resource หรือสูตรบัฟ
  4. เปลี่ยนเฉพาะส่วนที่มีหลักฐาน แล้วตรวจ Calyx และ overworld battle แยกกัน

สถานะ: ยังไม่มีการยืนยันว่าเข้า fight ได้

รอบ 17 ตุลาคม 2026: แก้เส้นทางร่วมและรอผล Client

ตรวจ source ปัจจุบันก่อนแก้แล้วพบดังนี้:

  1. HitMonsterBattleInfo ไม่ได้ตั้ง monster_battle_type จึงส่งค่า NONE=0 แทน TRIGGER_BATTLE=1 (อ้างอิง enum ใน protocol/src/protocol.pb.zig ของ revision ที่ระบุข้างต้น) แก้ให้ระบุ enum ชัดเจน
  2. เพิ่มการอ่าน assist_monster_entity_info.entity_id_list ควบคู่รายการเป้าหมายอีกสองช่อง ยังคงตรวจว่าเป็นมอนที่ส่งในฉากจริง และบันทึก incoming IDs, รายการมอนในฉากและผลจับคู่ ไม่มีการสร้างเป้าหมายปลอม
  3. res.json ชุดปัจจุบันมี avatarConfigs ว่าง ตัวโหลดเดิมไม่ได้ใช้ AvatarConfig.json จึงหา entry attack buff ไม่ได้ เพิ่มการโหลดธาตุจากตารางนี้และ mapping ธาตุจาก resource กฎ Battle
  4. ปรับ owner, targets และ dynamic values ของบัฟตาม PearlSR รวมถึง entry attack สำหรับ Calyx แยก invasion ของ challenge ออกจาก custom stage และสร้าง map ก่อนใส่ battle targets
  5. กฎเฉพาะตัวละครแยกเป็น resources/BattleRules.json ระบุ revision ต้นทาง รองรับการเปิด/ปิด global passive จาก persistent ตามเดิม
  6. แปลง Battle SP จากสัดส่วน Cur/Max เป็นสเกล 10000 และเลิกบังคับ Monster MaxHP=10000 เมื่อค่าต้นทางเป็นศูนย์
  7. ลบการเดา Material IDs ด้วย 130000+AvatarID ซึ่งตรงกับ TryCreateItemData fail ใน Client และกัน automatic empty response ของ RogueTourn/ChenLing ที่มี NRE ใน log

ข้อจำกัด: log ตีมอนล่าสุดก่อนแก้แสดงว่าไม่พบเป้าหมายที่ผ่าน filter แต่ไม่มี incoming ID list จึงยังไม่ทราบเหตุที่จับคู่ไม่ผ่าน ส่วน capture Calyx ที่มีอยู่เป็นข้อมูลวันที่ 16 ต.ค. ไม่ใช่ทีมที่นำเข้าภายหลัง ชุด build รอบนี้เพิ่มข้อมูลที่จำเป็นต่อการแยกสองกรณีนี้

การคัดลอก config.json จาก PearlSR ไม่ได้ทำให้ Go อ่านไฟล์นั้น Go ยังคงใช้ freesr-data.json/persistent สำหรับทีมและ custom battle การใช้ custom stage กับดอกไม้มีในต้นฉบับเช่นกัน จึงไม่ถือเป็น regression ด้วยเหตุผลนั้นเพียงข้อเดียว

Build ผ่านและวาง gameserver-go.exe ที่ root แล้ว เปิด SDK/Game server พร้อมคอนโซลเพื่อให้ผู้ใช้ลองทั้ง Calyx และ overworld ผลใน Client ยังรอยืนยัน ไม่มีการแก้ข้อมูลทีมใน freesr-data.json

ผลลองจริง 11:53 และการแก้หน้าตัวละคร

  • 11:53:08 SceneCastSkill รับ assist target 30007 และจับคู่กับมอนในฉากได้ ส่ง response พร้อม BattleInfo ขนาด 2286 ไบต์ snapshot SHA-256 battle f9bb3cf79debf6678f9f466e917e6625e87793aa5fbaf275d90a3f2f39a1fe94 stage 30511021
  • 11:53:16 Client ส่ง PVEBattleResultCsReq และผู้ใช้ยืนยันว่าเข้าสู้ได้แล้ว ยังไม่ระบุผลดอกไม้โดยตรง
  • พบตัวขวางการลองเพิ่ม: เปิด C แล้วติดหน้าตัวละคร Log มี _SyncPlayerBoardData, Inventory _AddUniqueItems/_AddItem และ GetItemsInTypes
  • เทียบต้นฉบับพบ field 4 ของ GetPlayerBoardDataScRsp เป็น unlocked_personal_card_list แต่ Go ส่ง ID รูปโปรไฟล์; field 9 เป็น assist_avatar_id_list แต่ Go ส่ง ID การ์ด อีกทั้งไม่ได้ตั้ง HeadFrameInfo และ DisplayAvatarVec
  • ใน assembly จุด return address 0xdd02ad8 อยู่หลัง throw ที่ 0xdd02ad3 ซึ่งรับ branch จากการตรวจ ItemData+0x18 เป็น null ที่ 0xdd02841 สอดคล้องกับการส่ง ID ของผิดประเภท ยังไม่อ้างว่าเป็นข้อมูลอาวุธ/relic เสีย
  • แก้ profile fields ให้ตรง schema, อ่าน head icons จาก AvatarPlayerIcon.json และ owned cosmetics จาก ProfileConfig.json ที่ระบุ revision เติม ArchiveData จากข้อมูลผู้เล่นแทน null และไม่ตอบว่างให้ SocialPlay/PetMarble ซึ่งพบ exception เพิ่มในรอบใหม่
  • Build รอบโปรไฟล์ผ่านและแทนที่ root executable พร้อมเปิดเซิร์ฟเวอร์ใหม่แล้ว ผลหน้าตัวละครรอเปิดเกมใหม่ยืนยัน

ผู้ใช้ชี้แจงผลสำเร็จว่าเป็น Stagnant Shadow สำหรับวัสดุเลื่อนขั้นที่เรียกมอนออกมาจากเงาแล้วตี ไม่ใช่ผลทดสอบ Calyx หรือมอนทั่วไปทั้งหมด คง Calyx เป็นรายการที่ยังรอตรวจ

หลังเปิดเกมรอบ 11:56 ผู้ใช้ยืนยันอาการหน้าตัวละครหายแล้ว และ Player.log รอบใหม่ไม่พบ PlayerBoard/Inventory/Archive exceptions เดิม ยังพบ _OnGetCurBattleInfoScRsp ตอน login หนึ่งครั้ง ซึ่งต้องแยกจากผลสำเร็จของการตีมอน และยังไม่มี Calyx request ใหม่ใน log รอบนี้

ยืนยันผลท้ายรอบ

ผู้ใช้ตอบคำถามแยกเฉพาะ Calyx ว่า Calyx เข้าได้แล้ว จึงยืนยันผลลองใน Client ได้ทั้ง Calyx, Stagnant Shadow ผ่าน SceneCastSkill และการเปิดหน้าตัวละคร หลักฐาน Calyx ในส่วนนี้เป็นคำยืนยันโดยตรงของผู้ใช้ ไม่ใช่การอนุมานจาก build หรือจาก log ของ Stagnant Shadow ผลยังไม่ครอบคลุมโหมด challenge/มอนทั่วไปทุกชนิด และ login GetCurBattleInfo exception เป็นปัญหาที่บันทึกแยกไว้

ตรวจ log หลังคำยืนยันพบ Calyx request cmd 1305 เวลา 11:56:29 และ response cmd 1371 ขนาด 2279 ไบต์ capture เป็น StartQuickCocoonStageRsp stage 30511021, battle SHA-256 092528d29fc9bdfc025b89d8904d8a4f3ca706bb2cf777726b03f5a29d9233f7 จึงมีหลักฐาน response ของรอบเดียวกันประกอบคำยืนยันด้วย