feat: Add metro transit, gadgets, shop, social apps, and traffic models checkpoint

This commit is contained in:
CAtlas0784 committed 2026-10-29 01:55:01 +07:00
1 parent caa42b84ed
commit 74e2963a12
79 files changed
+527962 -496

No files matched your search

+890
View File
@@ -0,0 +1,890 @@
# Devlog 2026-09-19 — Metro + City Traffic Engine (AI Handoff)
> เอกสารส่งมอบงานสำหรับ AI/ผู้พัฒนาตัวต่อไป — อ่านจบแล้วควรจับงานต่อได้โดยไม่ต้องถามเรื่องบริบท
## สถานะปลายวัน (TL;DR)
| ระบบ | สถานะ |
| :--- | :--- |
| Metro / Light Rail (4 สายวนเมือง) | ✅ **ใช้ได้จริง ยืนยันในเกมแล้ว** (ผู้ใช้เห็นรถไฟวิ่ง) |
| City Traffic — client-driven ambient (MassTraffic) | 🟡 โค้ดครบ build ผ่าน แต่**ยังไม่ยืนยันว่า client ปล่อยรถ** (รอเทส) |
| City Traffic — server-driven fallback (IDM + NPC คนขับ + สีรถ) | 🟢 โค้ดครบ build ผ่าน (ปิดอยู่ เปิดด้วย `ServerSpawnedVehicles=true`) |
| NPC Spawner POI picker | ✅ ครบ ~30 สถานะจาก `UrbanDiversityPOIActionConfig` |
| Gacha ตั๋ว | ✅ ปุ่ม "เสกตั๋วกาชาทั้งหมด" (auto หา id จาก GachaPoolConfig) |
| WebUI คัทซีนรถไฟตอนวาร์ป | ✅ ลบแล้ว (วาร์ปตรง) |
Build: `dotnet build AnantaPS.sln -c Debug` → **0 error 0 warning** (ยืนยันล่าสุด)
## ไฟล์ที่แตะวันนี้
- `Ananta.Gameplay/Traffic/CityTrafficEngine.cs` — เขียนใหม่ทั้งไฟล์ (IDM + ไฟแดง + Aether + จัดการ session ศพ)
- `Ananta.Gameplay/Traffic/ITrafficVehicleDriver.cs` — เพิ่ม SpawnAetherVehicleAsync / SendAetherLaneDatasAsync / Spawn/RemoveAetherVehicleNpcAsync / SendAetherIntersectionUpdateAsync
- `Ananta.Handlers/Game/TrafficAndCrowdDriver.cs` — implement ช่องทางใหม่ทั้งหมด (+ SyncTeleportVehicle channel เดิม)
- `Ananta.RpcTypes/.../GameScene/AetherVehicleMethods.cs` — **ไฟล์ใหม่**: DTO ทั้งชุด (ClientVehicleInitData4229938, ClientVehicleLaneData4229938, ClientVehicleNpcInitData4229938, ฯลฯ)
- `Ananta.RpcTypes/.../GameScene/VehicleMethods.cs` — AetherAIInitData แก้ให้ตรง client จริง (4 field: header + Intersections)
- `Ananta.Core/Protocol/Client4229938/MethodId.cs` — เพิ่ม `SyncAetherAIIntersectionUpdateData = 68819947` (ตัวอื่นมีอยู่แล้ว ค่าตรง dump)
- `Ananta.Core/ClientData/Client4229938/RailCatalog4229938.cs` + `VehicleSpawnCatalog4229938.cs` — **ไฟล์ใหม่** (catalog ตารางรถไฟ + ตาราง spawn รถ/คนขับ)
- `Ananta.Core/ClientData/Client4229938/PoiActionCatalog4229938.cs` — เพิ่ม Name + All()
- `Ananta.Core/ClientData/Client4229938/EconomyConfigRepository.cs` — เพิ่ม `GachaTicketItemIds()`
- `Ananta.Handlers/Game/GameRouter.Metro.cs` — **ไฟล์ใหม่**: AskGetAllMetroInfos + push loop SyncRunningMetroInfos
- `Ananta.Handlers/Game/GameRouter.Vehicles.cs` — ลบ stub AskGetAllMetroInfos เก่า, init Aether ใส่ intersections
- `Ananta.Handlers/Game/GameRouter.Npc.cs` — ส่ง init Aether พร้อม intersections
- `Ananta.App/DebugApiServer.cs` — `/api/gacha/grant-tickets`, config `useAetherProtocol`/`moveReset`, POI ไดนามิก
- `Ananta.App/DebugPanel/index.html` — ลบคัทซีนรถไฟ, POI picker ยิงจาก API
- `Ananta.Gameplay/Traffic/RoadNetworkModels.cs` — TrafficSignalFile เพิ่ม Intersections
- ข้อมูลใหม่: `ClientData/4229938/Configs/Rail{Line,Stop,Station,Train}Config.json`, `VehicleSpawnConfig.json`
## ความรู้เชิงเทคนิคที่ค้นพบ (สำคัญมาก — อย่าทำพัง)
1. **UxSerializer อ่านเฉพาะ public FIELD** (ไม่ใช่ property) เรียงตามลำดับประกาศ — DTO ที่ใช้ property จะ serialize เป็น object เปล่า (บัคเดิมของ MetroClientInfo)
2. **นาฬิกา server วิ่งเร็วกว่าจริง 15 เท่า** — metro `ElapsedTime = เวลาตาราง(วินาทีจริง) × 15` ไม่งั้นขบวนคลานเต่า
3. **Aether lane handle = index ใน `ZoneGraphStorage.Lanes` (flat array)** — ZoneLaneData ไม่มี field handle; mapping ของเรา: `handle = lane.Id - base ของ region` (city base=0 → handle = lane id) — **ยังไม่ยืนยันในเกม**
4. `LaneHandle`/`DistanceAlongLane` อยู่ทั้งใน `ClientVehicleInitData` (spawn) และ `ClientVehicleLaneData` (sync ต่อเนื่อง, status flags: Normal=0, Teleport=4, StandBy=8)
5. คนขับรถจริง = NPC form **45200091**, ผู้โดยสาร = **45200093** (จาก `VehicleSpawnConfig.json` — มี ColorMod 9994xxxx เป็นสีรถ)
6. `MassTrafficIntersectionState`: Start=0, Transition=1, Finishing=2; เฟสไฟของเรา: period 0 = กลุ่ม zebra คู่ (t∈[0,17)), period 1 = กลุ่มคี่ (t∈[17,30)) — cycle 30s, offset = |inter×7919|%30
7. ทุก Aether packet ได้รับการยืนยันว่าออกจริง: packets log รอบทดลองมี AddData 1,594 + LaneDatas 9,034 รายการ — **client ยังไม่ render** = ปัญหาอยู่ฝั่ง parse/gating ของ client
8. UxContract: complex = 0xFF marker, Inline = ไม่มี marker, struct list ใช้ `ItemObjectEncoding = Struct`
## ปัญหาค้าง #1: รถ ambient (client-driven) — ยืนยันแล้วว่าปลุกได้ แต่ตำแหน่ง/โซนผิด
**ความคืบหน้าสำคัญ (ทดลองคืนเย็นวันนี้):** ส่ง init แยกงเขต 37 จุด (ZoneIndex=0 ทุกแยก) → **client ปล่อยรถ ambient ออกมาเป็นครั้งแรก!** แต่:
- รถเกิด**ซ้อนทับกันที่จุดเดียว** จนฟิสิกส์เด้งเละ (ไม่ใช่รถติดธรรมดา)
- **ไม่มี AI ขับ** เลย (รถตั้งอยู่กับที่/เด้ง)
**สมมติฐานเรียงตามความน่าจะเป็น:**
1. `ZoneIndex=0` ที่เราส่งทุกแยก**ผิด** — client ใช้ InstanceId+ZoneIndex ไป resolve แยกกับ zone graph ของตัวเอง; ผิด → จุด spawn เด不堪ใช้ → รถซ้อนกันที่เดียว ต้องหา **ZoneIndex จริงของแต่ละแยก** (จากตำแหน่งไฟใน TrafficSignals + Bounds ของ ZoneData ใน zonegraph JSON หรือจาก ZoneInfos blob)
2. Client ต้องการ**ตาราง period ของตัวเอง** — เราส่งแค่ index 0/1 ที่สมมติขึ้น; ตารางจริงน่าจะอยู่ใน `RPCSerializeAuto.lua` (ยังไม่ได้เปิด!) หรือ client คำนวณเองจาก zebraConnector
3. InstanceId อาจต้องเป็น handle อื่น (ไม่ใช่ id แยกจาก interInfos)
**สวิตช์ที่เพิ่มไว้ (ปรับสดผ่าน POST /api/traffic/config):**
- `ambientIntersections` (default **false** — ค่าเริ่มต้นปลอดภัย ไม่มีรถระเบิด; ใส่ init แยกงเขตเมื่อเปิด)
- `serverVehicles` (default false — รถ server-driven AddData+คนขับ+สี)
- `pushIntersectionUpdates` (default false — อย่าเปิดจนกว่าจะรู้ semantics ของ period)
- `useAetherProtocol`, `moveReset` (โหมดเก่า)
**แผนเซสชันหน้า:** (a) หา ZoneIndex จริงต่อแยก → ทดสอบ ambient อีกรอบ (b) เปิดดู `RPCSerializeAuto.lua` หา writer ของ SyncAetherAIInitDatas/IntersectionUpdateData เทียบ wire (c) probe แบบรถเดี่ยว: เปิด `serverVehicles=true` สแปม 1 คัน handle=0 distance=0 ดูว่าโผล่ตรงไหน → ถอด mapping จริงของ LaneHandle (d) ถ้าไหนไม่ผ่าน ถอด hex จาก `logs/packets-latest.log` เทียบ
## ปัญหาค้าง #0 (เดิม): AddData รถ server-driven มองไม่เห็น
รอบก่อน AddData+LaneDatas ยิง 1,594/9,034 แพ็กเก็ตแต่ client ไม่ render — **แต่ตอนนี้รู้แล้วว่า client render รถ Aether ได้** (ambient โผล่จริง) ดังนั้นโอกาสสูงที่ AddData ของเรา wire ผิดจุดเดียว (field order ของ ClientVehicleInitData ที่ flatten เอง หรือ LaneHandle) — หลังแก้ ZoneIndex/handle แล้วให้ทดสอบซ้ำ
## ปัญหาค้าง #2: Connection lost ตอนวาร์ป/สลับซีน
สงสัย client crash ระหว่าง reload ระบบ Aether/Metro กลางแพ็กเก็ต — รอยืนยันอาการหลังลบคัทซีนรถไฟออกจาก WebUI (รอบก่อนเด้งตอนเล่นคัทซีน + วาร์ปพร้อมกัน) ถ้ายังเด้ง: ลองหยุด push traffic/metro ชั่วคราวตอน scene switching (hook ที่ `GameRouter.RaidSwitch/SceneSwitch`)
## เส้นทางสำรองที่พร้อมใช้
- โหมด server-driven รถ: `POST /api/traffic/config {"ServerSpawnedVehicles": true}` (รถ spawn เป็น Aether vehicle + คนขับ + สี) — ถ้า ambient ไม่เคยขึ้น ให้ใช้เส้นนี้เป็นหลักแล้วไล่ debug wire ต่อ
- โหมด legacy scene-entity: `{"useAetherProtocol": false}` (SyncSpawnVehicle + SyncVehicleMove/SyncTeleportVehicle — รถกระตุก ใช้สำรองสุดท้าย)
## แหล่งข้อมูลอ้างอิง (บนเครื่องนี้)
- `C:\Users\Phitchayut\Desktop\Anangay asset\readable_world_dump_4229938_final\` — ดัมพ์ world (06_RUNTIME_CODE = โค้ด client ที่ถอดแล้ว, 01_CONFIGS = config จริง, 00_INDEX = ดัชนี+คู่มือ NPC_TRAFFIC_GUIDE_RU.md)
- `C:\Users\Phitchayut\Desktop\Anangay asset\Assembly-CSharp-ULTIMATE\` — **IL2CPP dump ใหม่ล่าสุด (ใช้ตัวนี้ก่อนเสมอ)** มี dump.cs + ux_rpc/
- `C:\Users\Phitchayut\Desktop\pc_dump_new\dump.cs` — dump รวม (เก่ากว่า แต่ใช้ได้)
- `C:\Users\Phitchayut\Desktop\Anangay asset\PrivateServer修改_20260918\` — เซิร์ฟเวอร์อื่น (มี Metro ที่ใช้ได้ = ต้นแบบที่พอร์ตมา; ไม่มี traffic)
- Lua ของ client: `Ananta lua server base original\LuaGen\AutoGen\RPCSerializeAuto.lua` (wire order ที่ client ใช้จริง — ยังไม่ได้เปิดตรวจ!)
## 🎯 บัคตายตัวถูกพบ (ท้ายวัน): ลำดับ field ผิดทั้ง packet
**+ บัค connection lost ตัวจริงถูกพบด้วย (จาก log 02:56):** `RuntimeLogs.AppendToPair` throw IOException เมื่อไฟล์ `packets-*.log` ถูก process อื่นล็อก (viewer/proxy) → exception ลอยเข้า RPC pipeline ตอน NotifyAsync → **session ตาย = "connection lost"** ไม่เกี่ยวกับ traffic/metro เลย! แก้แล้ว: log write เป็น best-effort (try/catch swallow) ใน RuntimeLogs.AppendToPair — logging ตายได้ เกมต้องไม่ตาย
เปิด `RPCSerializeAuto.lua` ตัวจริง (line 2600) — **`WriteClientVehicleInitData` ใส่ field ของ base class (Id/Position/Facing/EulerAngles) ท้ายสุด ไม่ใช่ต้น** และ `ControlType` เป็น WriteByte ไม่ใช่ Int32! DTO ของเราเรียงแบบ C# inheritance (base first) → wire พังทั้ง packet = เหตุผลที่ AddData มองไม่เห็นมาตลอด
**แก้แล้ว**: `ClientVehicleInitData4229938` เรียงใหม่ตาม Lua (derived first, base last, ControlType=byte) + เปิด `ServerSpawnedVehicles=true` เป็นค่าเริ่มต้น — **Build ผ่าน รอเทสรถในเกม**
**กฎใหม่สำหรับทุก DTO ที่มี base class**: ต้องเช็คลำดับจาก RPCSerializeAuto.lua เสมอ — Lua ของ client อยู่ที่ `Anangay asset\Ananta lua server base original\LuaGen\AutoGen\RPCSerializeAuto.lua` (writers ตระกูล Aether อยู่บรรทัด ~317 + 2600-2660; LaneData/NpcInitData/InitData ของเราตรงหมดแล้ว)
**ยังเปิดค้าง**: ambient MassTraffic (ยังเละ — ต้องหา ZoneIndex จริง + อ่าน logic ไฟแดงใน Lua เพิ่ม), UI translator port (ดู roadmap)
## ✅ ยืนยันจากไฟล์ "All lanes" (คนทำ traffic สำเร็จส่งมา) — ค่ำวันนี้
`Anangay asset\All lanes\lanes\`:
- **`handle-encoding.txt` = "index;0"** → **LaneHandle = index ของเลน** (ยืนยันจาก HUD บนเซิร์ฟเวอร์ที่ใช้ได้จริง) — mapping ของเรา (city base=0 → handle = lane.Id) **ถูกต้องแล้ว**
- **`lane-probe.txt`** = probe จากในเกมจริง (AetherCityManager): vehicle graph **14,171 lanes** / 140,786 pts / 71,410 links; lane[0] w=5.00 pts=0..20 links=0..4 zone=0 — **ตรงกับ city lane 0 ของเราทุกค่า**; นับ region ใน RoadNetwork.json: **city=14171** (ตรงเป๊ะ!), airport=15994, longqi=11963 (แยกกราฟตาม raid)
- **`DataHandle (root+0x6C) = 0`** ใน client จริง แต่ grep ทั้งสอง runtime config ไม่มี key นี้ = ทั้งเราและเขาส่ง default `-1044702623` เหมือนกันแล้วเขาใช้ได้ → **DataHandle ไม่ใช่ตัวการ**
- CSV ครบ: `vehicle-lanes.csv` (Handle;Index;Width;ZoneIndex;...;FirstX/Y/Z;LastX/Y/Z), `vehicle-lane-links.csv`, `pedestrian-*` (88,361 sidewalk lanes — ไว้ทำคนเดินเกาะทางเท้าจริงภายหลัง)
- **เหลือตัวสงสัยสุดท้าย = หน่วย Timestamp** → ทดสอบ 3 ปุ่มใน Traffic Lab (WebUI) แล้ว re-enter world; หรือใช้ `RPCSerializeAuto.lua` อ่าน Logic ฝั่ง client เพิ่ม (ไฟล์อยู่ `Anangay asset\Ananta lua server base original\LuaGen\AutoGen\`)
## 📋 แผนปิดจ็อบ traffic (สถานะล่าสุด — ผูกกับ commit นี้)
**เสร็จใน commit นี้แล้ว:**
- ✅ wire order `ClientVehicleInitData` แก้ตาม `RPCSerializeAuto.lua` (derived first, base last, ControlType=byte) — hex 89b ตรงสูตร
- ✅ log เขียน best-effort (ไม่ฆ่า session อีก)
- ✅ F บนรถ traffic/NPC → ข้าม flow ขึ้นรถ (ไม่เด้ง) — hijack จริงไว้ roadmap
- ✅ **เปลี่ยน spawn เป็น batch `SyncAetherAIVehicleAddDatas` (68571034) + NPC bind ตามหลัง** (ไม่มี single spawn อีกต่อไป)
- ✅ id base `750_000_100_000` ตาม HUD เซิร์ฟเวอร์ที่ใช้ได้
- ✅ Traffic Lab ลบจาก WebUI; `timestampMode` (0=unix,1=×15,2=session,3=÷15) กับ `serverVehicles`/`ambientIntersections` ยังเรียกผ่าน POST /api/traffic/config ได้
**ลำดับทดสอบเซสชันหน้า (relog หลังแก้ทุกครั้ง):**
1. เข้าเกมปกติ → มีรถไหม (batch + id base ใหม่ + wire ใหม่ = ควรเห็น)
2. ไม่เห็น → curl timestampMode 3 → 2 → 1 (ดู devlog หัว Timestamp สำหรับเหตุผล pending-buffer)
3. ยังไม่เห็น → สลับ `ambientIntersections:true` เทียบว่า client ปลุกระบบเองหรือไม่
4. จับ hex แพ็กเก็ต AddDatas ใหม่จาก `logs/packets-latest.log` เทียบ `WriteClientVehicleInitData` ทีละ field อีกรอบ (list ใหม่มี marker/count ต่างจากเดิม — จุดเสี่ยงใหม่ที่ควรตรวจ)
5. ถ้าทุกอย่างถูกแต่ยังไม่ render → อ่าน logic จริงฝั่ง client ต่อ: `ux_game_all.dump.cs` (impl stub ที่ 150532) + หา body เต็มจาก DecryptionVault (ถอด Lua/IL2CPP จาก L50)
**ห้ามลืม:** รถไฟใช้ได้จริงแล้ว — อย่าทำพังตอนแก้อย่างอื่น | `MetroClientInfo`/`PoiActionCatalog` เป็น **field** ไม่ใช่ property | ทุก DTO ใหม่ต้องเช็คลำดับจาก `RPCSerializeAuto.lua` ก่อนส่งจริง
**หลักฐานใหม่ค่ำนี้ (ตัดตัวแปรออกหมดแล้ว):**
- fastpatch ของเซิร์ฟเวอร์ที่ traffic ใช้ได้ = **3 ไฟล์แปล UI ล้วน** (`changelog.txt`, `ClientGameSwitch.lua`, `UidLayerPanelStore.lua`) → **ไม่ต้องแพตช์ client** — ทางส่งฝั่งเซิร์ฟเวอร์อย่างเดียวพอ
- `rpc_serializer.js`/`rpc_schema.json` ของทีมจีน **ไม่มี Aether** = ไม่มีตัวอย่างไบต์สำเร็จรูป ต้องพิสูจน์เอง
- `RPCDeserializeAuto.lua` ไม่มี reader ของ Aether → **S2C ถูกอ่านด้วย C# ฝั่ง client** → คำตอบ framing ที่แท้จริงอยู่ใน `Assembly-CSharp-ULTIMATE\ux_rpc\payload_types.dump.cs` + `ux_rpc_stringliterals.json` (ยังไม่ได้เปิด!)
**ลำดับการทำ (แต่ละขั้นทดสอบ 1 relog):**
1. **Timestamp ทดสอบ 4 โหมด** — สมบัติหลักคือคำเตือนจาก metro: นาฬิกา client = ค่าเซิร์ฟเวอร์/15 → unix ปกติอาจใหญ่ไป 15 เท่า = รถค้าง pending ตลอด (ตรงอาการเป๊ะ) เพิ่ม mode 3 = **unix/15** ใน `CurrentAetherTimestamp()` แล้วไลจ 3→0→1→2 ผ่าน `curl -X POST http://127.0.0.1:5809/api/traffic/config -d "{\"timestampMode\":N}"` (ปุ่ม Lab ถูกลบตามที่ผู้ใช้สั่ง — endpoint ยังอยู่)
## ✅ ก่อนปิดวัน: แพ็กเก็ตเราผ่านการตรวจโครงสร้างแล้ว
`packets-latest.log` (13:33:50): `SyncAetherAIVehicleAddData body=89b` — **89 ไบต์ = 0xFF complex marker + 88 ไบต์ field ครบตามลำดับ Lua ใหม่พอดี** → framing/ลำดับ/ชนิดถูกหมด ผู้ใช้ยืนยัน: เลิกทดลอง timestamp (โดเมนเวลาเดียวกันกับ realTime ที่ส่งอยู่แล้ว)
**สิ่งที่เหลือเป็นได้ได้แค่เงื่อนไขฝั่ง client — ลองเรียงใหม่ตามหลักฐาน:**
1. **ใช้ batch `SyncAetherAIVehicleAddDatas` (68571034, List<init>)** แทน single — สายสตรีมมิ่งของเซิร์ฟเวอร์จริงน่าจะใช้ตัวรวม (single อาจมีไว้สำหรับเคสพิเศษ) — **งานแรกของเซสชันหน้า**
2. NPC คนขับต้องตามก่อน render (เราส่ง NpcAdd ตามหลัง AddData แล้ว — ลองส่ง**ก่อน** AddData หรือพร้อมกันใน notify เดียว)
3. `AetherVehicleManager` จริงมี `m_pendingVehicles` + `ArrivalServerTime` + `MAX_PENDING_SYNC_DATA=30` — เปิด dump UX.Game (`ux_game_all.dump.cs` บรรทัด ~150532 คือ impl ตัวจริงแต่ body โดน strip) ศึกษา field รอบ ๆ หาเงื่อนไข promote เป็น render
4. ถ้ายังไม่รอด → แวะไปทาง **ambient (client เป็นคน spawn)** ที่เคยเห็นรถแล้ว: แก้ `ZoneIndex` ต่อแยกด้วยตำแหน่งไฟจาก `vehicle-lanes.csv` (มี ZoneIndex ทุกเลน!) แทนการใช้ 0
2. **สลับเป็น batch notify** `SyncAetherAIVehicleAddDatas = 68571034` (list<init>) — single AddData อาจไม่เข้า MassTraffic; + เปลี่ยน id base เป็น **750000100000** ตามที่เห็นใน HUD เซิร์ฟเวอร์ที่ใช้ได้
3. **Hexdump ของเราเทียบ Lua เอง** — ถอดแพ็กเก็ต AddData จาก `logs/packets-latest.log` (hex เปิดอยู่) แล้ว decode มือทีละ field ตาม `WriteClientVehicleInitData` เพื่อจับความต่างระดับ serializer (จุดเสี่ยง: Complex marker 0xFF, `Int7` = value+1 bias, WriteList7Bit มี marker ก่อน count)
4. **เปิด `ux_rpc/payload_types.dump.cs`** ค้น `AetherAIVehicleAddData` → framing จริงฝั่งอ่าน (Struct vs Complex ที่ระดับ method payload ไม่ใช่ type)
5. ถ้าครบทุกขั้นยังไม่เห็น → อ่าน `AetherVehicleManager` ใน dump จริง (มี `m_pendingVehicles` + `MAX_PENDING_SYNC_DATA=30` + `ArrivalServerTime`) หาเงื่อนไข "เลย้ยก pending เข้า render"
**NPC คนขับ/คนเดิน:** หลังรถเห็นแล้ว ค่อยใช้ `vehicle-lanes/pedestrian-*` CSV (88k เลน) ทำ `SyncAetherAICrowdAdd`/`SyncAetherAIStaticNpcAddData` ตามรูปแบบเดียวกัน
**การทดลองถัดไป (หลังแก้ wire order แล้วยังไม่เห็นรถ):** สงสัย **หน่วย Timestamp** — client เก็บรถ incoming ใน pending buffer จนนาฬิกาตัวเองถึง `Timestamp+0.35s`; ถ้านาฬิกาคนละฐาน = รถค้าง pending ตลอด = มองไม่เห็นทั้งที่ WebUI นับได้ เพิ่มสวิตช์ `{"timestampMode": N}` แล้ว: **0=unix (default), 1=unix×15 (สเกลนาฬิกาเกม), 2=วินาทีนับจากสตาร์ทเซิร์ฟเวอร์ (monotonic เล็ก)** — ทดสอบทีละโหมด + re-enter world; โหมดไหนเห็นรถ = จบ จดโหมดนั้นไว้เป็น default
## 🔑 DecryptionVault (ได้มาช่วงเย็น — กุญแจไข client assets)
แตกไว้ที่: `C:\Users\Phitchayut\Desktop\Anangay asset\DecryptionVault_extracted\DecryptionVault\`
คือชุด RE ทั้งหมดของ **L50 VFS** (ระบบไฟล์เข้ารหัสของ client): index v5/v8 + entry payloads (LZ4) + descriptors ถอดสมบูรณ์, textures 89k ได้, **มีกุญแจ XOR ครบ** (`KEYS-AND-CONSTANTS.md`), โค้ด decoder C#/Python ใน `code/`, spec ใน `formats/`
**ทำอะไรได้สำหรับบัคเรา:** ถอด **Lua ของ client** ออกจาก L50 packages ได้ = ได้มาซึ่ง:
1. `RPCSerializeAuto.lua` ตัวจริง → **wire format ที่แน่นอนของทุก Aether packet** (แก้ AddData มองไม่เห็น)
2. **Logic ไฟแดง/MassTraffic จริง** (period tables, ZoneIndex mapping) → แก้รถ ambient ซ้อนทับ/ไม่มี AI
3. **Lua ของ DevPanel จีน** → ทำ UI translator ให้ตรงจุดโดยไม่ต้องเดา
**เส้นทางเซสชันหน้า (แนะนำให้เริ่มที่นี่ก่อนอื่น):**
1. คอมไพล์/รัน decoder จาก `code/` (L50Index.cs + L50Payload.cs + Lz4Block) กับไฟล์เกมในเครื่องเรา (โฟลเดอร์เกม Ananta.exe — หาตำแหน่งก่อน)
2. ระบุไฟล์ Lua ใน index (หาชื่อ/path ที่ขึ้นต้น lua / ui / rpc) → ถอดออกมา
3. อ่าน `RPCSerializeAuto.lua` → เทียบ DTO ของเราทุกตัว (ClientVehicleInitData, LaneDatas, InitDatas, IntersectionUpdate)
4. หา logic ไฟแดง (MassTraffic/IntersectionPeriod) → แก้ init intersections ให้ตรง
5. ต่อ UI translator ด้วยพจนานุกรมที่เตรียมไว้ (หัวข้อ roadmap บน)
**หมายเหตุ:** เครื่องคนส่งมี decoded output 600,514 ไฟล์ที่ `D:\Ananta\ufmapp_decoded\` (path ของเขา — ถ้าเรามีไฟล์ไม่ครบต้อง decode เองจาก client ในเครื่องเรา)
## สิ่งที่ยังไม่ได้ทำ (roadmap)
- **UI Translator (DevPanel จีน → English)** — แพ็กเกจ `Ananta_UI_Translation_RU_EN` (ใน Anangay asset) จากกลุ่มอื่น: หลักการ = fastpatch แทน Lua 1 ไฟล์ใน client แล้ว hook ข้อความ UI ด้วยพจนานุกรม `中文=English` (1,352 entries ครอบ dev menu/settings/phone/talents; ไม่ครอบ dialogue/item names)
- **เตรียมไว้แล้ว**: `config/localization/translation.{en,ru}.txt` + `Ananta.Proxy/proxy/tools/generate_client_config_patch.DRMK.reference.js` (generator ของ Drmk 15KB ที่มี Lua hook ฝังอยู่)
- **ต้องพอร์ต**: Lua hook (~500 บรรทัด: HookTable + dictionary loader + env `DRMK_DEV_MENU_DICT`) จากไฟล์ reference เข้า generator ของเรา (74KB คนละสาย — ห้ามทับ!), เพิ่ม config keys `ui.devMenuDictionary` + `ui.devMenuLanguage:"en"` ใน `config/private-server.json`
- **ตรวจสอบความสำเร็จ**: console ต้องขึ้น `dev-menu dictionary: 1352 entries, 20 rules` ตอนสตาร์ท; คู่มือเต็มอยู่ `Anangay asset\Ananta_UI_Translation_RU_EN\README.txt` (EN section ท้ายไฟล์)
- **ข้อควรระวัง**: fastpatch พัง = เกมค้าง Initializing Game — แก้แล้วต้องทดสอบเข้าเกมทันที และเก็บ backup generator เดิมไว้ก่อนแก้
- รถไฟสาย 9 (Longqi) / สายอื่นนอกเมือง — ปล่อยปิดไว้เพราะ scene ไม่มี MetroPath
- ระบบ Borrow/hijack รถ traffic แบบเกมจริง (`AskAetherAIBorrowVehicle` → `SyncAetherAIRaidVehicleRegressAether`)
- ตั๋วรถไฟ/บัส (SyncAetherAIBusStopEvent), MassBusRoute
- ผู้โดยสารในรถ ambient (โหมด server-driven มีแล้ว)
---
# 🔥 ต่อยอดรอบดึก (session ถัดจากแผนข้างบน): เจอตัวการตายตัว + แก้ครบ
## สาเหตุรากเหง้าที่รถ server-driven ไม่ขึ้น — "method id ปลอม"
เปิด `ux_rpc/ux_game_all.dump.cs` (ตาราง `IGameSceneToClient_*` จริงของ client 4229938) + เทียบ `RPCMethodIdToName.lua`:
| ที่เราเคยใช้ | ค่า | ผลตรวจ |
| :--- | :--- | :--- |
| `SyncAetherAIVehicleAddDatas` (batch) | 68571034 | ❌ **ไม่มีใน client เลย** — client มีแค่ single `SyncAetherAIVehicleAddData = 68916691` แพ็กเก็ต batch ถูก client เด้งทิ้งเงียบ ๆ มาตลอด = สาเหตุที่แก้ wire order แล้วยังไม่เห็นรถ |
| `SyncAetherAIVehicleNpcRemove` | 68173578 | ❌ ไม่มีจริง — ตัวจริงคือ `SyncAetherAINpcRemove = 68332284` (รับ ulong pid เหมือนกัน) |
| single AddData | 68916691 | ✅ ตรง (hex 89b ที่เคย verify = single ตัวนี้แหละ) |
**แก้แล้ว:** `TrafficAndCrowdDriver.SpawnAetherVehiclesAsync` ยิง single notify ต่อคัน (loop ใน C#), NpcRemove ใช้ `SyncAetherAINpcRemove`, ใส่ comment ปิดกั้น id ปลอมทั้งสองจุดใน `MethodId.cs`
## บัค wire order อีกชุด: Intersection Init/Update
`RPCSerializeAuto.lua` (line 2506/2515) เขียน **derived fields ก่อน base** เหมือนกฎของ ClientVehicleInitData:
- `WriteClientTrafficIntersectionInitInfo`: InstanceId(u64) → ZoneIndex(i32) → CurrentState(b) → Current/Next/RailPeriod(b×3)
- `WriteClientTrafficIntersectionPeriodUpdateInfo`: IntersectionIndex(u64) → CurrentState(b) → Current/Next/Rail(b×3)
DTO เดิมเราเรียง period bytes ก่อน → **wire พังทั้ง packet** (แก้แล้วตามลำดับ Lua)
## แก้ ambient MassTraffic: ZoneIndex จริงต่อแยก (หมดยุคสมมติ 0)
**หลักฐาน:** client มี `AetherIntersectionManager` — โหลด blob `StaticIntersectionData` (artifact id 111 = `StaticIntersectionData.json` ใน client res, `AetherResourceConfig.json`) ซึ่งมี `List<StaticZoneInfo>` = {ZoneIndex, Position, TrafficLights, TrafficLightPeriodControls} แล้ว `BuildIntersectionData` resolve init ที่เราส่ง **ด้วย ZoneIndex → `_zoneIndexToBlobIndex`** → ส่ง 0 ทุกแยก = entity ล้วนระเบิดไปที่เดียว (ตรงอาการรถซ้อน)
**หา ZoneIndex จริงได้แล้ว:** city zonegraph = `vfc_1286_e108` (14,171 lanes ตรง probe เป๊ะ, 2,246 zones) ไฟจริงเมือง = `vfc_1286_e104` world JSON (1,082 ไฟ / 171 แยก) — เอาตำแหน่งไฟไป match zone tag "Intersection" (point-in-bounds + fallback ≤25m): **171/171 แยก match ครบ = 224 zones** → สร้าง `ClientData/4229938/World/InterZones.json` ไว้แล้ว (มี source กำกับ)
- เซิร์ฟเวอร์โหลดตอน `Initialize` → `GetAetherIntersectionZonePeriods()` คืน 1 entry **ต่อ zone** (InstanceId = `900_000_000_000 + zoneIndex`, ClientTrafficIntersectionInitInfo ใหม่)
- ปุ่ม push update (`SyncAetherAIIntersectionUpdateData`) เปลี่ยนเป็น address ต่อ zone instance ด้วย
- ⚠️ ยังไม่รู้ตาราง period จริงของ client (อยู่ใน blob) — ส่ง 0/1 ไปก่อน client ก้าวเอง; ถ้าอยากได้ blob จริงต้อง decode L50 จากเครื่องที่มีเกม (หา `StaticIntersectionData.json`)
## ความรู้ใหม่จาก dump (AetherVehicleManager / AetherCity.Manager)
- pending flow: `SyncAetherAIVehicleAddData` → `EnqueuePendingVehicle` (ArrivalServerTime = Timestamp + 0.35s) → `CollectReadyPendingVehicles(currentServerTime)` promote เป็น entity → **Timestamp = unix ตามที่ผู้ใช้ยืนยันโดเมนเดียวกับ realTime ที่ส่งอยู่**
- `DELTA_TIME_PER_SERVER_FRAME = 0.05` (server stream 20Hz — เรา 10Hz + delay buffer 0.35s ยังไหล่ทัน), `MAX_PENDING_SYNC_DATA = 30`
- client คำนวณ speed limit เองจาก lane (`EstimatedFreewaySpeedMps 16.67 / DefaultRoadSpeed 9.72`, variance ±35% จาก `RandomFraction`) → Speed ที่เราส่งเป็นแค่ hint
- ไฟจริงเมืองทั้งเมือง = vfc_1286_e104 (171 แยก) ส่วน TrafficSignals.json เรา (165 ไฟ/37 แยก จาก vfc_47) คือแค่โซนเริ่มเกม — IDM light logic ฝั่ง server-driven ยังใช้ตัวเดิมได้
## ลำดับทดสอบ (relog ทุกครั้ง)
1. เข้าเกมปกติ (`ServerSpawnedVehicles=true` default) → **รถควรโผล่+วิ่งแล้ว** (single 68916691 + wire 89b + IDM LaneDatas 10Hz, client render/interpolate ตาม zone graph ตัวเอง)
2. รถโผล่แต่ติดตรง → `curl -X POST http://127.0.0.1:5809/api/traffic/config -d "{\"pushIntersectionUpdates\":true}"` ดูไฟ; ยังติด → hex แพ็กเก็ต LaneDatas ใหม่จาก `logs/packets-latest.log` เทียบ `WriteClientVehicleLaneData`
3. ทดสอบ ambient แท้ (AI รถจาก client ล้วน): `{"ambientIntersections": true}` → re-enter → ถ้ารถ ambient วิ่งได้ครบ = ปิดจ็อบ; ถ้ายังซ้อน = blob ฝั่ง client ไม่ครอบ zone ไหนไปดู packet แรกที่ entity ไม่เกิด
4. NpcAdd คนขับ (68685163) ตามหลังรถ — ถ้ารถโผล่แต่ไม่มีคนขับ = เช็ค `WriteClientVehicleNpcInitData` อีกรอบ (field ตรง dump แล้ว)
**อย่าลืม:** อย่ายิง id ปลอม (68571034/68173578) กลับมาอีก — comment เตือนวางไว้ใน MethodId.cs แล้ว | Metro ยังใช้ได้จริง ห้ามทำพัง | ทุก DTO ใหม่เช็คลำดับจาก `RPCSerializeAuto.lua` ก่อนเสมอ
---
# 🎁 20 ก.ย.: เช็คของใหม่ "AnantaGadgetLoader" → ทำเป็นระบบ server-side แล้ว
## ของที่เอามาเช็ค (transfer.it zwGjHmhhjJQZ)
> 📁 เก็บไว้ที่ `C:/Users/Phitchayut/Desktop/Anangay asset/AnantaGadgetLoader_v1.0/` — exe + คู่มือจีน + `RE_extracted/` (Frida agent JS ที่แกะออกแล้ว + code object gadget_world)
`AnantaGadgetLoader_v1.0.zip` (55.6 MB) = PyInstaller(python313) + **Frida** — inject เข้า Ananta.exe แล้ว spawn gadget แบบ interactive รอบผู้เล่น (ตู้ vending, ตู้ claw, ประตู, ม้านั่ง, อาร์เคด, ร้านค้า) ไม่เขียนไฟล์ ปิดเกมหาย แยกจาก `new-worldmod` (AnantaWorldMod — ไม่ได้แถมมา) ที่ดูแล props ที่ไม่ interact ได้
**วิธีทำงานจริง (แกะ `gadget_world` จาก PyInstaller → Frida JS agent 149KB):**
- หา singleton `LX6.Item.GadgetMgr` + `LX6.Game.MyPlayerManager` (ตำแหน่งผู้เล่น)
- เรียก **`GadgetMgr.SyncGadgetGridAOIAddByIndex(indexX, indexZ, HashSet<ulong> exceptIds, List<int> sectorIdList, GridIndex playerStandardIndex)`** (private method) ทีละ cell 40m รัศมี 8 cell (320m) ทุก 0.7 วิ
- **ข้อมูล gadget ทั้งหมด baked ในเครื่อง client อยู่แล้ว** — server/tool แค่บอกว่า "cell นี้ให้โหลด sector อะไร" (sector 0 = กลางแจ้ง, ของในอาคารอยู่ sector ต่อตึก — ตัว tool ใช้ cells.json จาก world dump)
- บทเรียนของมัน: cell ที่ส่งตอน grid data ยังไม่พร้อม = หาย (ต้อง re-send), Remove แล้ว Add กลับมักไม่ผลิต ("poisoned" — ห้าม remove เอง), Add ซ้ำไม่ซ้อน (idempotent), `LoadGridAndLocalDataRaid` ห้ามเรียกเอง (แตก channel)
## สิ่งที่ค้นพบและ implement ลงเซิร์ฟเวอร์เรา (ไม่ต้อง inject แล้ว!)
`SyncGadgetGridAOIAddByIndex` คือ internal ที่ handler ของ **RPC จริงจาก server** เรียก — `SyncGadgetGridAOIDecrease = 68956797` (CSharpOnly, มีใน MethodId.cs เราอยู่แล้ว แต่ไม่เคยมีใครส่ง — config `world.content.gadgets` ก็วางรอไว้แต่ไม่มี implement)
**wire format (จาก `RPCSerializeAuto.lua` line 4474-4830 + payload_types.dump.cs — 4 param เป็น struct ทั้งหมด = inline ไม่มี marker):**
1. `GadgetGridAOIIncrease`: PlayerStandardIndex(GridIndex X/Z) → addInfos(List<Complex GadgetEntityInfo> = ส่งว่าง) → indexList(List<GridIndex> = cell ที่สั่งโหลด) → addUniqueIds/removeIds/activeIds/inactiveIds(List<u64> ว่าง) → reason(byte: Init=0, Loaded=1, AOIMove=2, ...)
2. `GridAOIDecrease` (add): SectorIdList(List<i32> = [0]) → BuildingId(i32=0) → StandardIndexList(cells เดียวกัน) → ExceptIds(ว่าง)
3. `GridAOIDecrease` (remove): ว่างทั้งหมด (ไม่ remove ตามบทเรียน poisoned)
4. `GadgetExtraSyncInfo`: DropLimit ว่าง
**ไฟล์ใหม่:** `Ananta.RpcTypes/.../GadgetMethods.cs` (DTO ตามข้างบน) + `Ananta.Gameplay/World/GadgetStreamingEngine.cs` (loop 700ms ต่อ session: หา cell จากตำแหน่งผู้เล่น → ส่ง cell ใหม่ในรัศมี 3 cell (config) budget 49/tick → re-send ทั้งหมดทุก 120 วิแบบ idempotent เพื่อกันคิวแรกตก) + driver implement ใน `TrafficAndCrowdDriver` (IGadgetStreamDriver) + wire-up ใน `PrivateServerApplication` — **Build ผ่าน 0/0**
**สวิตช์:** `config/private-server.json → world.content.gadgets.{enabled,radiusCells,loadBudgetCells}` (default เปิด radius 3)
**ข้อจำกัดที่ยังเหลือ:** sector [0] = กลางแจ้งเท่านั้น — gadget ในอพาร์ตเมนต์/อาคารต้องมีแผนที่ cell→sector (แบบ cells.json ของ tool) ต่อยอดด้วยการสกัด sector จาก client blob ภายหลัง
**ทดสอบ:** เข้าเกม → log server ต้องขึ้น `[GADGET] Grid stream started` → เดินไปตู้ vending/อาร์เคดในเมืองต้องเห็นของโผล่ + กด interact ได้; ถ้าไม่โผล่ เทียบ hex SyncGadgetGridAOIDecrease ใน packets-latest.log กับ Lua writer ทีละ field (ระวัง: อย่าส่ง cell ก่อน player transform พร้อม — engine เราข้าม tick นั้นแล้ว)
---
# 🕹️ 20 ก.ย. (ต่อ): new-worldmod + ทำให้ gadget "กดได้"
## สถานะจากผู้เล่น: ตู้ vending โผล่แล้ว (ระบบสตรีมใช้ได้!) แต่กด interact ไม่ได้ + ส่ง new-worldmod.zip มา (ไฟล์ที่หายจากชุดเดิม)
## new-worldmod.zip → เก็บที่ `Anangay asset/new-worldmod/` (มี SOURCE ครบ)
- `world_mod.js` = Frida agent สตรีม **`LX6.Item.SceneItemMgr.SyncSceneItemGridAOIAddByIndex(cx, cz, exceptIds:HashSet<u64>, sectorIdList, playerStandardIndex)`** (5 พารามิเตอร์ บน 4229938) — เหมือน gadget loader แต่คนละ manager (เสาไฟ/props/ของทำลายได้/เป้าจับโทเค็นไซส์)
- **`cells.json` = แผนที่ grid→sector จริง 5,116 cells สกัดจาก runtime client รูป `[x, z, sectorBitmask]`** — ใช้ต่อยอดทำ indoor gadget sector ได้ภายหลัง (meta.baked=5116)
- `object_spawner.py` (GUI) / `inject.py` (headless) / มีปุ่ม Save cells สำหรับ export แผนที่เมื่อเปลี่ยนแผนที่
## สาเหตุ "กดไม่ได้" + แก้แล้ว
การ interact ของ gadget เป็น **server round-trip** ที่เซิร์ฟเวอร์เราไม่เคยตอบเลย (มีแต่ id ใน MethodId.cs ไม่มี handler):
- กด F → client รัน graph ของ gadget → ยิง `AskInteractCmd(InteractCmdData)` (Notify, 67390408) และ/หรือ
- `AskSceneDeviceChangeState(SceneDeviceEntityType, SceneDeviceStateChangeInfo)` (**Invoke ต้องมี return** `SceneDeviceCheckIndexCallback{CheckIndex i32, Error u32}`, 67226162)
- `AskSceneDeviceRecordValue(...)` (67703474), `AskSceneDeviceSendSignal(type, id, signalName)` (67144566)
- เซิร์ฟเวอร์จริงตอบ callback + ส่ง `SyncSceneDeviceStateChange/ValueChange/SignalSend` และ `SyncInteractCmd(s)` กลับมา → graph ของ gadget ได้ ACK แล้วค่อยเปิด UI/อนิเมชัน → ไม่มีตัวตอบ = กดแล้วเงียบ
**wire format ยืนยันจาก `ClientToGameSceneDelegate.lua`** (client เขียน args เอง: line 552/2230/892/1366/4068): type เป็น **byte** (SceneDeviceEntityType: Gadget=0, Destructible=1), statechangeinfo/valuechangeinfo เป็น WriteStruct inline, InteractCmdData = {CmdType u32, sender u64, receiver u64, broadCastType i32, CommandData List7Bit<byte>, stringParam1, stringParam2} — ⚠️ `CommandDataLen` ใน dump เป็น metadata ไม่ได้อยู่บน wire ห้ามใส่ใน DTO
**แก้แล้ว (Build ผ่าน 0/0):**
- `GadgetMethods.cs` เพิ่ม DTO interaction ทั้งชุด (InteractCmdData, StateChangeInfo, ValueChangeInfo, CheckIndexCallback, Ask*/Sync* wrappers)
- `GameRouter.Gadgets.cs` (ใหม่): รับ AskInteractCmd(s) → echo `SyncInteractCmd`/`SyncInteractCmds` (68260752/68976896) กลับ; รับ AskSceneDeviceChangeState/RecordValue → return `SceneDeviceCheckIndexCallback{CheckIndex=ที่ส่งมา, Error=0}` + ส่ง `SyncSceneDeviceStateChange/ValueChange` (68165561/68332775); รับ AskSceneDeviceSendSignal → ReturnEmptyOk + `SyncSceneDeviceSignalSend` (68477555)
**ทดสอบ:** เข้าเกม → เดินเข้าใกล้ตู้ vending → กด F → log server ต้องขึ้น `[GADGET] interact cmd ...` / `change state ...` แล้วหน้าเกมต้องเปิด UI/ทำงานตอบสนอง; ถ้ายังไม่ขึ้น prompt ปุ่ม F เลย = ต้องไล่ state ตอน spawn ต่อ (occupantInfos/states ใน GenerateGadget — จดไว้เป็นขั้นถัดไป) | ประตู/ยกของในอาคารอาจต้องใช้ `AskGadgetSceneRoomTrigger` (67413973, Invoke) เพิ่ม
---
# 🚶 20 ก.ย. (ต่อ 2): คนเดินถนน + NPC dialog + Quest
## 1. คนเดินตามถนนไม่มี — สาเหตุ: PedestrianNetwork.json มาจากกราฟผิดไฟล์
UrbanCrowdEngine มีระบบ spawn คนเดินบนทางเท้าอยู่แล้ว (โหลด `World/PedestrianNetwork.json`) แต่ไฟล์เดิมถูกสร้างจาก zonegraph **vfc_1000_e8** (X -5667..3490, Z -3688..2083) ซึ่งไม่ใช่กราฟเมืองที่ผู้เล่นอยู่ — ผู้เล่นแถว (3121, 2475) อยู่ **นอกครอบช่อง Z ของไฟล์เดิมทั้งหมด** → หา lane ใกล้ ๆ ไม่เจอเลย → ไม่มีคนเดิน (เหลือแต่ NPC ตามร้านจาก Interiors.json)
**แก้แล้ว:** สร้างใหม่จาก **vfc_1286_e107** = กราฟคนเดินของเมืองจริง (88,361 เลน Pedestrian ทั้งหมด, bounds X 264..4368 / Z 644..2823 ครอบเมือง, ใกล้ผู้เล่น 1,387 เลนในรัศมี 200m) ตัด PedestrianBanned ออก → `PedestrianNetwork.json` ใหม่ 87,879 เลน / 22.8MB / สคีมาเดิม (id, tags, pts, isCrosswalk + spatialGrid 100m) engine โหลดได้ทันทีไม่ต้องแก้โค้ด
> 📌 บทเรียน: e108 = กราฟรถเมือง (Vehicle ล้วน 21,333 เลน — subset 14,171 ที่ client โหลด), e107 = กราฟคนเดินเมือง, vfc_1000_e8 = กราฟอีกพื้นที่นึง (ห้ามใช้กับเมือง), vfc_1287_e0 = กราฟรถเมืองอีกเวอร์ชัน
## 2. NPC dialog (กด F คุยกับ NPC)
Client เดา dialog id เองจาก config ของ NPC แล้วขออนุมัติผ่าน server (ตัวหนังสือบทสนทนา baked ฝั่ง client — server แค่เปิด/ปิด):
- `AskAgentStartNpcDialog(agentEntityId u64, npcDialogId u32, dialogDuration f32)` (Notify, 67438669) → ตอบ **`SyncAgentPrepareStartNpcDialog(agentId, npcDialogId, npcSoundId=0, virtualNpc=false, entireDialogId=0)`** (68710152) = เปิดหน้าคุย
- `AskAgentFinishNpcDialog(agentEntityId, npcDialogId)` (67940690) → ตอบ **`SyncAgentFinishNpcDialog(agentId)`** (68922661) = ปิด
wire ยืนยันจาก ClientToGameSceneDelegate.lua line 2480/5414
## 3. Quest (Task) accept / submit / tracker
เดิม AskAcceptTask/AskSubmitTask แค่ ReturnEmptyOk ไม่มี push → quest ไม่ขึ้นใน tracker เลย ตอนนี้:
- `AskAcceptTask(taskId)` / `AskSubmitTask(taskId)` / `AskAcceptAndSetCurrentTask(taskId, spirit, ignoreSwitch)` (ClientToGameDelegate.lua 3172/2656/7136) → ack + ส่ง **`SyncPlayerSingleTask(taskId, TaskViewData, TaskSpoonViewInfo, TaskStateData)`** (64917604) ด้วย State = Accepted(1)/Submited(3) (TaskState enum: NotAccept=0, Accepted=1, Submited=3, Aborted=4)
- AcceptAndSetCurrent ส่ง `SyncCurrentTask{Type=1, TaskId}` เพิ่ม (payload เดียวกับตอน world entry)
- DTO ใหม่ทั้งชุดใน `TaskAndDialogMethods.cs`: TaskViewCounter (recursive, Complex), IntList (List<i32> ใช้ WriteList = **Int32 count** ไม่ใช่ 7bit!), TaskSpoonViewInfo, TaskViewData (dict ใช้ WriteDict7Bit → **Count=Int7** บังคับใส่ attribute), TaskStateData (struct inline)
- ⚠️ จาก RPCSerializeBase.lua: WriteList = 0xFF + Int32 count, WriteList7Bit = 0xFF + 7bit count, WriteDict7Bit = 0xFF + 7bit count, complex null = 0x00 — เช็คทุก field กับ Base ตรงนี้ก่อนเขียน DTO ใหม่
## ขั้นถัดไป (roadmap)
- Quest counter (ฆ่ามอนสเตอร์ X ตัว / เก็บของ) ต้อง hook กับระบบ combat/inventory — ยังไม่ทำ
- ถ้า NPC ยังไม่โชว์ prompt ปุ่ม F ต้องดูต่อที่ agent component ตอน spawn (SyncManagedLogicAgent / dialog config ของ formwork นั้น)
- Indoor gadget sector: ใช้ `cells.json` ของ new-worldmod (`Anangay asset/new-worldmod/new-worldmod/cells.json` รูป [x, z, sectorBitmask] 5,116 cells) ทำ mapping cell→sector ต่อ
**ทดสอบ:** relog → (1) เดินตามถนน คนเดินควรเริ่มเกิดรอบตัวใน ~1-2 นาที (spawn 1 คน/2.5 วิ จนถึง 25) (2) เข้าไปคุย NPC ที่ร้าน/คนเดิน กด F = หน้าคุยเปิด (3) รับ/ส่งเควส = ขึ้น tracker | log: `[CROWD] Loaded 87879 pedestrian ...`, `[NPC] start dialog ...`, `[TASK] accept task=...`
---
# 🌃 20 ก.ย. (ต่อ 3): ปิดรอบ "ทำให้ครบทุกระบบที่ทำได้" — Build 0/0
## 1. SceneItem grid stream (เสาไฟ / props / สิ่งที่ทำลายได้) — AnantaWorldMod แบบ server-side
เจอ packet จริงที่คุม world props: **`SyncDestructibleGridAOIIncrease = 68934309`** (client handler เรียก `SceneItemMgr.SyncSceneItemGridAOIIncrease(DestructibleGridAOIIncrease)` — id มีใน MethodId.cs เราอยู่แล้ว ไม่เคยมีคนส่ง)
- payload = struct inline: PlayerStandardIndex(GridIndex) → addInfos(List<Complex DestructibleInfo> = ว่าง) → indexList(List<GridIndex>) → addUniqueIds/removeIds(List<u64>) → reason(byte) — wire จาก `WriteDestructibleGridAOIIncrease` (RPCSerializeAuto.lua 3293)
- engine เดิม (GadgetStreamingEngine) push คู่กับ gadget packet ทุก batch — เสาไฟ/props เต็มถนนโดยไม่ต้อง inject
## 2. Indoor gadget sector — ใช้แผนที่ cell→sector จริงจาก new-worldmod
- คัดลอก `new-worldmod/cells.json` → `ClientData/4229938/World/CellSectors.json` (5,116 cells, **94 sector ids** รูป [x, z, sectorId...] — sector id เป็นเลข hash ใหญ่ ไม่ใช่ bitmask)
- engine โหลดตอน Initialize → group cell ใน batch ตาม sector list เดียวกัน → ส่ง 1 packet ต่อ group (`GridAOIDecrease.SectorIdList` = sector จริงของ cell นั้น) — cell ไหนไม่มีใน map = [0] กลางแจ้ง
- คุมด้วย config เดิม `world.content.gadgets.loadAllSectors` (ตอนนี้ = true → ใช้แผนที่จริง; false = sector 0 ล้วน)
- ผล: gadget ในอพาร์ตเมนต์/ร้าน (ประตู ตู้เสื้อ เครื่องใช้) ควรเริ่มโผล่ตามตึก
## 3. ประตู / ห้องภายใน
- `AskGadgetSceneRoomTrigger(sceneRoomId i32, gadgetUniqueId u64, isEnter bool)` (67413973, Invoke) → ack ReturnEmptyOk (client เดินเข้าห้องเอง) — handler ใหม่ใน GameRouter.Gadgets.cs
- `AskGadgetDoorTransfer` มี handler accept อยู่แล้วใน Endpoints.cs (ไม่แตะ)
## 4. ยืมรถ traffic ขับได้ (borrow) — ปิด roadmap เก่า "hijack not supported"
flow จาก dump: กด F บนรถ traffic → client ยิง **`AskAetherAIBorrowVehicle(vehicleInstanceId)`** (67122370, Invoke) → server:
1. ack + `CityTrafficEngine.DetachForBorrow(entityId)` (ถอดออกจากซิม + คืน id NPC)
2. ลบคนขับ/ผู้โดยสารด้วย `SyncAetherAINpcRemove`
3. push **`SyncAetherAIBorrowVehicle`** (68187552) + **`SyncAetherAIChangeVehicleControl`** (68321386, ControlType=2 RaidVehicle) → รถกลายเป็น raid vehicle ขับได้
4. ลงจากรถ: `AskAetherAIRaidVehicleRegressAether` (67334266) → ตอบ ControlType=1 คืนสู่ traffic
## ยังไม่ทำ (ติดข้อมูล/hook ใหญ่)
- Quest counter แบบ active (ฆ่า X / เก็บ Y) — ต้อง hook ระบบ combat + parse TaskConfig ฝั่ง server
- UI translator (fastpatch Lua) — เสี่ยงพัง boot ต้องมีเวลาทดสอบเข้าเกมทันที
- Bus stop / Metro สาย 9 — scene ไม่มี path ของจริง
- SyncSceneDeviceOccupantChange (คนนั่งม้านั่งซ้อนกัน) — รอทดสอบ interact รอบแรกก่อน
**ลำดับทดสอบรวม (relog):** (1) ถนนมีเสาไฟ/props + คนเดิน (2) ตู้ vending กด F ได้ (3) เข้าอาคารมี gadget ในบ้าน (4) กด F รถ traffic แล้วขับได้ (5) คุย NPC / รับ-ส่งเควส
---
# 🏠 20 ก.ย. (ต่อ 4): ระบบบ้าน (แก้ตกแมพ) + Quest ใช้ asset จริง 4,342 เควส — Build 0/0
## 1. บ้าน/interior ตกแมพ — สาเหตุ: server ไม่เคยเปิด SceneRoom
ระบบบ้านเดิมมีแค่ unlock สิทธิ์ใน PlayerInfo (HouseInfoList) — ตอนเดินเข้าประตู client ขอเข้าห้อง แต่ไม่มีใครตอบ → client ไม่โหลด geometry ของห้อง → ตกทะลุพื้น
กลไกจริง (จาก dump): ประตู/ห้อง = **SceneRoom** ทั้งหมด
- C2S: `AskGadgetSceneRoomTrigger(sceneRoomId, gadgetUniqueId, isEnter)` (67413973), `AskEnterSceneRoom(sceneRoomId, isVehicle, moveId)` (67714973), `AskExistSceneRoom(...)` (67982277), `AskAgentSceneRoomTrigger(sceneRoomId, spoonAgentId, isEnter)` (67923734)
- S2C ตอบ: **`SyncSpoonRoomState(sceneRoomId, enabled, taskId)`** (68542118) — ห้องโหลดเมื่อ enabled=true และปิดเมื่อออก (id มีใน MethodId.cs อยู่แล้ว ไม่เคยมีคนส่ง!)
**แก้แล้ว:** handler ทั้ง 4 ตัว (GameRouter.Gadgets.cs) → ack + push SyncSpoonRoomState ตาม isEnter
## 2. Quest system — ใช้ TaskConfig.json จริง 4,342 เควส
- คัดลอก `TaskConfig.json` (7.4MB, จาก world dump `_ALL_CONFIGS/table/` รูป `{"records":[...]}`) → `ClientData/4229938/Configs/`
- **`TaskCatalog4229938`** (ใหม่, Ananta.Core): Lazy parse → Id/Name/RelatedRaid/CounterCount/NextTasks ต่อเควส (client มี config ชุดเดียวกัน baked — ชื่อ/เป้าหมาย render ฝั่ง client เอง server แค่จัด state)
- **Task handlers ใหม่ (GameRouter.Endpoints.cs):**
- per-session state ใน `session.Items["taskStates"]` (State + int[] counters ขนาดตาม Counter ของ config)
- `AskAcceptTask` → validate catalog + push `SyncPlayerSingleTask` (State=Accepted, CounterValues=0 ตามจำนวน counter)
- `AskSubmitTask` → push Submited(3) + **auto-accept NextTasks** (เดินเควสได้ต่อเนื่องทั้งสาย)
- `AskAcceptAndSetCurrentTask` → accept + `SyncCurrentTask`
- `AskFinishTaskCounter(taskId, counterIndex)` → +1 counter แล้ว push (sandbox shortcut ที่ client มีอยู่แล้ว)
- **World entry:** push `SyncPlayerAllTask(taskInfos, [], currentTask=10001, EventPanelInfo ว่าง, loginGameServer=true)` (64260853) ก่อนใช้งาน tracker + เดิม push SyncPlayerSingleTask ของเควสที่ค้าง Accepted จาก session เดิม
- **บ้าน:** push `SyncHouseFurnitureInfo({})` (66177525) ตอน world entry = บ้านว่างสะอาด ไม่ติด state เฟอร์นิเจอร์
## ยังเปิดค้าง
- Quest counter แบบ auto (ฆ่ามอนสเตอร์แล้วนับเอง) — ต้อง hook combat event กับ counter config ของแต่ละเควส (TaskEventConfig.json 3.3MB รออยู่)
- ถ้าเข้าบ้านบางหลังยังตก: ดู log `[ROOM]` — ถ้า client ไม่ยิง ask เลยแปลว่าประตูบ้านนั้นใช้กลไกอื่น (map entrance ผ่าน MapentranceConfig) ค่อยตามต่อ
- Quest progress รีเซ็ตตอนปิดเกม (state อยู่ใน session) — ถ้าต้องการถาวรค่อยย้ายลงไฟล์
---
# 🗄️ 20 ก.ย. (ต่อ 5): manifest.jsonl 600k ไฟล์ + เจอไฟล์เกมในเครื่อง — เปิดทาง decode เองได้
## ของใหม่: manifest.jsonl (164MB, 600,514 entries = 77.67GB decoded)
คือดัชนีของ output ถอดชุดเต็มจากเครื่องผู้ส่ง (ufmapp_decoded): แต่ละบรรทัด = {c: container vfc_N, hash u64, off, len, kind, out, out_len, sha256}
- kinds: opaque 316k / catalogue 133k (.cab = CAB record) / lz4+bulk 89k (.part) / lz4-full 61k (.obj) / media 243 (.mp4 XOR 0x76)
- ข้อจำกัด: out เป็นชื่อ hash (nameHash→name ยังเป็นโจทย์เปิดของ vault เอง) และไฟล์จริง 77GB อยู่เครื่องผู้ส่ง
## เจอไฟล์เกมในเครื่องเรา — decode เองได้!
- `E:\Ananta_beta\Ananta\Ananta_Data\StreamingAssets\Blocks` = 653/1,680 containers (41.9GB — ชุดบางส่วน)
- Desktop trunk_release = vfc_3000/3001 (AssetsNotPatch) บล็อกยักษ์ 131MB+
- `StreamingAssets/NoAB/*.pkg` + `numPath/` + ShaderBlobs = แหล่ง hash oracle ที่ vault รู้จัก
- ข้อจำกัด: 341k/600k entries อยู่ใน container ที่ไม่มีในเครื่อง (ต้องโหลดเกมเต็มเพิ่ม หรือขอไฟล์จากผู้ส่ง)
## VaultTool (ใหม่) — `Anangay asset/VaultTool/VaultTool/`
คอนโซล .NET ที่ใช้โค้ดของ DecryptionVault (L50Payload + Lz4Block.Decoder) ตรง ๆ:
- `scan <blocksDir> <manifest> <term> [maxHits]` — ไล่ entry ตาม manifest อ่าน (c, off, len) → decode ตาม kind → ค้น ASCII term พร้อม context
- `extract <blocksDir> <container> <off> <len> <kind> <out>` — แตก entry เดียว decode แล้วเขียนไฟล์
- กลยุทธ์: ค้นชื่อ resource ตรง ๆ ไม่เวิร์ค (catalogue ไม่มี path) → สแกนหา "ชื่อ field เฉพาะ" ในเนื้อหา เช่น `TrafficLightPeriodControls` (จาก StaticIntersectionDataBlob) เพื่อหา entry ของ **StaticIntersectionData.json** (artifact 111) และ `IntersectionPeriodConfig` (artifact 7)
- สแกนรอบแรก (catalogue+lz4 ทั้งหมด): 0 hit — ชื่อ resource ไม่โผล่ใน descriptor ตามที่ vault รายงาน
- กำลังสแกน opaque (raw ล้วน สแกน byte ตรง) — อยู่ใน `VaultTool/scan_period.log` + `scan_trunk.log`
## เป้าหมายถัดไปเมื่อเจอ
1. **StaticIntersectionData.json** → วางใน ClientData → ambient traffic init ใช้ ZoneIndex+Period จริง 100% (แทน mapping จาก zonegraph)
2. **IntersectionPeriodConfig.asset** → ตารางไฟแดงจริง
3. แหล่ง sector อาคารเพิ่มเติมสำหรับ indoor gadget (เทียบกับ cells.json ของ worldmod)
---
# 🏆 20 ก.ย. (ต่อ 6): StaticIntersectionData ตัวจริงถูกถอดได้ — ambient traffic ใช้ข้อมูล 100% จริงแล้ว
## ผลสแกนเต็มชุด (1,679/1,680 containers, 98GB trunk บน E:)
เจอ **4 ไฟล์** ที่มี `TrafficLightPeriodControls` (น่าจะครบทุกชุดที่เกมมี):
| container | zones | ช่วง ZoneIndex | หมายเหตุ |
|---|---|---|---|
| vfc_1286 | **171** | 59–2224 | **เมืองหลัก — ติดตั้งใช้แล้ว** |
| vfc_1620 | 209 | 4–1792 | อีก district |
| vfc_47 | 37 | 75–111 | โซนเริ่มเกม (ตรง TrafficSignals เดิมเป๊ะ) |
| vfc_999 | 507 | 7–2122 | master set ครบทุกแยก |
ไฟล์ decode แล้วทั้งหมดอยู่ `Anangay asset/VaultTool/static_intersection_*.raw`
## สิ่งที่ได้ในไฟล์ (โครงสร้างตรง StaticIntersectionDataBlob ของ client)
`{"ZoneInfos":[{ZoneIndex, Position, TrafficLights:[{Index, CrosswalkControlIndex, VehicleControlIndex}], TrafficLightPeriodControls:[{TrafficLightControls:[{TrafficLightStateFlags}], VehicleLanesOpen:[laneHandles]}]}]}`
- ZoneIndex = zone จริงใน zonegraph (ยืนยัน: 138/171 ตรงกับ mapping ที่ derive เอง ส่วนที่ไม่ตรงคือชุด derive ของเราตก/เกิน — **ไฟล์จริงชนะเด็ดขาด**)
- TrafficLightPeriodControls มี 1–7 แถวต่อแยก (ไม่ใช่ 2 เท่ากันหมด) — engine วน period ตามจำนวนแถวจริง
- VehicleLanesOpen = เลน handle ที่เปิดในแต่ละ period (ไว้ทำ lane gating ต่อได้)
## โค้ดฝั่ง server (Build 0/0)
- `ClientData/4229938/World/StaticIntersectionData.json` (ติดตั้งแล้ว, strip BOM)
- `RoadNetworkModels.cs`: + StaticIntersectionFile/StaticZoneInfoEntry/... models
- `CityTrafficEngine`: โหลดไฟล์นี้เป็นแหล่งหลักของ `GetAetherIntersectionZonePeriods` — init 171 zone ด้วย ZoneIndex จริง, period วนในจำนวนแถวจริงของแต่ละแยก (แหล่งเดิม InterZones.json เป็น fallback)
## บทเรียน VaultTool
- scan แบบ multi-term แล้ว (คั่นด้วย comma); ครั้งหน้าแนะนำคำ: `IntersectionPeriodConfig`, `MassConfig.prefab`, `SyncAetherAI`, `GadgetLodInfoHash`
- ชื่อ resource ไม่โผล่ใน catalogue (ยืนยันแล้ว 0 hit) — ต้องใช้เทคนิคค้นเนื้อหาด้วยชื่อ field เฉพาะแบบนี้ต่อไป
- NoAB `scripts/main/depends/maps/objects` = ตาราง dependency tree ชื่อ+MD5 (โครงสร้าง: [u32 len][name][0x00] ซ้ำ จบด้วย len=0 + MD5 16B บาง record) — parse เก็บไว้ `VaultTool/noab_names.json`, format ยังต้อง reverse ต่อ (record บางชนิดไม่มี hash คั่น)
---
# 💬 20 ก.ย. (ต่อ 7): NPC interact เต็มรูปแบบ + ระบบเดต (InviteRide) — Build 0/0
## สถาปัตยกรรมที่ค้นพบ
- **ปุ่ม F บน NPC ถูกคุมฝั่ง server** ผ่าน agent property: `SyncAgentPropertyChange(agentId, SyncAgentProperty{V, Type})` (S2C 68745768) / `SyncFullAgentProperty` (68299392) — `AgentPropertyType.EnableInteract = 0` คือสวิตช์เปิด interaction
- polymorphic framing: V = complex (0xFF + subclass fields) แล้วตามด้วย Type byte (client ใช้ Type เลือก subclass) — wire จาก WriteSyncAgentProperty (10046) + WriteAgentPropertyDataBool (394)
- **ระบบเดต = ตระกูล InviteRide**: `SyncInviteRideNpcInfo(instanceId, active)` (64133673) ปลุก NPC เป็นคู่เดต, `AgentPropertyType.InviteRideNpcInteract = 21` (List<{Index, Sprite, LabelId}>) = เมนู interact ตอนเดต, action ต่าง ๆ เป็น enum InviteRideActionType (ซื้อน้ำ/ตู้ claw/ลงจากตู้ vending)
- ของขวัญ: `AskInteractNpcWithGift(activityCfgId, itemId, count)` (63477049)
## สิ่งที่ implement
1. **`AgentPropertyMethods.cs`** (DTO ใหม่): AgentPropertyDataBool, SyncAgentPropertyBoolChange, InviteRideNpcInteractItem, AgentPropertyDataInviteRide, SyncAgentPropertyInviteRideChange, SyncInviteRideNpcInfo, GmStartInviteRideNpc, AskInteractNpcWithGift — ทั้งหมดตามลำดับ Lua
2. **Hook ตอน spawn NPC** (`GameRouter.Npc.cs`): หลัง SyncManagedLogicAgent ส่ง `EnableInteract=true` ให้ทุก static/crowd NPC ที่เสก → ปุ่ม F/เมนู interact โผล่บน NPC ทุกตัว
3. **Handlers เดต** (`GameRouter.Gadgets.cs`):
- `GmStartInviteRideNpc(npcId)` (65008383 — ยิงจาก GM console ของ client) → ack + `SyncInviteRideNpcInfo(active=true)` + push property เมนูเดต (Type 21)
- `AskInteractNpcWithGift` → ack (ของขวัญ sandbox ไม่จำกัด)
## ทดสอบ
- เข้าเกม → NPC ทุกตัว (ร้าน/เดินถนน) ต้องมีปุ่ม F → กดแล้วเปิดบทสนทนา (dialog echo รอบก่อน)
- เดต: GM console (ALT+F1) สั่ง StartInviteRideNpc → NPC กลายเป็นคู่เดต ตามไปทำกิจกรรม (ซื้อน้ำ/ตู้ claw ด้วยกัน)
- ถ้าเมนูเดตว่าง: ต้องเติม InviteRideNpcInteractList จาก config จริง (ยังไม่เจอใน dump — ค้น `LabelId` ใน VaultTool ต่อได้)
---
# 🌐 20 ก.ย. (ต่อ 8): แปลเมนูเสก NPC ในเว็บจีน → อังกฤษ — Build 0/0
## ปัญหา
เมนูเสก NPC ใน Web Debug Panel แสดงชื่อจีนจาก AgentConfig.json (888 ชื่อ unique) + UrbanDiversityPOIActionConfig.json (3,460 ท่า POI)
## วิธีแก้ — ตารางแปล CN→EN แบบ segmentation + char fallback
- เครื่องมือสร้างตาราง: `Anangay asset/VaultTool/build_translation.py` (+ char_en.json)
- dictionary ~1,000 คำ CN→EN (สร้างจาก frequency analysis 4 รอบ: stances/demographics/actions/objects/locations/roles)
- segment แบบ greedy longest-match → คำที่ไม่เจอ fallback char-level dict (261 chars)
- ผล: agents แปลสมบูรณ์ 336/888, POI 1,864/3,460 — **ที่เหลือเป็น partial อ่าน EN ได้ เหลือ CJK ปนนิดหน่อย** (proper noun ส่วนใหญ่)
- ตาราง: `ClientData/4229938/Configs/{agent_names_en,poi_actions_en}.json`
- **`NameTranslator`** (ใหม่, อยู่ Core เพื่อไม่ให้ circular): lazy load ตาราง, ชื่อที่ไม่มีในตารางคืนค่าเดิม
- `DebugApiServer` NPC catalog endpoint: ชื่อ agent + POI ผ่าน translator
- **ช่องค้นหายังค้นชื่อจีนเดิมได้**: `NpcCatalog4229938.Search` match ทั้งชื่อ EN แปลแล้ว + ชื่อจีนเดิม + id
- placeholder ค้นหาเปลี่ยนตัวอย่างเป็น EN/จีน (Security, Placeholder, 占位, Duck)
## หมายเหตุ
- ชื่อ teleport ใน panel มี EN อยู่แล้ว (Ivory Cove, Paradox Plaza ฯลฯ — cn เป็น secondary data)
- อยากได้คุณภาพเต็ม: ขยาย dictionary ต่อใน build_translation.py แล้วรันใหม่ → ไฟล์ json regenerate อัตโนมัติ
- ⚠️ ไม่ได้ git add/commit ทั้งหมดนี้ตามคำสั่งผู้ใช้ (repo ถูกลบ)
---
# 🎨 20 ก.ย. (ต่อ 9): WebUI ใหม่แบบ in-game settings + CBT2→CBT3 audio migration
## 1. Debug Panel รีดีไซน์ด้วย design system ของ PrivateServer1 (Drmk)
- port design tokens ทั้งชุด (light/dark theme, การ์ด blur, pill nav, ปุ่ม filled/tonal, keycap font Bahnschrift) มาเขียน mapping ให้ DOM ของเรา (.card/.npc-tab/button/input/table/#clock/#log) — **JS/endpoint เดิมไม่แตะ** (72 functions ยังทำงานเหมือนเดิม)
- header ใหม่: โลโก้ ANANTA (SVG จาก Drmk) + ปุ่มสลับธีม (จดจำค่าใน localStorage)
- backup ของเดิม: `DebugPanel/index.html.bak_before_restyle`
- build ผ่าน 0/0
## 2. CBT2 → CBT3: ความจริงเรื่อง Timeline/Animation + เสียงที่หายไป
- **Blocks: CBT2 เป็น subset ของ CBT3 เป๊ะ** (654 ไฟล์ ทั้งหมดมีใน CBT3; CBT3 มีเพิ่มอีก 1,026) — Timeline/Animation อยู่ใน VFS blocks ดังนั้น CBT3 มีข้อมูลทุกอย่างที่ CBT2 มีอยู่แล้ว ไม่มีอะไรต้อง migrate ระดับ block
- **GameRes: CBT2 มี 32 ไฟล์ที่ CBT8 ไม่มี (2.15GB) = audio ทั้งเกม!** CBT3 ถูกตัด Wwise banks ออกหมด (Bank/Streams + พากย์์ CN/JP/EN)
- **migrate แล้ว:** คัดลอกครบ 32 ไฟล์เข้า CBT3 GameRes → เสียง/เพลง/พากย์กลับมาทั้งหมด
- สคริปต์ใช้ซ้ำได้: `Anangay asset/VaultTool/migrate_cbt2_to_cbt3.py` (idempotent — รันใหม่ได้ทุกครั้งที่เกมอัปเดตแล้วตัดไฟล์)
## 3. repair-shop excerpts (DROP NEW)
เป็นชุดแพตช์อ้างอิงของระบบร้านซ่อมรถ/Shop/Teleport/SceneDevice จาก Drmk — ยังไม่ได้ port รอบนี้ (จุดที่น่าพอร์ตต่อ: `GameRouter.SceneDevices.cs` + `shop_timeline_fix.lua` เสริมระบบ shop/gadget ของเรา)
## 📁 ย้ายที่เก็บแล้ว (ไฟล์รับแล้ว): โฟลเดอร์ DROP NEW ถูกยุบรวมเข้า Anangay asset
- `Anangay asset/PrivateServer1/` — Drmk fork + WebUI ต้นฉบับ (DebugPanel: `PrivateServer1/Drmk.Server/Drmk.App/DebugPanel/index.html`)
- `Anangay asset/PrivateServer1.7z`
- `Anangay asset/repair-shop/` + `repair-shop.zip` — excerpts ระบบซ่อมรถ/Shop/SceneDevice (ยังไม่พอร์ต)
- งานที่ทำแล้วจากชุดนี้: Debug Panel เราใช้ design system ของ PrivateServer1 แล้ว (backup อยู่ที่ DebugPanel/index.html.bak_before_restyle)
---
# 🛠️ 27 ก.ย.: พอร์ตระบบร้านค้า/4S/tuning จาก repair-shop (DROP NEW) — Build 0/0
## ระบบที่ได้ (โค้ด battle-tested จาก Drmk แปลง namespace)
**ไฟล์ใหม่:**
- `RpcTypes/.../Game/ShopMethods.cs` — contracts ครบ: AskNpcShop(63563263), SyncFullCommodityInfos(64769566), AskNpcShopCommodityInfo(63699253), AskVehicleShopSpawnVehicle(63497070)+Result, AskBuyModifyParts(63701165, dict=Dict7Bit!), AskBuyCommodities(63614427), AskBuyCommodity(63970007), AskVehicleShopDestroyParking(63613647), ShopCommodityItem(Reader[366] — CommodityBadgeDiscountDict ต้อง Int7 ไม่งั้น desync 711 รายการ), NpcShopCommodityInfo(Reader[638])
- `Handlers/Game/GameRouter.Shop.cs` — caches (ShopConfig→Group→Commodity chain, commodity→part/tag/suit, default suits, part tags, suit rows) + 7 handlers + BuildAppearance (default suit base + bought overrides merged by PartTag; stat tags 13/20/21/22 ไม่เข้า visual) + PushUnlockedVehiclesAsync + ShopInteriors (west/seaside พิกัดจริง) + Continental 4S shop (94100019, spawn 3607.9,-0.1,2353.9 f-31.2)
- `Handlers/Game/GameRouter.Spoon.cs` — AskGetSpoonServerOutputPortIndexByType → port 0, AskDoSpoonServerActionByType → push สินค้าร้านใกล้ (60m) เข้าเมนูช่าง
- `Handlers/Game/VehicleTuningStore.cs` — persist ซื้ออู้ต่อ pid ที่ `save/vehicles/<pid>.json`
**แก้ไฟล์เดิม:**
- `WorldEntryState` + VehiclePartsByConfig/VehicleSuitByConfig/SessionUnlockedVehicleConfigs
- `SpawnDirectAsync` + พารามิเตอร์ `pushSummonStart` (push SyncStartSummonVehicle 68681875 — ไม่งั้น autoshop ปฏิเสธ "ไม่ใช่รถตัวเอง") + **appearance merge ใน SyncLogicVehicleEnter** (Parts/SuitId จริง ไม่ใช่ว่าง)
- `BuildUnlockedResult(world)` — รวมรถที่ซื้อ + parts/suit overrides (PublishGarageAsync โหลด tuning จากดิสก์ก่อน push)
- **data ใหม่ 5 ไฟล์** ใน Configs: ShopConfig(113)/ShopCommodityGroupConfig(117)/ShopCommodityConfig(2127)/VehiclePartConfig(1352)/VehiclePartSuitConfig(147) จาก world dump
**ที่ตั้งใจไว้ตอนแรกแต่ไม่ได้ทำ:**
- SceneDevices.cs ของ Drmk — handler เดิมของเรา (Gadgets.cs) ทำ semantics เดียวกันแล้ว (ack CheckIndex ของ client + mirror Sync) จึงไม่แทน
- GameRouter.Teleport.cs (373 บรรทัด: shop enter/leave teleport + CutscenePreview + fuse) — ชนกับ teleport flow สนามบินของเรา ต้องผสานระวัง → คิวถัดไป
- proxy Lua fixes (shop_waypoint_fix ฯลฯ) — เป็น fastpatch ฝั่ง client ใช้เมื่อจำเป็น
## ทดสอบ
1. เข้าร้านซ่อมรถ (west: 1439,1.5,2379 / seaside: 2791,1.4,1286) → เมนูปรับแต่งต้องมีสินค้า (log `[SHOP] npc shop=... pushed N`)
2. ซื้อชิ้นส่วน/สี/ชุดแต่ง → รถเปลี่ยนหน้าตา + กด F เข้ารถขับออกได้
3. 4S dealer (94100019) ซื้อรถ → รถ spawn ที่จุดส่ง + snap ตำแหน่ง + ขึ้น garage list
4. ซื้อแล้วรีสตาร์ทเซิร์ฟเวอร์ → ของยังอยู่ (`save/vehicles/<pid>.json`)
---
# 🔍 27 ก.ย. (ต่อ): วิเคราะห์ Player.log ของผู้เล่น — หลักฐานรถ aether และเทียบ DRMK
## บริบทจาก Discord DRMK-PS
- ผู้ใช้เขาแจ้ง "pedestrians ยืนนิ่งไม่เดิน" = ปัญหาเดียวกับที่เรา**แก้แล้ว** (PedestrianNetwork.json จากกราฟเมืองที่ถูกต้อง e107) — เรานำหน้าเขา
- polareve กำลังแก้ T-pose ในคัทซีนด้วยการถอด animation จาก CBT2 ฉีดเข้า CBT3 ("key is the injection method")
## ผล diff ไฟล์ CBT2(E:\Ananta_beta\Ananta) vs CBT3(trunk 4229938)
- **Blocks: CBT2 ⊂ CBT3 เป๊ะ** (0 ไฟล์ที่ CBT8 ไม่มี) — Timeline/Animation อยู่ใน blocks ดังนั้น CBT3 มีข้อมูลครบกว่า
- **GameRes: CBT2 มี 32 ไฟล์ (2.15GB) ที่ CBT8 ไม่มี = audio ทั้งเกม** → migrate ครบแล้ว (migrate_cbt2_to_cbt3.py)
- NoABFull ของ CBT3 ⊃ NoAB ของ CBT2 (ทุกไฟล์ใหญ่กว่า)
- ⇒ การฉีด CBT2→CBT3 ของ polareve ต้องใช้ **CBT2 ตัวเต็มของเขา** (install เราเป็นชุดย่อย) + injection ผ่าน CoreLib VFSBuilder (DecryptionVault พิสูจน์แล้วว่าใช้ได้)
## 🔥 หลักฐานใหม่จาก Player.log (Sep 20 01:42-01:45 — ผู้เล่นทดสอบหลังแก้แล้ว)
`[AetherVehicleLifecycle] Flush dropped missing BaseVehicle id=750000100001 type=NormalVehicle` ×212
**Chain ที่พิสูจน์ได้จาก stack trace:**
1. `GameSceneToClientImpl.SyncAetherAIVehicleLaneDatas` รับ LaneDatas ของเรา ✓
2. `AetherVehicleManager.OnSyncAetherVehicleLaneData` เข้า pending ✓
3. `City.UpdateStep1 → FlushPendingAetherVehicles → FlushPendingVehicleImpl` รันทุกเฟรม ✓ (timestamp promote โอเค)
4. **แต่ drop เพราะ "missing BaseVehicle"** — BaseVehicle = drive-layer vehicle object ที่ถูกสร้าง **asynchronously หลัง GO (prefab) instantiate**
**lifecycle vocabulary จาก IL2CPP stringliteral.json (pc_dump_new):** Flush dropped missing BaseVehicle / Flush failed to create VDE / GO Instantiate / refresh waiting for BaseVehicle / AetherVehicleGODecision (PriorityRender/ShouldNotUseGo) / AetherVehicleUnit (EnsureUnit/GOLoaded) / Attach invariant failed / View invariant failed / hasBaseVehicle={0} sameVde={1}
## สมมติฐานเหลืออยู่ (เรียงตามน่าจะเป็น)
1. **GO Decision ไม่ instantiate** — AetherVehicleGODecision ตัดสินจากระยะ/visibility/อะไรบางอย่างที่เรายังไม่ส่ง (ตรวจ: log ต้องมี ShouldNotUseGo ปนอยู่ไหม)
2. **prefab/dependency ของรถ aether หายใน VFS ของ CBT3** — ตรงกับงาน CBT2→CBT3 ของ polareve (โมเดล legacy spawn เห็นได้ = โมเดลหลักมี แต่ GO variant ของ aether อาจอ้าง dependency อื่น)
3. NpcAdd ต้องมาก่อน/พร้อม AddData
## การทดลองถัดไป (ต้องใช้เกมจริง)
1. ทดสอบรอบใหม่ด้วย build ปัจจุบัน → ส่ง `log/Player.log` กลับมา → ดูว่า drop เฉพาะรถไกลหรือ drop หมด
2. grep log หา `ShouldNotUseGo` / `GOLoaded` / `EnsureUnit` / `refresh waiting` — แยกว่า GO decision บล็อก หรือ prefab load พัง
3. ถ้า prefab พัง: ใช้ VaultTool หา path โมเดล (`Assets/Res/Prefab/Drive/Vehicles/*.prefab`) ใน VFS CBT3 เทียบ CBT2 → เจอตัวขาด = ยืนยันสาเหตุ → inject ตามแนว polareve
4. ทดสอบสลับลำดับ NpcAdd ก่อน AddData (แก้ที่ GameRouter.Shop/Spoon ไม่เกี่ยว — อยู่ที่ CityTrafficEngine.SpawnAroundPlayerAsync)
---
# 🚗 27 ก.ย. (ต่อ): เจอสาเหตุรถ aether ไม่ขึ้นจากโค้ด PrivateServer1 — แก้แล้ว Build 0/0
> หมายเหตุ: build 4229938 = **CBT8** (แก้คำเรียกเดิม CBT3)
## ค้นพบจาก GameRouter.Aether.cs ของ PrivateServer1 (2,406 บรรทัด, ใช้ได้จริงในเกม)
ลำดับ spawn รถ traffic ต่อคันที่ถูกต้อง (ของเราขาดขั้น 1-2 และ 4-5):
1. **`SyncLogicVehicleEnter`** — spawn legacy scene entity = **ตัวสร้าง BaseVehicle** (ของที่ flush หา!)
2. **`SyncSpawnVehicle`** (VehicleClientInfo, Interactable=false, Velocity=speed)
3. `SyncAetherAIVehicleAddData` (68916691 — ของเรามีอยู่แล้ว wire ตรงกัน)
4. **`SyncAetherAIChangeVehicleControl`** (ControlType=AetherVehicle)
5. **`SyncAetherAIVehicleForceGo`** (forceGo=true — สั่งรถเริ่มวิ่ง)
6. NpcAdd คนขับ + driver behavior task
**นี่คือสาเหตุ "Flush dropped missing BaseVehicle" ที่เจอใน Player.log** — เราส่งแค่ AddData (aether registration) โดยไม่มี legacy entity = ไม่มี BaseVehicle ให้ flush attach
**ข้อยืนยันอื่น:** ServerClock ของเขา = anchor unix + uptime (เหมือน mode 0 ของเรา → timestamp unix ถูกต้อง), intersection update wrapper ให้ wire เดียวกัน, `VehicleColorId` field name ต่างแต่ลำดับเดียวกัน = wire เดียวกัน
## สิ่งที่แก้ (TrafficAndCrowdDriver.cs)
- `SpawnAetherVehiclesAsync`: ต่อคันส่งครบ 5 ขั้น (LogicVehicleEnter → SpawnVehicle → AddData → ChangeVehicleControl → ForceGo)
- `RemoveAetherVehicleAsync`: ส่งคู่ AetherRemove + SyncDestroyVehicle (legacy cleanup ตาม lifecycle "DetachRaidLocal (SyncRemoveVehicle)")
- `SyncAetherAIVehicleForceGo4229938` DTO ใหม่
## รอทดสอบ
เข้าเกม → รถ traffic ต้องโผล่และวิ่งแล้ว (legacy entity ให้ BaseVehicle + aether AI ขับ + ForceGo เริ่ม) — ถ้ายังมี drop ใน Player.log ให้ดูว่า "refresh waiting for BaseVehicle" (รอโหลด ปกติ) หรือ drop ถาวร
---
# 🧵 27 ก.ย. (ต่อ): วิเคราะห์ log "Connection Lost" ของผู้เล่น — เจอ 3 บัค แก้ครบ Build 0/0
## log ที่ผู้เล่นส่งมา (ทดสอบ 01:48-01:57) เผยอะไร
1. **`unimplemented notify AskInteractCmd → accepted`** — ระบบ interact/เดต/dialog ของเรา**ไม่เคยทำงานเลย** ตั้งแต่แรก! สาเหตุ: `GameRouter.Build()` ใช้ whitelist `Enabled4229938MethodIds` — handler ใหม่ทุกตัวที่เพิ่งเขียนไม่ถูกลงทะเบียน ไม่ว่าจะมี [Handler] attribute แล้วก็ตาม
2. **`[AETHER] init sent` ×4 ใน 9 วินาที** — SendAetherInitForNpcAsync ไม่มี dedup (ยิงซ้ำทุกครั้งที่ spawn NPC batch)
3. **`[CROWD-MOTION-ERROR] Cannot access a disposed object: SemaphoreSlim`** สแปมหลัง client หลุด — background loop ยัง probe session ที่ dispose แล้ว
4. client ปิด connection เอง ("forcibly closed by the remote host") หลัง world-ready ~50 วิ — สาเหตุฝั่ง client ยังไม่ชัด (ต้องดู Player.log ฝั่ง client ของรอบนั้น)
## สิ่งที่แก้ (Build 0/0)
- **`GameRouter.cs` whitelist +17 method ids**: AskInteractCmd/AskInteractCmds, AskAgentStart/FinishNpcDialog, GmStartInviteRideNpc, AskInteractNpcWithGift, AskGadgetSceneRoomTrigger, AskEnter/ExistSceneRoom, AskAgentSceneRoomTrigger, AskNpcShop, AskVehicleShopSpawnVehicle, AskVehicleShopDestroyParking, AskBuyModifyParts, AskGetSpoonServerOutputPortIndexByType, AskDoSpoonServerActionByType, AskFinishTaskCounter — **ระบบ interact/dialog/เดต/scene room/ร้านค้า/spoon เดินจริงแล้ว**
- **TrafficAndCrowdDriver.GetPlayerPosition**: guard ObjectDisposedException → คืน no-player (สแปมหาย)
- **GameRouter.Npc.cs**: aether init dedup ต่อ session
## ⚠️ บทเรียนสำคัญสำหรับทุก handler ใหม่
`[Handler]` attribute อย่างเดียวไม่พอ — **ต้องเพิ่ม MethodId ใน `Enabled4229938MethodIds` (GameRouter.cs) ด้วยทุกครั้ง** ไม่งั้น router ปฏิเสธเงียบ ๆ ด้วยข้อความ "unimplemented"
---
# 🚙 27 ก.ย. (ต่อ 2): เปลี่ยนรถ traffic เป็น AI-task drive mode (จาก PrivateServer1) — Build 0/0
## ปัญหาจากผู้เล่น: รถบางคันไม่ขับ/ชนกันเละ/โผล่-หาย/ไม่มีคนขับ
สาเหตุ: สมองสองฝ่าย — server สตรีม IDM positions แย่งกับ client AI ที่ ForceGo สั่งวิ่ง
## แก้: สลับเป็น AI-task drive mode ตาม PrivateServer1 (โค้ดที่ใช้ได้จริง)
- พอร์ต `VehicleAiTaskMethods.cs` (CruiseAITaskParameters + enums + StuckCheckConfig) และ `BehaviorTaskCatalog.cs`
- spawn ต่อคัน (หลัง ForceGo): BuildCruiseWaypoints (จุดปลาย lane chain 12 จุด) → `SyncAssignVehicleAITask` (CruiseType=GoToTargetInOrderLoop, flags=SlowDownWhenArriveTarget|DynamicSpeed, drivingFlags=DFStopForCars|Peds|Lights|SteerAroundObjects, min/max speed ±, AI config pool [16,20-30]) → `SyncChangeVehicleAITaskState(Running)`
- **หยุดสตรีม LaneDatas** (IDM ฝั่ง server เหลือเป็น bookkeeping สำหรับ despawn อย่างเดียว) — สอง stream ชนกันทำให้รถสแนป/ชนกัน
- DespawnDistance 220→420 ลด churn
- คนขับ: NpcAdd wrapper + `SyncDoAetherAgentBehaviorTaskDef` (VehicleNpcTaskId = AnimState 3 จาก AetherNpcBehaviorTaskDefineConfig) — คนขับจะมีตัวตน/ทำท่าขับ
- Despawn เอาตามระยะอย่างเดียวตอนนี้ (client ขับออกไกลเร็ว)
## ค้างต่อ
- บัค intro cutscene เลือกเพศ MC ในรถ (ต้องดู Player.log ตอนเกิด)
- สกิล/พลังต่างตัวละครไม่ครบ — ตรวจ COMBAT-CATALOG perSpiritViews กับ FightSkillConfig รายตัว
---
# 🎬 27 ก.ย. (ต่อ 3): แผนพอร์ต Intro Cutscene (รถไฟ/กรอกชื่อ/เลือกเพศ) จาก PrivateServer1
## หลักฐานว่าทำได้
ภาพจาก DRMK-PS: intro รถไฟเล่นได้จริงถึงขั้น "录入信息" (กรอกชื่อ) + เลือกชื่อเรียก "Chenxiu" + 确认 — โค้ดทั้งหมดอยู่ใน PrivateServer1 ที่เรามี
## สถาปัตยกรรมของเขา: เควสขับคัทซีน
- **StoryRootTaskId = 60004938** (รากของ story chain ใน TaskConfig) — accept task รากนี้ = client เริ่มเล่น intro คัทซีนเอง (รถไฟ → กรอกชื่อ → เลือกเพศ → ขับรถ)
- chain เดินผ่าน NextTasks ใน TaskConfig; แต่ละ task มี counters (TaskConfig.Counter) ที่ต้องเต็มก่อน complete แล้ว advance ไป task ถัดไป
- client รายงาน event ผ่าน AskChangeTaskCounterValue / AskFinishTaskCounter
## ไฟล์ที่ต้องพอร์ต (inventory + ลำดับ)
1. `Drmk.Core/ClientData/Client4229938/TaskCatalogRepository.cs` (266 ln) — ChainFrom(root)/Counters(id)/Name(id) — เสริมเข้า TaskCatalog4229938 ที่เรามีอยู่แล้ว (TaskConfig ชุดเดียวกัน)
2. `WorldEntryState` +3 fields: QuestStates(dict u32→byte), QuestCounterValues(dict u32→int[]), CurrentQuestTaskId(u32) — Drmk มีอยู่ line 218-225
3. `Handlers/Game/GameRouter.StoryFlow.cs` (277 ln) — AcceptStoryTaskAsync / ReportCounterAsync / CompleteAndAdvanceAsync / StartStoryChainAsync / PushTaskContainerAsync / PushCurrentTaskAsync — **แทนที่** handler AskAcceptTask/AskSubmitTask/AskFinishTaskCounter แบบง่ายที่เราเพิ่งทำ (ของเขา container-based ครบกว่า)
4. `Handlers/Game/GameRouter.SwitchTimeline.cs` (1,161 ln) — ตัวขับ transition คัทซีน↔โลก (ReportPreSwitchSpiritFinish / AskSwitchSpiritComplete / SwitchTeleportTiming.Immediate|Apex) — เทียบกับ Switching.cs ของเราก่อนรวม (อาจทับกัน)
5. DTO ที่มาพร้อม (หาใน Drmk RpcTypes): SyncPlayerTaskContainer + task container structures
6. whitelist: AskChangeTaskCounterValue, AskDoGuide, AskFinishGuide (+ตัวที่ไฟล์ใหม่ส่ง)
7. World entry ของเรา (WorldRouter) ต้องเปลี่ยน opening=false → accept story root (StartStoryChainAsync) หลัง ready — แล้ว client เล่น intro เอง จบแล้วถึงเข้า WORLD-MIN แบบเดิม
## ลำดับทำ (รอบหน้า — งานสด ๆ ประมาณ 1 session)
1. พอร์ต TaskCatalogRepository (ChainFrom/Counters/Name) + เติม 3 fields ใน WorldEntryState
2. พอร์ต StoryFlow.cs ทั้งไฟล์ (แก้ namespace + GetStateIfExists) + ลบ handler เควสเก่าที่ทับ + whitelist เพิ่ม
3. Build + ทดสอบ: เข้าเกม → intro รถไฟต้องเล่นถึงหน้ากรอกชื่อ/เลือกเพศ → confirm → คัทซีนรถ → โลก
4. รอบหลัง: SwitchTimeline.cs (คัทซีนอื่น ๆ นอก intro) + สกิลตัวละครให้ครบ (COMBAT-CATALOG perSpiritViews vs FightSkillConfig)
## ⚠️ ข้อควรระวัง
- PushTaskContainerAsync ต้องมาก่อน ack เสมอ (รูปแบบเดียวกับบทเรียนร้านค้า)
- เควสเดิมที่เราทำ (accept/submit/chain) จะถูกแทน — อย่าลืมย้ายความสามารถ AskFinishTaskCounter ไปรวม
- TaskCatalogRepository อ่าน TaskConfig.json ที่เราติดตั้งไว้แล้วใน Configs ✓
## 🎯 27 ก.ย. (ต่อ 4): minimal intro trigger ใส่แล้ว
- WorldEntryState + QuestStates/QuestCounterValues/StoryChain/UnlockedQuestIds/CurrentQuestTaskId
- World entry (V7 ready): push `SyncPlayerSingleTask` (story root 60004938, Accepted) + `SyncCurrentTask(taskId=60004938)` + `SyncPlayerAllTask(CurrentTask=60004938)` — client ควรเริ่ม intro รถไฟ/กรอกชื่อ/เลือกเพศเองเมื่อ current task = story root
- TaskCatalogRepository พอร์ตแล้ว (ChainFrom/Counters/Name) พร้อมสำหรับ StoryFlow เต็ม
- รอบหน้า: ถ้า intro เล่นถึงหน้ากรอกชื่อแต่ confirm แล้วค้าง → ต้องพอร์ต GameRouter.Quests.cs (container push) + StoryFlow.cs เต็มตามแผนด้านบน
## 🔍 27 ก.ย. (ต่อ 5): ผลจาก Player.log จริง (CBT8 trunk, ทดสอบ 03:01)
- **StoryClient รับ S011 ของเราแล้ว**: `CreateClientNode Nid 冲突 nid=1 old=S011PlayerNet new=StoryServer.Story.World.S011.S011PlayerNet` — conflict+rebuild นี้อาจเป็น flow ปกติ (server-authoritative)
- **ผู้เล่นยืนยัน intro เล่นถึงเลือกเพศ MC ในรถได้** — แปลว่า intro flow ใช้ได้บางส่วนอยู่แล้ว บัคที่เหลือต้องระบุอาการเฉพาะ
- `AkxManagerSprint: Token ว่าง` ×3 — น่าจะเป็น AI-chat service (ต้อง backend NetEase) ไม่ใช่คัทซีน
- `Metro got 0 gadget infos` — AskMetroGadgetIds คืนว่าง (รถไฟไม่มี gadget info — ปรับได้ทีหลัง)
- **ทดสอบนี้ยังเห็น `unimplemented AskInteractCmd`** — แปลว่าทดสอบด้วย build เก่า (ก่อน whitelist fix) — รอบหน้าทดสอบด้วย build ใหม่เท่านั้น
## สิ่งที่ต้องทำต่อ (เรียงตามความคุ้ม)
1. ผู้เล่นทดสอบด้วย build ใหม่ (whitelist ครบ + AI-task traffic) → รายงาน: รถวิ่งไหม / intro ติดตรงไหน / อาการเป๊ะ
2. ถ้า intro ค้างหลังเลือกเพศ: พอร์ต `GameRouter.Quests.cs` (container push เต็ม) + StoryFlow.cs ตามแผน
3. สกิลตัวละคร: เทียบ perSpiritViews กับ FightSkillConfig ราย spirit หาตัวขาด
4. AskMetroGadgetIds คืน gadget จริงจาก RailStationConfig (เสริมประสบการณ์รถไฟ)
---
# 📦 27 ก.ย. (ต่อ 6): แผนดึง intro/content จาก CBT2 → CBT8 (NoAB injection)
## หลักฐาน
- client CBT8 ไม่เล่น intro คัทซีนเลย — intro content ถูกตัดออกจากชุด CBT8
- client โหลด content เริ่มต้นจาก **NoAB/NoABFull = ResourcePackage** (log: "[ResourcePackage] index main/maps/depends path")
- CBT2 NoAB: main=698 entries, CBT8 NoABFull main=1,260 entries — **คนละชุด content จริง** (CBT2 มี intro, CBT8 ถูกแทนที่)
- CBT2 install: `E:\Ananta_beta\Ananta\Ananta_Data\StreamingAssets\NoAB\{main,maps,depends,objects}` / CBT8: `...\NoABFull\{เดียวกัน}` + `NoABFull.manifest` (arraySize=133,398)
## ขั้นตอน injection (session หน้า)
1. **Parse index ทั้งสอง** ด้วย vault decoder (`code/L50Index.cs` — index v5/v8; โครงหัว: [u32 count][u32 count][u32 size][ffffffff]...) → ได้ entry list (hash/offset/len) ของ CBT2 NoAB และ CBT8 NoABFull
2. **Diff** → CBT2-only entries = content ที่ CBT8 ตัดออก (intro คัทซีน/รถไฟ อยู่ในนี้)
3. **Decode ตัวอย่าง** (L50Payload) ตรวจว่าใช่ intro จริง (ค้นคำว่า Timeline/Train/S011/Scene ใน payload)
4. **Inject 2 ทาง**:
a. ง่ายสุด: คัดลอกไฟล์ payload ของ CBT2 + เขียน `NoABFull.manifest` ใหม่ (ต่อท้าย entry array — รูปแบบ manifest ตาม `code/L50Index.cs`)
b. ทางการ: CoreLib `VFSBuilderBuildGroup` สร้าง vfc ใหม่ (vault พิสูจน์แล้ว)
5. **ทดสอบ**: เปิดเกม (ไม่ต้องรอ server) — client เล่น intro เองถ้า content ครบ
## ข้อควรระวัง
- NoABFull.manifest มี hash ตรวจ/ขนาด array — ผิดแม้บิตเดียว client อาจไม่ mount
- CBT2 payload อาจอ้าง dependency ที่ CBT8 ไม่มี → ต้องเอา depends ของ CBT2 ตามไปด้วย
- fastpatch.zip (server เราเสิร์ฟ) ไม่เกี่ยวกับ NoAB — ไม่กระทบ
## 🧩 สรุปโครง story ของ client (จาก dump)
- **18 story worlds**: S001(Player+Arrest), S002(Flee/Interact/Escort/Exam/**Vehicle**), S004(Concert), S005, S006, S009, **S011(vehicle boarding)**, S019 — โค้ด story มีครบทุก build ไม่ได้หายไปไหน
- **intro cutscene = data-driven**: scenario อยู่ใน TaskConfig chain (60004938) + timeline/scenario data — client มี engine ครบ รอ server ส่ง task/event ที่ถูกต้อง
- **ไม่มี dump เฉพาะ CBT2** — dump ที่มี (ULTIMATE + pc_dump_new) ตรงกับ CBT8 ทั้งหมด; ถ้าต้องการ dump CBT2 ใช้ GameAssembly.dll ของ `E:\Ananta_beta\Ananta` + Il2CppDumper สร้างเองได้
- **ไม่ใช่ปัญหาไฟล์**: CBT2 blocks ⊂ CBT8 blocks, NoABFull ⊃ NoAB — content data อยู่ครบ ปัญหาอยู่ที่ story/event flow ฝั่ง server
## สรุปประเด็น intro (สถานะปัจจุบัน)
1. server push story root 60004938 (Accepted) + SyncCurrentTask แล้ว ✓
2. client รับ S011 story node แล้ว ✓ (log: CreateClientNode rebuild)
3. คัทซีนยังไม่เล่น → **ขาด story event/command ถัดไปที่ server ต้องส่ง** (เช่น scenario advance, cutscene trigger, dialogue events) — ต้องจับจาก Drmk StoryFlow หรือ log จริงเทียบ
## ขั้นต่อไปที่แม่นที่สุด
ทดสอบด้วย build ใหม่ (whitelist ครบ) → เข้าเกม → เก็บ **ทั้ง server log + Player.log** → หา story command ที่ client รอ (StoryClientNetManager.ReceiveCommands จะโชว์ทุก command) → เทียบกับสิ่งที่ server ส่ง → เติม command ที่ขาด
## ✅ 27 ก.ย. (ต่อ 7): พอร์ต Quests.cs (container engine) เสร็จ — Build 0/0
- `GameRouter.Quests.cs` (ใหม่): BuildTaskContainer/PushTaskContainerAsync (SyncPlayerAllTask จาก QuestStates จริง), AcceptQuestAsync (counter slots ตาม TaskConfig), SubmitQuestAsync (Submited + auto-advance NextTasks), ReportCounterAsync (all-full → submit เอง), handlers AskAcceptTask/AskAcceptAndSetCurrentTask/AskChangeTaskCounterValue/AskFinishTaskCounter/AskSubmitTask/AskDoGuide/AskFinishGuide
- ลบ handler เควสเก่าซ้ำใน Endpoints.cs (AskAcceptTask/AskSubmitTask/AskFinishTaskCounter แบบง่าย) — World.cs อ้าง QuestEntry/QuestStateAccepted ตัวใหม่แล้ว
- ตอนนี้ story chain 60004938 (intro) เดินได้ปลายทางถึงท้าย chain เมื่อ client รายงาน counter/submit
## 📱 27 ก.ย. (ต่อ 8): Social/Moments app — DTO + catalog + data พอร์ตแล้ว (รอ handlers)
- พอร์ตแล้ว: `SocialAppMethods4229938.cs` (DTO โพสต์/คอมเมนต์ครบ) + `SocialCatalogRepository4229938` + config 3 ไฟล์ (SocialMedia{,NPC,Comment}Config.json)
- **ค้าง**: `GameRouter.SocialApp.cs` (506 ln) กับ `PhoneAppContent.cs` (419 ln) — อ้าง `Drmk.Server.State.SessionState` (SocialReadPosts/SocialLikedPosts/SocialPlayerComments) + config `Gameplay.SocialApp` — รอบหน้าพอร์ต handlers + เติม session fields (เก็บใน session.Items แทน state class ก็ได้)
- whitelist เพิ่ม: AskMomentsPostSimpleInfos, AskMomentsPostInfos, AskMomentsUnreadMessage, AskMomentsHaveUnreadMessage, AskMomentsMarkRead, AskMomentsLikePost, AskMomentsSendCommentWithId, AskMomentsTapPostWithCount, AskServerGraffitoUrl, AskChangeHackerName, AskReadHackerNewPost, AskAcceptHackerPostTask
## 🌐 27 ก.ย.: วินิจฉัย "แปลภาษา dev panel ในเกมไม่เปลี่ยน" — พบว่าไม่เคย implement จริง
ตรวจสายครบ: translation.en.txt ✓ มี | private-server.json ไม่มี key | generator **ไม่มี hook เลย (0 match)** | fastpatch.zip สร้างโดย generator จึงไม่มี hook ฝั่ง client
**สรุป: ที่ "เคยทำ" มีแค่ไฟล์พจนานุกรม — Lua hook ไม่เคยถูกพอร์ตเข้า generator เลย**
### แผนแก้ (session หน้า — ตามลำดับ)
1. อ่าน reference: `Ananta.Proxy/proxy/tools/generate_client_config_patch.DRMK.reference.js` (hook: env DRMK_DEV_MENU_DICT + โหลด dictionary + HookTable ~500 บรรทัด)
2. พอร์ต hook เข้า `generate_client_config_patch.js` **ของเรา** (74KB — ห้ามทับ แก้จุดเดียว: สร้าง Lua hook ไฟล์ใหม่จาก dictionary + patch เข้า fastpatch)
3. แหล่ง dictionary: `config/localization/translation.en.txt` (มีแล้ว) → generator อ่านแล้วฝัง
4. fastpatch.zip ที่เสิร์ฟ = `fastpatch_4229938.zip` ข้าง server.js (สร้างจาก generator) — regenerate แล้ว client ต้องอ่าน Lua ใหม่ (log: "read lua bytes from fast patch" ต้องมีไฟล์ hook)
5. ทดสอบ: console ต้องขึ้น `dev-menu dictionary: 1352 entries, 20 rules`
## ✅ 27 ก.ย. (ต่อ 9): fastpatch แปล dev panel สร้างสำเร็จ — พร้อมเทสต์
- รัน generator ของ UI package ผ่าน shim (EN generator ชี้ config ของเรา) + NODE_PATH fengari + DRMK_DEV_MENU_DICT
- ผล: `Ananta.Proxy/proxy/public/fastpatch_4229938.zip` (30KB) มี UidLayerPanelStore.lua 86.5KB ที่ฝัง dev-menu hook + dictionary (1,084 entries จาก translation.en.txt ของเรา)
- **คำสั่ง regenerate เมื่อไหร่ก็ได้:** `cd "Anangay asset/Ananta_UI_Translation_RU_EN/Drmk.Proxy/proxy/tools" && NODE_PATH="<proxy>/node_modules" DRMK_DEV_MENU_DICT="<ngp>/config/localization/translation.en.txt" node generate_client_config_patch.en.js` (shim แก้ require เป็น absolute แล้ว)
- ⚠️ Run-All ที่ rebuild fastpatch จาก generator ของเราจะ**ทับ hook** ทุกครั้งที่สตาร์ท — ต้องรันคำสั่งข้างบนซ้ำหลังสตาร์ท หรือรอบหน้า merge hook เข้า generator ของเราถาวร
- ปล. dictionary เหลือ 1,084 entries (จาก 1,352 ตามที่คนทำไว้) — translation.en.txt ของเราอาจเป็นเวอร์ชันต่างจากของเขา
## 🚙 27 ก.ย. (ต่อ 10): แก้ traffic จาก feedback รอบทดสอบ — Build 0/0
feedback: รถแทบไม่ขยับ/บางคันขยับแล้วชนคันไม่ขยับ/หายกลางอากาศทั้งที่มองเห็น
- **หายกลางอากาศ** = despawn ตามระยะที่คำนวณจากตำแหน่งจำลองของเรา (เพี้ยนเพราะ client ขับเองคนละเส้น) → รถ ClientDriven เปลี่ยนเป็นเก็บตาม**อายุ 5 นาที** แทนระยะ
- **คันไม่ขับ** = lane ที่ไม่มีทางต่อ (Next ว่าง) → ไม่เลือก spawn บน lane dead-end อีก
- ชนกัน: คันที่ขับชนคันไม่ขับ — คันไม่ขับจะลดลงเมื่อ spawn เฉพาะ lane มีทางต่อ (ของเดิมที่เหลือจะหมดไปเอง)
- (NPC ไม่มีอนิเมชั่น / Quest coming soon / Contacts ว่าง / CBT2 camera cutscene = คิวใหญ่ แยกทำรอบหน้า)
## 🔬 27 ก.ย. (ต่อ 11): ผลวิจัย opening trigger — คำตอบสุดท้ายของสายนี้
- `LoginManager.isFirstLogin` / `enterTutorial` (Lua-exported) = ตัวตัดสิน intro ฝั่ง client — set จาก Lua login flow
- **LoginGateRouter ของ Drmk = เหมือนเราเป๊ะ** (TryLogin → SyncRoleList(pid)) — ไม่ใช่ server
- Story worlds 18 ตัวครบทุก build — ไม่ใช่โค้ดที่หาย
- **สรุป: intro เป็น client-side local state + story data ที่ server ส่งต่อ** — client ที่เคยเล่น (local save บอกว่าเคยดู opening) จะข้ามเอง การบังคับให้เล่นใหม่ = ลบ local state ของ client หรือ push story task ให้ถูกจังหวะ (ทดสอบอยู่)
- ปัจจัยที่ยังควบคุมได้จาก server และควรทดสอบ: story root task 60004938 (ใส่แล้ว) + story command stream (SyncStoryCoreClientInfo/ServerInfo — โครงมีใน dump)
## คิวงานถัดไป (เรียงแล้ว)
1. ทดสอบ build ปัจจุบัน (traffic AI-task + quest container + whitelist + fastpatch แปลภาษา)
2. ถ้า intro ยังไม่เล่น: เทียบ StoryClientCommand stream จาก log (ReceiveCommands) กับที่ server ส่ง
3. สกิลครบทุกตัว (COMBAT-CATALOG vs FightSkillConfig)
4. Contacts app (NETWORK OF CONTACTS) — ผูก NPC จริงเข้า contact data
5. CBT2 camera cutscene — ตามแผน NoAB injection
## ✅ 27 ก.ย. (ต่อ 12): audit "no dummy" รอบ 1 — Build 0/0
- `AskSceneDeviceRecordValue` → เพิ่ม mirror `SyncSceneDeviceValueChange` (client apply ค่าจริงจาก Sync ไม่ใช่แค่ ack)
- `AskReadCommodities` → handler จริง: push inventory ของร้านจาก ShopCommodityCache (เดิม fallback)
- `AskBuyCommodityToBag` / `AskBuyCommoditiesToBag` → ack แบบ sandbox (เศรษฐกิจไม่จำกัด ตาม design)
- **placeholder ที่เหลือ (ทำไม่ได้จริงโดยไม่มี backend/document):**
- `GadgetEntityInfo/DestructibleInfo/TaskEventInfo` DTO stubs — list ว่าง by design (server ไม่ส่ง entity ใน grid stream)
- Spoon port 0 — ตรงกับพฤติกรรม fallback ของ Drmk เอง
- สกิลบางตัว / intro full flow / CBT2 cutscene — ตามแผนคิวใหญ่
- ⚠️ server เดิม (Ananta.App.exe) ยังรันอยู่ล็อก DLL — ปิดแล้ว build ใหม่ก่อนทดสอบ
## ✅ 27 ก.ย. (ต่อ 13): merge dev-menu hook เข้า generator ถาวร — Build 0/0
- ยก template ทั้งบล็อก (UID override + dev-menu hook + dictionary.lua + TranslateCore/RULES/ResolveRoot) จาก generator ของ UI translation package เข้า `generate_client_config_patch.js` ของเรา
- เพิ่ม consts: showDiagnostics/languageOverride (+ buildLuaDictionary + dictPath resolution อ่าน `ui.devMenuDictionary`)
- **ผล:** `Run-All` ที่ rebuild fastpatch ทุกสตาร์ท ตอนนี้ได้ patch แปล dev panel มาด้วยเสมอ — ไม่ต้องรันคำสั่งแยกอีก
- dictionary = `config/localization/translation.en.txt` (1,084 entries) เปลี่ยน/เพิ่มคำแปลได้ที่ไฟล์นี้แล้ว regenerate
- ตรวจแล้ว: fastpatch_4229938.zip มี hook ✓ (Lua 72.7KB)
## ⚠️ 27 ก.ย. (ต่อ 14): feedback รอบทดสอบ AI-task — สรุปสาเหตุ + คิวแก้
**1. Reconnecting บ่อย** — สงสัย handler ใหม่ตัวใด parse พังแล้ว exception ลอยขึ้น session (GetArgs ที่ shape ไม่ตรง) หรือ client พังที่ packet ใหม่ → ต้องดู server log ตอน reconnect (มี exception ไหน) + Player.log
**2. รถไม่ขับ/ทับกัน/หาย** — AI-task บางคันไม่ทำงาน (waypoint chain สั้น/ทิศผิด), spawn ทับกัน (ยังไม่มี min-gap), หาย = churn ยังเร็ว
**3. ไม่มีคนขับ** — NpcAdd ส่งแล้วแต่ NPC ไม่ render ในรถ (อาจต้องมี agent sync info แบบ static NPC หรือ behavior task ผูกผิดจังหวะ)
**4. NPC อนิเมชั่นมั่ว** — AmbientPoiActions สุ่ม 1-10 ไม่ตรงบริบท → ควรใช้ idle ตรงบริบท/ไม่สุ่ม
**5. Quest coming soon + ไม่มี NPC รับเควสตามทาง/หมุด** — ต้อง spawn quest NPC ตามตำแหน่ง task + push map marker (TaskConfig มีข้อมูล) — งานใหญ่
**6. Cutscene trigger ไม่ได้** — ผูกกับ story command stream (SyncStoryCoreClientInfo) ที่ยังไม่มี flow เต็ม
**7. Talent/Contacts** — คิวหลัง
## ลำดับแก้ที่คิดว่าคุ้มสุด (รอบหน้า)
1. **กัน session ตายจาก handler exception** — ครอบ try/catch ทุก handler ใหม่ + ตรวจ server log ตอน Reconnecting ว่า exception ตัวไหนยิงซ้ำ
2. **รถ: สลับทั้งระบบเป็น AI-task ล้วน** — spawn เฉพาะ lane มี chain + min-gap ระหว่างรถ (แบบ Drmk line 814) + ลด churn
3. **คนขับ** — ศึกษา NpcAdd ของ Drmk ว่าต้องมี AgentSyncClientInfo ประกอบไหม (แบบ static NPC ของเรา)
4. **NPC เดินรับเควส + หมุด** — spawn quest NPC จากตำแหน่งใน TaskConfig/TaskEvent + marker push
---
# ✅ 27 ก.ย. (ต่อ 15): ปิดคิว 6 ข้อจาก HANDOFF — Build 0/0
ทำคิวงานถัดไปครบ 6 ข้อ (build ผ่าน 0 error 0 warning แล้ว — รอผู้เล่นทดสอบในเกม)
## 1. Reconnecting — session ไม่ตายจาก exception อีกต่อไป
- **ต้นตอที่เจอ (จากโค้ด ไม่ใช่แค่สมมติ):** `TcpServer.HandleClientAsync` ปิด session ทันทีถ้ามี exception หลุดจาก dispatch โดยมี 2 ช่องทางหลุด: (a) `RpcPacketReader.Parse` พังแล้ว **rethrow** (`RPC_PARSE_FAILED`) (b) fallback reply ตอน handler exception ตัวมันเอง throw ซ้ำ
- **แก้:** `RpcRouter.DispatchAsync` — parse พัง = log + ข้าม frame (session อยู่ต่อ); ครอบ fallback reply ด้วย try/catch ชั้นใน; `RpcFrameDispatcher` ครอบ `router.DispatchAsync` อีกชั้น (last-resort) — exception ที่เหลือเสียแค่ 1 frame ไม่ใช่ทั้ง session
- log ใหม่: `rpc parse failed ... — session kept` / `dispatch survived frame ... (session kept)` — เจอบ่อย ๆ แปลว่ามี packet ที่ parse พังต้องตามแก้ DTO ต่อ
## 2. รถ AI-task — chain + min-gap + churn
- **spawn เฉพาะ lane มี chain จริง:** เพิ่ม `ChainHops()` — ต้องมี lane ต่ออย่างน้อย 3 hops (เดิมเช็คแค่ Next.Count>0 เจอ lane ปลายทางแบบมี 1 ทางต่อสั้น ๆ รถก็นั่งแห้ง) + `SpawnMinLaneLength` 15→30 ม.
- **min-gap (แบบ Drmk):** same-lane gap 25→30 ม. + **junction clearance** — ถ้ารถคันอื่นอยู่บน next lane ห่างปากทาง <15 ม. ไม่ spawn (เดิมเกิดมาทับท้ายรถพอดี) + world overlap 2.5→4 ม.
- **churn:** lifetime รถ ClientDriven 5→15 นาที (อาการ "หายกลางอากาศ" คือ lifetime recycle เดิมตัดรอบสั้นไป — ตอนนี้ recycle ที่ 900 วิ)
## 3. คนขับรถ — เจอสาเหตุจริง: NpcFormworkId ผิดชนิด
- **สาเหตุ:** เราส่ง `spawnEntry.DriverNpcType` (= 45200091 จาก VehicleSpawnConfig — เป็น **persona id**) เป็น `NpcFormworkId` แต่ client resolve field นี้กับ **AgentConfig catalog** (Drmk ยืนยันใน log ตัวเอง: "司机模板池 (AgentConfig,非 persona)") → id ไม่เจอ = NPC ถูก drop เงียบ ๆ = ไม่มีคนขับ
- **แก้:** คนขับ/ผู้โดยสารถูกสุ่มจาก `NpcCatalog4229938` (AgentConfig.json ที่มีอยู่แล้ว) เฉพาะ category=citizen ที่มี model
- **ครอบจักรยาน:** คัดลอก `AetherNpcBehaviorTaskDefineConfig.json` (391 records) จากชุด Drmk มาใส่ `ClientData/4229938/Configs/` — ก่อนหน้านี้ไฟล์นี้**ไม่มี** ทำให้ `BehaviorTaskCatalog.VehicleNpcTaskId` = 0 → ไม่เคยส่ง `SyncDoAetherAgentBehaviorTaskDef` ให้คนขับเลย ตอนนี้ task id resolve ได้ (AnimState=3 มี 46 rows)
## 4. NPC อนิเมชั่น idle ตรงบริบท
- เดิม `AmbientPoiActions = [1..10]` สุ่มรวม **走路 (เดิน原地)/跑步 (วิ่ง)/受惊 (ตกใจ)** — อนิเมชั่นพวกนี้บนคนยืนดูพัง
- ตอนนี้เลือกจาก pool ที่สร้างจากชื่อใน UrbanDiversityPOIActionConfig: คนเดินถนน = pool **站姿\*** (ยืน: โทรศัพท์/มองไปมา/พิงผนัง/ปรบมือ ฯลฯ) + idle; คนในร้าน = 站姿 + **商品/售货机/购物** (เลือกของ/ใช้ตู้ vending)
## 5. เควสตามทาง + หมุด + Contacts app
- **Quest NPC + หมุด:** ระบบใหม่ใน `GameRouter.Quests.cs` — ตอน accept task (หรือ chain advance) เซิร์ฟเวอร์เสก **quest NPC 1 ตัวต่อ session** ยืนข้างหน้าผู้เล่น ~10 ม. เยื้องขวา 4 ม. (citizen จาก AgentCatalog, EnableInteract ครบ = กด F คุยได้) และ push **`SyncTrackGPS` (63861463)** = หมุดแผนที่ (writer ตรงจาก RPCSerializeAuto.lua: `TeamTrackGPS{TrackPosition, TrackGPSId, TrackGPSType}` — ตัวนี้ยังไม่เคยมีใครส่ง ทั้งเราและ Drmk) — NPC ตัวเดียวกัน "เดิน" ไปจุดใหม่เมื่อเควสเปลี่ยน (ไม่รกแมพ)
- DTO ใหม่: `TeamTrackGPS4229938` ใน TaskAndDialogMethods.cs
- ถ้า accept ตอน world-entry (transform ยังไม่พร้อม) จะข้ามแล้วจับตอน accept ถัดไปในเกม
- **Contacts app (NETWORK OF CONTACTS):** แอปนี้อ่านจาก push เท่านั้น (ไม่มี C2S fetch — ตรวจ Lua delegate ครบ) → พอร์ต **`AddSpiritPhoneInfos` (64199774)** จาก Drmk (PushSpiritHandbookAsync) ยิงตอน LoginGame หลัง SyncPlayerInfo: 2 เบอร์ summon + **24 คอนแทกต์ชื่อ NPC จริง** (AgentConfig → NameTranslator EN) ต่อทุกตัวละคร + implement C2S `AskPhoneAddContact/EditContact/DeleteContact` แบบ sandbox (เก็บใน session แล้ว re-push ทั้งเล่ม) + group/option handlers เป็น ack — **whitelist เพิ่ม 8 ids แล้ว**
- DTO ใหม่: `Ananta.RpcTypes/.../Game/PhoneMethods.cs`
## 4b. สกิล (audit — ไม่ต้องแก้โค้ด)
- เทียบ FightSkillConfig (88 styles) vs FightSpiritConfig (109 spirits): style ที่ผูก spirit ทั้ง 8 ตัว resolve ได้หมด / `AllowsSkill` รับทุก skill ใน SkillConfig อยู่แล้ว / ทุก style ปลดล็อก (`UnlockedStyleIds`) — **ไม่เจอตัวขาดระดับข้อมูล** ถ้ายังเจอสกิลหายในเกมให้เอา log `[COMBAT] reject unknown-skill` มา (ตอนนี้ไม่ควรขึ้นแล้ว)
## 6. CBT2 NoAB injection — parse + diff สำเร็จ (ขั้น decode/inject ยังเปิด)
- เครื่องมือใหม่: `Anangay asset/VaultTool/noab_diff.py` + ผลที่ `cbt2_only_main_names.json`
- **ยืนยันโครง `main`:** header `[u32 count][u32 count][u32 size][0xFFFFFFFF]` — CBT2 = **698** entries, CBT8 = **1260** (ตรง devlog เดิมเป๊ะ); ข้างในเป็น catalog ชื่อ asset แบบ ASCII อ่านได้
- **diff `main`:** CBT2-only = **3,253 ชื่อ** รวม **scene 17 แห่ง** เช่น `sc_level1206_c_TrainBossBattle.unity`, `sc_room_*`, raid หลายด่าน + **ชุด QTE TimelineScript ครบ (Tap/Swipe/Shake/LongPress QTE + DodgeTrain)** = ยืนยันเนื้อ intro/QTE รถไฟอยู่ใน CBT2 จริง
- **ยังติด:** `maps`/`depends` เป็น native `ResourcePackage` format (ไม่มี ASCII เลย — records [u32 hash][u32 off][u32 len] ต้อง RE จาก UnityPlayer `ResourcePackage_Bindings`) และ `NoABFull.manifest` = 4B count (133,398) + record 22 ไบต์/แถว (หารลงตัวเป๊ะ: 2,934,756/133,398=22) แต่ยังไม่ยืนยัน semantics ของ field — ขั้น inject (คัดลอก payload + เขียน manifest ใหม่) ต้อง decode ตรงนี้ก่อน ไม่งั้นเสี่ยง mount ไม่ขึ้นทั้งเกม
## บทเรียนใหม่ (เพิ่มในกฎเหล็ก)
- **`NpcFormworkId` ของ SyncAetherAIVehicleNpcAdd ต้องเป็น AgentConfig id เท่านั้น** — ห้ามใช้ persona id จาก VehicleSpawnConfig (45200091/45200093) — client drop เงียบ
- **`AetherNpcBehaviorTaskDefineConfig.json` ต้องอยู่ใน Configs เสมอ** — ถ้าหาย driver/passenger จะไม่มี behavior task (ยืนเฉย) แม้ formwork ถูก
- handler ใหม่ whitelist ครบ แต่ **client อาจส่ง C2S ที่ DTO เราไม่มี** — parse พังตอนนี้ไม่ฆ่า session แล้ว เห็น `rpc parse failed` ใน log = เก็บ method id นั้นมาเพิ่ม DTO
## วิธีทดสอบ (relog รอบเดียวเช็คได้หมด)
1. **เสถียรภาพ:** เล่น 10 นาที ดูว่า Reconnecting หาย (ถ้ายังมี — ส่ง server log มาดูบรรทัด `session kept`/exception)
2. **รถ:** รถวิ่งต่อเนื่องกว่าเดิม ไม่หายตรงหน้า (lifetime 15 นาที) ไม่เกิดทับท้ายรถที่แยก คนขับ+ผู้โดยสารต้องเห็นในรถทุกคัน
3. **คนเดิน:** อนิเมชั่นต้องเป็นท่ายืนธรรมชาติ (โทรศัพท์/มองรอบ/พิงผนัง) — ไม่มีวิ่ง原地/ตกใจอีก
4. **เควส:** รับเควส → NPC quest giver โผล่ข้างหน้า + **หมุดบนแมพ** กด F คุยได้; ส่งเควสแล้ว NPC เดินไปจุดใหม่ของเควสถัดไป
5. **โทรศัพท์:** แอป NETWORK OF CONTACTS ต้องมีรายชื่อ (142857/206337 + ชื่อ NPC ภาษาอังกฤษ ~24 ราย) และเพิ่ม/ลบคอนแทกต์ได้