This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

891 lines
124 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 ราย) และเพิ่ม/ลบคอนแทกต์ได้