Files
GaCy-GO/docs/GLM-GACHA-GUIDE.md
T

301 lines
25 KiB
Markdown
Raw Normal View History

2026-10-28 18:54:43 +07:00
# ข้อมูลและวิธีเพิ่มตู้กาชาให้ GLM — Client Beta ปัจจุบัน
อัปเดต: 24 ตุลาคม 2026 (Asia/Bangkok)
อ่านคู่กับ [GLM-BETA-DATA.md](GLM-BETA-DATA.md) และ [ZCODE-HANDOFF.md](ZCODE-HANDOFF.md)
## 1. เป้าหมายผู้ใช้และสถานะจริง
ผู้ใช้ต้องการเลือกตู้ตัวละคร โดยแต่ละตู้มีตัวเด่นหนึ่งตัว และผลสุ่มตรงกับตัวที่แสดงบนตู้ ไม่ได้ต้องการแค่หน้า Standard ที่เปิดดูได้แต่สุ่มไม่ได้
ตอนนี้ backend มีโค้ด featured-only sandbox สำหรับสุ่ม 1/10 ครั้ง และจัดการ duplicate material แต่ **ยังไม่มี featured banner ที่เปิดใช้งานจริง** Policy เปิดเพียง Standard เพื่อหลีกเลี่ยง Client crash จึงยังไม่ตรงคำขอ
เอกสารนี้ให้ข้อมูลที่ตรวจแล้วและลำดับพัฒนาต่อ ไม่ได้อ้างว่าการเพิ่ม path ใน JSON จะทำให้ตู้ใช้งานได้ หรือว่าดึง asset ที่หายกลับมาแล้ว
Client ที่อ้างอิงใน capture นี้: `GameAssembly.dll` SHA-256
```text
4aa08a0706f27d2cf9951b61920c179140ece9ea94f7e34422c18e46922939b6
```
เป็น Client Beta .54 ที่ตรวจ runtime จริง ไม่ใช้ hash/RVA ของ CW capture .53 แทน
## 2. ตารางตู้จริงที่ดึงไว้แล้ว
`resources/PreviewGameplayCatalog.json` → `tables.gacha` มี `GachaBasicInfoConfigRow` ครบ 6 แถวของ capture นี้:
| GachaID | GachaType | SortID | PrefabPath | ผลตรวจ asset |
| ---: | ---: | ---: | --- | --- |
| 1001 | 1 | 99 | `UI/Drawcard/GachaPanel/StandardGacha_1001.prefab` | มี |
| 4001 | 2 | 1 | `UI/Drawcard/GachaPanel/NoviceGacha_4001.prefab` | มี |
| 2001 | 11 | 2 | `UI/Drawcard/GachaPanelLimited/AvatarGacha_1102.prefab` | ไม่มี |
| 2002 | 11 | 3 | `UI/Drawcard/GachaPanelLimited/AvatarGacha_1204.prefab` | ไม่มี |
| 3001 | 12 | 4 | `UI/Drawcard/GachaPanelLimited/LightConeGacha_23001.prefab` | ไม่มี |
| 3002 | 12 | 5 | `UI/Drawcard/GachaPanelLimited/LightConeGacha_23010.prefab` | ไม่มี |
ชื่อ enum ที่พบใน Lua คือ `Normal`, `Newbie`, `AvatarUp`, `WeaponUp` การจับคู่เลขข้างบนมาจาก captured rows ไม่ใช่การสร้างเลขเพิ่ม
แถวเหล่านี้ยังเก็บ `PoolLabelIcon`, `PoolLabelIconSelected`, `PoolName`, `PoolDesc`, `TypeTitle` และ TextID/hash64 ให้ใช้ original numbers ห้ามอ่านผ่านเครื่องมือที่ทำ uint64 เกิน 2^53 สูญเสีย precision
**ข้อจำกัด:** แถวในตารางไม่ได้พิสูจน์ว่ามี bundle ใน installation ผล `AssetMgr.Exists` ยืนยันแล้วว่า limited ทั้ง 4 path ข้างบนไม่มี
หลักฐาน: `battle_diagnostics/gacha-asset-exists-20261024.log` มีผลครบ 6 path และ `END` Policy เก็บผลพร้อม hash ไว้ใน `resources/GachaSandboxPolicy.json`
Seele prefab ที่ไม่มีเคยทำ `UIPrefabLoader._HandleLoadResult` ArgumentNullException ระหว่าง `GachaMainPage._create_pools_panel` แล้วออกกลับโลกจอดำ ห้ามเพิ่มกลับ allowlist เพื่อให้หน้าตู้ดูเยอะขึ้นทั้งที่ asset ยังไม่มี
## 3. Generic/group prefab ที่พบใหม่ — ตรวจแล้วไม่มีเช่นกัน
ถอดจาก bytecode ของ GachaMainPage ปัจจุบัน พบ group presentation ที่มี path จริงดังนี้:
| Group enum และค่าที่ capture จริง | Path ที่อ้างถึง | ผลตรวจใน Client ปัจจุบัน |
| --- | --- | --- |
| `MultiAvatarUp=1` / `MultiWeaponUp=2` | `UI/Drawcard/GachaPanel/MultiAvatarGacha.prefab` | ไม่มี |
| `Collaboration=3` | `UI/Drawcard/CollaborationGacha/CollaborationGacha.prefab` | ไม่มี |
| `CollaborationMerge=4` | `UI/Drawcard/CollaborationGacha0440/CollaborationGacha0440.prefab` | ไม่มี |
หลักฐานรอบล่าสุด: `battle_diagnostics/glm-beta-targeted-metadata.log`
```text
ASSET UI/Drawcard/GachaPanel/MultiAvatarGacha.prefab true false
ASSET UI/Drawcard/CollaborationGacha/CollaborationGacha.prefab true false
ASSET UI/Drawcard/CollaborationGacha0440/CollaborationGacha0440.prefab true false
END
```
ใน log นี้ boolean แรกคือการเรียกตรวจสำเร็จ ส่วน boolean ที่สองคือผล Exists จึงหมายถึง **ตรวจสำเร็จและไม่มี asset** ไม่ใช่พบ asset แล้ว
Group path เป็นข้อมูลสำหรับเข้าใจโครงสร้าง UI ไม่ใช่ fallback ที่พร้อมเปิดใช้ อย่าเสนอว่าเปลี่ยนมาใช้ MultiAvatarGacha แล้วจะหายทันที
ผลตรวจ paths แยกเป็น machine-readable data ใน `battle_diagnostics/glm-gacha-group-assets.json` ส่วนผลอ่าน **ตาราง group จริง** ล่าสุดอยู่ใน `battle_diagnostics/glm-gacha-groups-current.json`: `BEGIN count=0` จน `END count=0` จึงเป็นตารางว่างที่อ่านจบแล้ว ไม่ใช่ยังไม่ได้ capture ห้ามสร้าง GroupID หรือ pool-to-group mapping โดยเอาค่า enum 1–4 มาใช้เป็น IDs
## 4. การเพิ่มตู้มีสองกรณี
### กรณี A: มี config และ asset ของตู้จริงครบใน Client
1. ใช้แถว `GachaBasicInfoConfigRow` และ group/type ที่มี provenance ตรงรุ่น ปัจจุบัน group table อ่านครบแล้วเป็น 0 แถว จึงไม่มี original group mapping ให้เปิดใช้ และยังต้องอ่าน type data ที่จำเป็นให้ครบ
2. ตรวจ prefab, tab icon ทั้งสองสถานะ และ dependency ที่ controller ใช้ ให้มีจริงใน installation
3. ประกาศตู้ผ่าน resource catalog และ verified policy ไม่ใส่ game ID/path เจาะจงลง handler
4. ส่ง GetGachaInfo ให้ใช้ ID เดียวกับแถวที่ Client resolve ได้ และ prize/detail data ตรงกับตัวที่หน้าตู้แสดง
5. ให้ DoGacha ใช้ banner mapping เดียวกัน ตรวจ request/count และจัดการ grant/sync/persistence ให้ครบ
6. ตรวจ entry/exit และ animation state จาก callback จริง ไม่แก้ด้วยการปิด Canvas หรือกล้องทุกตัว
**กรณีนี้ยังทำไม่ได้กับ limited 4 แถวปัจจุบัน เพราะผลตรวจยืนยันว่า prefab ไม่มี**
### กรณี B: ไม่มี original banner asset — ต้องทำ custom presentation
นี่เป็นทางพัฒนาที่ตรงคำขอได้ แต่ยังไม่มี implementation สำเร็จใน source ปัจจุบัน:
1. กำหนด extension catalog แยกจาก raw captured tables โดยระบุว่าเป็น custom presentation ไม่ปลอมว่าเป็น original Client data
2. ใช้ avatar/item metadata และภาพที่ตรวจว่ามีจริงเป็นรายตัว ต้องแสดงชื่อ/ภาพตรงกับผล draw ห้ามใช้ภาพ Standard ค้างไว้แต่สุ่มเป็นตัวอื่น
3. ทำ UI/controller/binder ที่ใช้ asset ที่มีจริง อาจประกอบจากส่วนย่อยที่ตรวจแล้ว หรือทำ presentation ใหม่ที่เข้ากับระบบ UI; ไม่ใช่เพียงเปลี่ยน `Row.PrefabPath` เป็น Standard แล้วคาดว่า limited binder จะทำงาน
4. ต่อ pool/group/tab data และ selection state เข้ากับ Client โดยใช้ชนิด/enum/schema ที่ยืนยันแล้ว เก็บ custom ID mapping ใน resource ที่ตรวจความถูกต้องได้
5. ให้ Client และ server resolve ตู้เดียวกันจาก catalog เดียวกัน ก่อนรับการสุ่ม
6. ส่งผล Item/GachaItem/duplicate materials ให้ครบ และคืน transition กลับโลกเมื่อปิด
การส่ง GachaID ใหม่ที่ Client ไม่มี config **ไม่พอ**: Client ต้องสร้าง/resolve pool, type และ group ได้ด้วย การส่ง GachaID 1001 ซ้ำหลายแถวจะชน key ไม่ได้กลายเป็นตู้แยกทุกตัวละคร
ห้ามใช้สูตร `bannerID = avatarID + ค่าคงที่` หรือเดา path ของทุกตัวละครขึ้นเอง แล้วอ้างว่าเป็นตาราง Beta ให้กำหนด custom extension อย่างชัดเจนถ้าจำเป็นต้องสร้าง namespace ใหม่
## 5. สิ่งที่ Client ใช้สร้างหน้า — มีหลักฐานแล้ว
`battle_diagnostics/gacha-main-page.json` เป็นผลอ่าน bytecode ที่ไม่ execute ของ:
```text
@BakedLua/Ui/Gacha/GachaMainPage.bytes
```
### `_create_pools_panel` — prototype 26 / source line 308
ลำดับชื่อที่พบใน bytecode:
```text
GetSortedGachaGroupDatas
IsValid / IsFeatureClosed / IsSinglePoolGachaGroup
DefaultGachaPoolData.PrefabPath
sync_load_prefab
_gachaType2tabItem
create_panel / set_tab_data
```
แยกสองเส้นทาง:
- Single-pool group: โหลด `DefaultGachaPoolData.PrefabPath` แล้วสร้าง tab item ด้วย panel/binder ตาม `GachaType`
- Group: เลือกจาก `_groupID2GroupPrefab` / `_groupType2GroupPrefab` และ `_groupID2TabItem` / `_groupType2TabItem` พร้อม binder ของ group
ไม่ควรดูเพียง GetGachaInfoScRsp แล้วมองข้าม group data ที่หน้า UI ใช้จริง
### ฟังก์ชันสำคัญอื่น
- `_init_type2tab_table` prototype 8: ผูก `Normal`, `AvatarUp`, `WeaponUp`, `Newbie` กับ tab item/binder
- `_init_group_tab_prefab` prototype 9: กำหนด group prefab/controller/binder maps
- `_get_group_tab_prefab_config` prototype 10: เลือก override ด้วย GroupID แล้วจึง GroupType
- `_on_group_tab_change` prototype 31 และ `_on_tab_change` prototype 32: cache selected pool และ setup main page
- `_setup_main_page` prototype 33: setup draw/decide/top bar และ `Row.PoolName` ผ่าน `SafeSetTextID`
### Native/data types ที่ตรวจพบ
`BaseGachaPoolData` มี:
- `Row` getter/setter ชนิด `GachaBasicInfoConfigRow`
- `TypeBasicInfoRow`, `PrizeItemDatas`, `DetailItemDatas`
- `SingleDrawData`, `MultiDrawData`, `GachaTokenData`, `DecideData`
- `SyncGachaPoolData`, `_InitPrizeItemsRow`, `_InitDetailItemsRow`, `IsValid`, `IsFeatureClosed`, `PrefabPath`
`BaseGachaGroupData` มี:
- `_GachaGroupDataRow`, `_GachaPoolDataList`, `GroupID`, `GroupType`
- `AddGachaPoolData`, `GetSortedGachaPoolData`, `Sort`, `IsValid`, `IsFeatureClosed`, `HasGachaPool`
Capture ล่าสุดยืนยัน **shape** ของ `RPG.GameCore.GachaGroupDataRow`:
```text
PoolLabelIcon System.String
PoolLabelIconSelected System.String
GachaIDList System.UInt32[]
GroupType RPG.GameCore.GachaGroupType
GroupID System.UInt32
FromBinary(reader, GachaGroupDataRow byref)
```
ต่อจาก metadata ได้ตาม native chain และอ่าน flat GetAll ของตารางนี้ครบแล้ว:
```text
GachaGroupDataRow.FromBinary RVA 0x1e853750
-> wrapper RVA 0x1e853700
-> Add dictionary RVA 0x1e853950
-> flat GetAll RVA 0x1e852f20
BEGIN count=0
END count=0
```
หลักฐาน `ev_551674df9f28f4fc92485a8b8f3490795a8296564ee586fe356b16bca00e0b8b`, ข้อมูล `battle_diagnostics/glm-gacha-groups-current.json` และ assembler files `glm-gacha-group-add.asm`, `glm-gacha-group-dictionary.asm`, `glm-gacha-group-flat-getall.asm` ผูกกับ SHA ปัจจุบัน
**ตาราง group ที่ตรวจนี้ว่างจริง ณ capture** ไม่ได้แปลว่า Lua/UI ไม่มีโค้ดรองรับ groups แต่ไม่มี GroupIDs หรือ GachaIDList ให้ใช้เป็น original mapping อย่าอ่านค่า enum เป็น GroupID หรือเอาข้อมูลของ reference รุ่นเก่ามาเติมแล้วเรียกว่า Beta ปัจจุบัน
RVAs ข้างบนใช้บันทึกเส้นทางหลักฐานของ assembly hash นี้เท่านั้น ไม่ใช่ค่าที่ให้ฝังใน production MethodHandle/addresses ใน log ใช้ได้เฉพาะโปรเซสที่ capture ห้ามนำ pointer เดิมกลับไปใช้หลังเปิดเกมใหม่
`GachaTypeBasicInfoRow` มี `GachaTypeID`, `GachaBar`, `DiamondID`, `ItemPrice`, `ItemCosume`, `UpPropability`, `BuyPos` ต้องอ่านค่าจริงของ type ที่ใช้ ไม่เดาค่า currency/token หรือราคา
### Lua modules ที่ควรอ่านต่อแบบเจาะจง
- `Ui.Gacha.GachaPool.DefaultGachaPoolTabItem` และ binder
- `Ui.Gacha.GachaPool.DefaultGachaAvatarGroupTabItem` และ binder
- `Ui.Gacha.GachaPool.GachaSubAvatarGroupPanel`
- `Ui.Gacha.GachaPool.GachaSubAvatarItemIconPanel`
- `Ui.Gacha.GachaPool.DefaultGachaAvatarPanel`
- `Ui.Gacha.GachaPool.DefaultGachaUpAvatarPanel`
UpAvatarPanel มี `_init_imgs`, `_setup_up_avatar_img`, `setup_view`, `_on_load` แต่การมีชื่อ module ไม่ได้พิสูจน์ว่ามี prefab ที่ใช้งานได้
## 6. Protocol fields ที่ใช้อ้างอิงได้
ต้นฉบับ `protocol/proto/StarRail.proto` แถวประมาณ 20415 เป็นต้นไป:
| Message | Field ที่มีชื่อชัด | Wire tag |
| --- | --- | ---: |
| GachaInfo | end_time / gacha_id / gacha_ceiling / begin_time | 1 / 2 / 3 / 5 |
| GachaInfo | prize_item_list / item_detail_list | 10 / 11 |
| GetGachaInfoScRsp | retcode / gacha_info_list | 1 / 15 |
| DoGachaCsReq | gacha_id / gacha_num | 4 / 15 |
| DoGachaScRsp | retcode / gacha_item_list / gacha_num / gacha_id | 1 / 2 / 6 / 13 |
| GachaItem | transfer_item_list / is_new / token_item / gacha_item | 5 / 7 / 10 / 15 |
`GachaInfo` ยังมี fields obfuscated และ GetGachaInfoScRsp มี `AKMDBPNEHDE` ชนิด `NEIMLKNMDBM` อย่าอ้างว่าเป็น group payload แน่นอนหรือเติม tag ตามการคาดเดา ต้องตาม native consumer ก่อน
ตาราง command ที่เช็กใน source อยู่ที่ `protocol/proto/upstream_cmdids.go`:
```text
CommandSourceRevision = 005d2c12db4781071f4b5b68e8f364587886a710
GetGachaInfoCsReq 1974 -> GetGachaInfoScRsp 1919
DoGachaCsReq 1931 -> DoGachaScRsp 1987
```
เลขนี้ให้เป็นหลักฐานอ้างอิงในเอกสารเท่านั้น implementation ต้องใช้ named resolver ของโปรเจกต์ ห้ามฝังตัวเลขแทนชื่อหรือรับ aliases หลายรุ่น และ source revision ไม่ใช่หลักฐานว่า native schema .54 ทุก field ยืนยันครบแล้ว
## 7. โค้ด backend ปัจจุบันและการแก้ต่อ
### จุดอ่าน catalog
`internal/common/gacha_catalog.go`:
- `LoadBrowsableGachaBanners` ใช้ verified prefabs สำหรับ GetInfo
- `LoadFeaturedGachaBanners` ยอมรับเฉพาะ basename `AvatarGacha_<avatarID>.prefab` ที่อยู่ใน policy
- ไม่สร้าง GachaID หรือ prefab paths เพิ่มเอง
- ปัจจุบันพบ Standard ได้ แต่มี `AvatarID=0` จึงไม่เปิด featured draw
ข้อควรระวังสำหรับ custom presentation: logic ที่อนุมาน avatar จาก basename ใช้กับ extension catalog ใหม่ไม่ได้เสมอไป ต้องออกแบบ explicit mapping พร้อมตรวจ provenance/asset availability แทนการเปลี่ยน regex ให้รับทุกชื่อ
### จุดตอบ GetInfo/Draw
`internal/gameserver/handlers_gacha.go`:
- GetInfo ใช้ activity schedule จาก runtime config ส่ง non-null `GachaCeiling`
- ถ้ามี featured avatar จึงส่ง prize/detail lists
- DoGacha ตรวจ banner/count และ reject ระหว่าง Battle
- ต้องมี avatar ใน imported roster และมี Eidolon resource ที่ตรง
- duplicate ให้ material ตาม configured first Eidolon cost ไม่ใช่ตัวละคร/ไอเท็มสลับมั่ว
- ส่ง material delta ใน PlayerSync และ GachaItem ตามจำนวน draws
### Persistence ที่ต้องรักษา
`GrantGachaDuplicate` อ่าน latest `freesr-data.json` ใต้ shared process/file locks แล้ว merge เฉพาะ materials ไม่สร้าง avatar ใหม่ ไม่เปลี่ยน rank/EX/Traces ไม่เขียน inventory ทับทั้งก้อน
Sandbox policy ปัจจุบัน:
```text
allowed_draw_counts: 1, 10
charge_currency: false
duplicate_reward: configured_first_eidolon_cost
```
นี่ไม่ใช่ official probability/price model หากจะเปิด acquisition ของ avatar ที่ยังไม่มี ต้องทำ valid avatar/item sync และ targeted persistence เพิ่ม ยังไม่มีเส้นทางนั้นในโค้ดนี้
ไม่ควรเปลี่ยน DoGacha ให้ตอบ success พร้อมผลว่างเพื่อให้ spinner หยุด เพราะจะไม่ตรงจำนวน draws และ state ของ Client
## 8. อ่าน bytecode ต่ออย่างไร
Client นี้ใช้ `xluau.dll` มี Luau v4/type1 bytecode ไม่ใช่ LuaJIT:
- `string.dump` ไม่มี และ `jit.util` ใช้ไม่ได้
- มี `debug.getupvalue`, `debug.getinfo`, `debug.info`, `package.loaders`
- รอบที่เก็บ GachaMainPage ใช้ package loader เพื่อรับ closure โดย **ไม่ execute closure และไม่แทน package.loaded** แล้ว passive hook `xluaL_loadbuffer` เก็บ bytes
- อย่าเอา callback/module execution ไปปนกับการอ่านข้อมูล หรือ reload หน้าขณะผู้ใช้สู้
เก็บไว้แล้ว:
```text
battle_diagnostics/gacha-main-page.luau
battle_diagnostics/gacha-main-page.hex
battle_diagnostics/gacha-main-page.json
tools/inspect_luau_v4.py
```
ตัวอย่างอ่านไฟล์ capture offline จาก root โปรเจกต์:
```text
python tools/inspect_luau_v4.py battle_diagnostics/gacha-main-page.luau --output battle_diagnostics/gacha-main-page.json
```
Parser อ่าน version/type ที่รองรับเท่านั้น และตรวจว่าใช้ bytes ครบ ไม่ execute chunk เป็นเครื่องมือวิเคราะห์ ไม่ใช่ gameplay test และไม่ใช่ decompiler ที่ยืนยันความหมายทุกคำสั่ง
Layout reference: Luau upstream `VM/src/lvmload.cpp`, `Common/include/Luau/Bytecode.h` สำเนาอยู่ใน diagnostics ประกอบการตรวจ format
REA evidence ของ capture: `ev_57f147cb094af2060be82f5110e378d25ff2179f0390c56348368a855e7658b8` ขนาด GachaMainPage 23630 bytes ไม่มี message truncation ใน capture นี้
ทุก `.lua` ใน `scripts/` ถูกส่งเข้า Client อัตโนมัติ temporary probes ต้องอยู่ใน `tools/` เมื่อไม่ได้ใช้งาน จำกัดเฉพาะ types/functions ที่ระบุและ pause ระหว่าง Battle ห้ามสแกนทุก assembly type/method บน main thread
ณ จุดส่งต่อ probes ที่ใช้รอบนี้ถูกถอดจาก `scripts/` และ Frida sessions ปิด/detach แล้ว ไม่ได้ kill Client
## 9. เช็กลิสต์รับงานต่อ
- [ ] อ่าน package/manifest คู่กับ GLM-BETA-DATA เพื่อไม่ปน .53 และ .54
- [ ] ตัดสินทาง custom presentation โดยยอมรับว่า limited และ generic group prefabs ที่เช็กทั้งหมดไม่มีใน installation นี้
- [ ] ใช้ผล group table ที่อ่านจบแล้ว 0 แถวเป็นข้อจำกัดของ original config; อ่าน type data และ consumer ของ fields obfuscated ที่ยังจำเป็น ก่อนออกแบบ custom mapping
- [ ] ทำ explicit banner/avatar/presentation mapping ให้ Client และ backend ตรงกัน
- [ ] ตรวจ selected portrait/tab/icon/material dependencies เป็นราย resource ไม่เดาชื่อไฟล์
- [ ] รองรับ 1/10 draws และ duplicate/acquisition ตามขอบเขตที่ประกาศ
- [ ] รักษา progression/EX false/material delta และป้องกัน stale session ทับ API
- [ ] ทำ close/exit ให้คืน world/UI transition ไม่ค้างดำ
- [ ] ตามคำสั่งผู้ใช้ล่าสุด: ไม่เพิ่มหรือรัน tests และไม่วนให้ผู้ใช้ลองทีละ handler; ระบุส่วนที่ยังไม่มี Client confirmation ตามจริง
**จุดสรุปให้ GLM:** Backend ส่วนหนึ่งพร้อมแล้ว แต่การเปิดตู้ตัวละครจริงใน Beta นี้ติด presentation/config mapping และ assets ที่ไม่อยู่ใน installation การมี GachaInfo response หรือเพิ่ม allowlist อย่างเดียวไม่ใช่การทำฟีเจอร์เสร็จ