124 KiB
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 / SendAetherIntersectionUpdateAsyncAnanta.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 SyncRunningMetroInfosAnanta.Handlers/Game/GameRouter.Vehicles.cs— ลบ stub AskGetAllMetroInfos เก่า, init Aether ใส่ intersectionsAnanta.Handlers/Game/GameRouter.Npc.cs— ส่ง init Aether พร้อม intersectionsAnanta.App/DebugApiServer.cs—/api/gacha/grant-tickets, configuseAetherProtocol/moveReset, POI ไดนามิกAnanta.App/DebugPanel/index.html— ลบคัทซีนรถไฟ, POI picker ยิงจาก APIAnanta.Gameplay/Traffic/RoadNetworkModels.cs— TrafficSignalFile เพิ่ม Intersections- ข้อมูลใหม่:
ClientData/4229938/Configs/Rail{Line,Stop,Station,Train}Config.json,VehicleSpawnConfig.json
ความรู้เชิงเทคนิคที่ค้นพบ (สำคัญมาก — อย่าทำพัง)
- UxSerializer อ่านเฉพาะ public FIELD (ไม่ใช่ property) เรียงตามลำดับประกาศ — DTO ที่ใช้ property จะ serialize เป็น object เปล่า (บัคเดิมของ MetroClientInfo)
- นาฬิกา server วิ่งเร็วกว่าจริง 15 เท่า — metro
ElapsedTime = เวลาตาราง(วินาทีจริง) × 15ไม่งั้นขบวนคลานเต่า - Aether lane handle = index ใน
ZoneGraphStorage.Lanes(flat array) — ZoneLaneData ไม่มี field handle; mapping ของเรา:handle = lane.Id - base ของ region(city base=0 → handle = lane id) — ยังไม่ยืนยันในเกม LaneHandle/DistanceAlongLaneอยู่ทั้งในClientVehicleInitData(spawn) และClientVehicleLaneData(sync ต่อเนื่อง, status flags: Normal=0, Teleport=4, StandBy=8)- คนขับรถจริง = NPC form 45200091, ผู้โดยสาร = 45200093 (จาก
VehicleSpawnConfig.json— มี ColorMod 9994xxxx เป็นสีรถ) MassTrafficIntersectionState: Start=0, Transition=1, Finishing=2; เฟสไฟของเรา: period 0 = กลุ่ม zebra คู่ (t∈[0,17)), period 1 = กลุ่มคี่ (t∈[17,30)) — cycle 30s, offset = |inter×7919|%30- ทุก Aether packet ได้รับการยืนยันว่าออกจริง: packets log รอบทดลองมี AddData 1,594 + LaneDatas 9,034 รายการ — client ยังไม่ render = ปัญหาอยู่ฝั่ง parse/gating ของ client
- UxContract: complex = 0xFF marker, Inline = ไม่มี marker, struct list ใช้
ItemObjectEncoding = Struct
ปัญหาค้าง #1: รถ ambient (client-driven) — ยืนยันแล้วว่าปลุกได้ แต่ตำแหน่ง/โซนผิด
ความคืบหน้าสำคัญ (ทดลองคืนเย็นวันนี้): ส่ง init แยกงเขต 37 จุด (ZoneIndex=0 ทุกแยก) → client ปล่อยรถ ambient ออกมาเป็นครั้งแรก! แต่:
- รถเกิดซ้อนทับกันที่จุดเดียว จนฟิสิกส์เด้งเละ (ไม่ใช่รถติดธรรมดา)
- ไม่มี AI ขับ เลย (รถตั้งอยู่กับที่/เด้ง)
สมมติฐานเรียงตามความน่าจะเป็น:
ZoneIndex=0ที่เราส่งทุกแยกผิด — client ใช้ InstanceId+ZoneIndex ไป resolve แยกกับ zone graph ของตัวเอง; ผิด → จุด spawn เด不堪ใช้ → รถซ้อนกันที่เดียว ต้องหา ZoneIndex จริงของแต่ละแยก (จากตำแหน่งไฟใน TrafficSignals + Bounds ของ ZoneData ใน zonegraph JSON หรือจาก ZoneInfos blob)- Client ต้องการตาราง period ของตัวเอง — เราส่งแค่ index 0/1 ที่สมมติขึ้น; ตารางจริงน่าจะอยู่ใน
RPCSerializeAuto.lua(ยังไม่ได้เปิด!) หรือ client คำนวณเองจาก zebraConnector - 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 หลังแก้ทุกครั้ง):
- เข้าเกมปกติ → มีรถไหม (batch + id base ใหม่ + wire ใหม่ = ควรเห็น)
- ไม่เห็น → curl timestampMode 3 → 2 → 1 (ดู devlog หัว Timestamp สำหรับเหตุผล pending-buffer)
- ยังไม่เห็น → สลับ
ambientIntersections:trueเทียบว่า client ปลุกระบบเองหรือไม่ - จับ hex แพ็กเก็ต AddDatas ใหม่จาก
logs/packets-latest.logเทียบWriteClientVehicleInitDataทีละ field อีกรอบ (list ใหม่มี marker/count ต่างจากเดิม — จุดเสี่ยงใหม่ที่ควรตรวจ) - ถ้าทุกอย่างถูกแต่ยังไม่ 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):
- 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 — ลองเรียงใหม่ตามหลักฐาน:
- ใช้ batch
SyncAetherAIVehicleAddDatas(68571034, List<init>) แทน single — สายสตรีมมิ่งของเซิร์ฟเวอร์จริงน่าจะใช้ตัวรวม (single อาจมีไว้สำหรับเคสพิเศษ) — งานแรกของเซสชันหน้า - NPC คนขับต้องตามก่อน render (เราส่ง NpcAdd ตามหลัง AddData แล้ว — ลองส่งก่อน AddData หรือพร้อมกันใน notify เดียว)
AetherVehicleManagerจริงมีm_pendingVehicles+ArrivalServerTime+MAX_PENDING_SYNC_DATA=30— เปิด dump UX.Game (ux_game_all.dump.csบรรทัด ~150532 คือ impl ตัวจริงแต่ body โดน strip) ศึกษา field รอบ ๆ หาเงื่อนไข promote เป็น render- ถ้ายังไม่รอด → แวะไปทาง ambient (client เป็นคน spawn) ที่เคยเห็นรถแล้ว: แก้
ZoneIndexต่อแยกด้วยตำแหน่งไฟจากvehicle-lanes.csv(มี ZoneIndex ทุกเลน!) แทนการใช้ 0 - สลับเป็น batch notify
SyncAetherAIVehicleAddDatas = 68571034(list) — single AddData อาจไม่เข้า MassTraffic; + เปลี่ยน id base เป็น 750000100000 ตามที่เห็นใน HUD เซิร์ฟเวอร์ที่ใช้ได้ - Hexdump ของเราเทียบ Lua เอง — ถอดแพ็กเก็ต AddData จาก
logs/packets-latest.log(hex เปิดอยู่) แล้ว decode มือทีละ field ตามWriteClientVehicleInitDataเพื่อจับความต่างระดับ serializer (จุดเสี่ยง: Complex marker 0xFF,Int7= value+1 bias, WriteList7Bit มี marker ก่อน count) - เปิด
ux_rpc/payload_types.dump.csค้นAetherAIVehicleAddData→ framing จริงฝั่งอ่าน (Struct vs Complex ที่ระดับ method payload ไม่ใช่ type) - ถ้าครบทุกขั้นยังไม่เห็น → อ่าน
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 ได้ = ได้มาซึ่ง:
RPCSerializeAuto.luaตัวจริง → wire format ที่แน่นอนของทุก Aether packet (แก้ AddData มองไม่เห็น)- Logic ไฟแดง/MassTraffic จริง (period tables, ZoneIndex mapping) → แก้รถ ambient ซ้อนทับ/ไม่มี AI
- Lua ของ DevPanel จีน → ทำ UI translator ให้ตรงจุดโดยไม่ต้องเดา
เส้นทางเซสชันหน้า (แนะนำให้เริ่มที่นี่ก่อนอื่น):
- คอมไพล์/รัน decoder จาก
code/(L50Index.cs + L50Payload.cs + Lz4Block) กับไฟล์เกมในเครื่องเรา (โฟลเดอร์เกม Ananta.exe — หาตำแหน่งก่อน) - ระบุไฟล์ Lua ใน index (หาชื่อ/path ที่ขึ้นต้น lua / ui / rpc) → ถอดออกมา
- อ่าน
RPCSerializeAuto.lua→ เทียบ DTO ของเราทุกตัว (ClientVehicleInitData, LaneDatas, InitDatas, IntersectionUpdate) - หา logic ไฟแดง (MassTraffic/IntersectionPeriod) → แก้ init intersections ให้ตรง
- ต่อ 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 keysui.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 ทุกครั้ง)
- เข้าเกมปกติ (
ServerSpawnedVehicles=truedefault) → รถควรโผล่+วิ่งแล้ว (single 68916691 + wire 89b + IDM LaneDatas 10Hz, client render/interpolate ตาม zone graph ตัวเอง) - รถโผล่แต่ติดตรง →
curl -X POST http://127.0.0.1:5809/api/traffic/config -d "{\"pushIntersectionUpdates\":true}"ดูไฟ; ยังติด → hex แพ็กเก็ต LaneDatas ใหม่จากlogs/packets-latest.logเทียบWriteClientVehicleLaneData - ทดสอบ ambient แท้ (AI รถจาก client ล้วน):
{"ambientIntersections": true}→ re-enter → ถ้ารถ ambient วิ่งได้ครบ = ปิดจ็อบ; ถ้ายังซ้อน = blob ฝั่ง client ไม่ครอบ zone ไหนไปดู packet แรกที่ entity ไม่เกิด - 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):
GadgetGridAOIIncrease: PlayerStandardIndex(GridIndex X/Z) → addInfos(List = ส่งว่าง) → indexList(List = cell ที่สั่งโหลด) → addUniqueIds/removeIds/activeIds/inactiveIds(List ว่าง) → reason(byte: Init=0, Loaded=1, AOIMove=2, ...)GridAOIDecrease(add): SectorIdList(List = [0]) → BuildingId(i32=0) → StandardIndexList(cells เดียวกัน) → ExceptIds(ว่าง)GridAOIDecrease(remove): ว่างทั้งหมด (ไม่ remove ตามบทเรียน poisoned)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 ต้องมี returnSceneDeviceCheckIndexCallback{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, 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) → echoSyncInteractCmd/SyncInteractCmds(68260752/68976896) กลับ; รับ AskSceneDeviceChangeState/RecordValue → returnSceneDeviceCheckIndexCallback{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 ใช้ 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 = ว่าง) → indexList(List) → addUniqueIds/removeIds(List) → 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.csAskGadgetDoorTransferมี handler accept อยู่แล้วใน Endpoints.cs (ไม่แตะ)
4. ยืมรถ traffic ขับได้ (borrow) — ปิด roadmap เก่า "hijack not supported"
flow จาก dump: กด F บนรถ traffic → client ยิง AskAetherAIBorrowVehicle(vehicleInstanceId) (67122370, Invoke) → server:
- ack +
CityTrafficEngine.DetachForBorrow(entityId)(ถอดออกจากซิม + คืน id NPC) - ลบคนขับ/ผู้โดยสารด้วย
SyncAetherAINpcRemove - push
SyncAetherAIBorrowVehicle(68187552) +SyncAetherAIChangeVehicleControl(68321386, ControlType=2 RaidVehicle) → รถกลายเป็น raid vehicle ขับได้ - ลงจากรถ:
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 + pushSyncPlayerSingleTask(State=Accepted, CounterValues=0 ตามจำนวน counter)AskSubmitTask→ push Submited(3) + auto-accept NextTasks (เดินเควสได้ต่อเนื่องทั้งสาย)AskAcceptAndSetCurrentTask→ accept +SyncCurrentTaskAskFinishTaskCounter(taskId, counterIndex)→ +1 counter แล้ว push (sandbox shortcut ที่ client มีอยู่แล้ว)
- per-session state ใน
- 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 พร้อม contextextract <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
เป้าหมายถัดไปเมื่อเจอ
- StaticIntersectionData.json → วางใน ClientData → ambient traffic init ใช้ ZoneIndex+Period จริง 100% (แทน mapping จาก zonegraph)
- IntersectionPeriodConfig.asset → ตารางไฟแดงจริง
- แหล่ง 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/... modelsCityTrafficEngine: โหลดไฟล์นี้เป็นแหล่งหลักของ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
AgentPropertyMethods.cs(DTO ใหม่): AgentPropertyDataBool, SyncAgentPropertyBoolChange, InviteRideNpcInteractItem, AgentPropertyDataInviteRide, SyncAgentPropertyInviteRideChange, SyncInviteRideNpcInfo, GmStartInviteRideNpc, AskInteractNpcWithGift — ทั้งหมดตามลำดับ Lua- Hook ตอน spawn NPC (
GameRouter.Npc.cs): หลัง SyncManagedLogicAgent ส่งEnableInteract=trueให้ทุก static/crowd NPC ที่เสก → ปุ่ม F/เมนู interact โผล่บน NPC ทุกตัว - 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 ตาราง, ชื่อที่ไม่มีในตารางคืนค่าเดิมDebugApiServerNPC catalog endpoint: ชื่อ agent + POI ผ่าน translator- ช่องค้นหายังค้นชื่อจีนเดิมได้:
NpcCatalog4229938.Searchmatch ทั้งชื่อ 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.7zAnangay 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/SessionUnlockedVehicleConfigsSpawnDirectAsync+ พารามิเตอร์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 ใช้เมื่อจำเป็น
ทดสอบ
- เข้าร้านซ่อมรถ (west: 1439,1.5,2379 / seaside: 2791,1.4,1286) → เมนูปรับแต่งต้องมีสินค้า (log
[SHOP] npc shop=... pushed N) - ซื้อชิ้นส่วน/สี/ชุดแต่ง → รถเปลี่ยนหน้าตา + กด F เข้ารถขับออกได้
- 4S dealer (94100019) ซื้อรถ → รถ spawn ที่จุดส่ง + snap ตำแหน่ง + ขึ้น garage list
- ซื้อแล้วรีสตาร์ทเซิร์ฟเวอร์ → ของยังอยู่ (
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:
GameSceneToClientImpl.SyncAetherAIVehicleLaneDatasรับ LaneDatas ของเรา ✓AetherVehicleManager.OnSyncAetherVehicleLaneDataเข้า pending ✓City.UpdateStep1 → FlushPendingAetherVehicles → FlushPendingVehicleImplรันทุกเฟรม ✓ (timestamp promote โอเค)- แต่ 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}
สมมติฐานเหลืออยู่ (เรียงตามน่าจะเป็น)
- GO Decision ไม่ instantiate — AetherVehicleGODecision ตัดสินจากระยะ/visibility/อะไรบางอย่างที่เรายังไม่ส่ง (ตรวจ: log ต้องมี ShouldNotUseGo ปนอยู่ไหม)
- prefab/dependency ของรถ aether หายใน VFS ของ CBT3 — ตรงกับงาน CBT2→CBT3 ของ polareve (โมเดล legacy spawn เห็นได้ = โมเดลหลักมี แต่ GO variant ของ aether อาจอ้าง dependency อื่น)
- NpcAdd ต้องมาก่อน/พร้อม AddData
การทดลองถัดไป (ต้องใช้เกมจริง)
- ทดสอบรอบใหม่ด้วย build ปัจจุบัน → ส่ง
log/Player.logกลับมา → ดูว่า drop เฉพาะรถไกลหรือ drop หมด - grep log หา
ShouldNotUseGo/GOLoaded/EnsureUnit/refresh waiting— แยกว่า GO decision บล็อก หรือ prefab load พัง - ถ้า prefab พัง: ใช้ VaultTool หา path โมเดล (
Assets/Res/Prefab/Drive/Vehicles/*.prefab) ใน VFS CBT3 เทียบ CBT2 → เจอตัวขาด = ยืนยันสาเหตุ → inject ตามแนว polareve - ทดสอบสลับลำดับ 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):
SyncLogicVehicleEnter— spawn legacy scene entity = ตัวสร้าง BaseVehicle (ของที่ flush หา!)SyncSpawnVehicle(VehicleClientInfo, Interactable=false, Velocity=speed)SyncAetherAIVehicleAddData(68916691 — ของเรามีอยู่แล้ว wire ตรงกัน)SyncAetherAIChangeVehicleControl(ControlType=AetherVehicle)SyncAetherAIVehicleForceGo(forceGo=true — สั่งรถเริ่มวิ่ง)- 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)")SyncAetherAIVehicleForceGo4229938DTO ใหม่
รอทดสอบ
เข้าเกม → รถ 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) เผยอะไร
unimplemented notify AskInteractCmd → accepted— ระบบ interact/เดต/dialog ของเราไม่เคยทำงานเลย ตั้งแต่แรก! สาเหตุ:GameRouter.Build()ใช้ whitelistEnabled4229938MethodIds— handler ใหม่ทุกตัวที่เพิ่งเขียนไม่ถูกลงทะเบียน ไม่ว่าจะมี [Handler] attribute แล้วก็ตาม[AETHER] init sent×4 ใน 9 วินาที — SendAetherInitForNpcAsync ไม่มี dedup (ยิงซ้ำทุกครั้งที่ spawn NPC batch)[CROWD-MOTION-ERROR] Cannot access a disposed object: SemaphoreSlimสแปมหลัง client หลุด — background loop ยัง probe session ที่ dispose แล้ว- client ปิด connection เอง ("forcibly closed by the remote host") หลัง world-ready ~50 วิ — สาเหตุฝั่ง client ยังไม่ชัด (ต้องดู Player.log ฝั่ง client ของรอบนั้น)
สิ่งที่แก้ (Build 0/0)
GameRouter.cswhitelist +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 + ลำดับ)
Drmk.Core/ClientData/Client4229938/TaskCatalogRepository.cs(266 ln) — ChainFrom(root)/Counters(id)/Name(id) — เสริมเข้า TaskCatalog4229938 ที่เรามีอยู่แล้ว (TaskConfig ชุดเดียวกัน)WorldEntryState+3 fields: QuestStates(dict u32→byte), QuestCounterValues(dict u32→int[]), CurrentQuestTaskId(u32) — Drmk มีอยู่ line 218-225Handlers/Game/GameRouter.StoryFlow.cs(277 ln) — AcceptStoryTaskAsync / ReportCounterAsync / CompleteAndAdvanceAsync / StartStoryChainAsync / PushTaskContainerAsync / PushCurrentTaskAsync — แทนที่ handler AskAcceptTask/AskSubmitTask/AskFinishTaskCounter แบบง่ายที่เราเพิ่งทำ (ของเขา container-based ครบกว่า)Handlers/Game/GameRouter.SwitchTimeline.cs(1,161 ln) — ตัวขับ transition คัทซีน↔โลก (ReportPreSwitchSpiritFinish / AskSwitchSpiritComplete / SwitchTeleportTiming.Immediate|Apex) — เทียบกับ Switching.cs ของเราก่อนรวม (อาจทับกัน)- DTO ที่มาพร้อม (หาใน Drmk RpcTypes): SyncPlayerTaskContainer + task container structures
- whitelist: AskChangeTaskCounterValue, AskDoGuide, AskFinishGuide (+ตัวที่ไฟล์ใหม่ส่ง)
- World entry ของเรา (WorldRouter) ต้องเปลี่ยน opening=false → accept story root (StartStoryChainAsync) หลัง ready — แล้ว client เล่น intro เอง จบแล้วถึงเข้า WORLD-MIN แบบเดิม
ลำดับทำ (รอบหน้า — งานสด ๆ ประมาณ 1 session)
- พอร์ต TaskCatalogRepository (ChainFrom/Counters/Name) + เติม 3 fields ใน WorldEntryState
- พอร์ต StoryFlow.cs ทั้งไฟล์ (แก้ namespace + GetStateIfExists) + ลบ handler เควสเก่าที่ทับ + whitelist เพิ่ม
- Build + ทดสอบ: เข้าเกม → intro รถไฟต้องเล่นถึงหน้ากรอกชื่อ/เลือกเพศ → confirm → คัทซีนรถ → โลก
- รอบหลัง: 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 ใหม่เท่านั้น
สิ่งที่ต้องทำต่อ (เรียงตามความคุ้ม)
- ผู้เล่นทดสอบด้วย build ใหม่ (whitelist ครบ + AI-task traffic) → รายงาน: รถวิ่งไหม / intro ติดตรงไหน / อาการเป๊ะ
- ถ้า intro ค้างหลังเลือกเพศ: พอร์ต
GameRouter.Quests.cs(container push เต็ม) + StoryFlow.cs ตามแผน - สกิลตัวละคร: เทียบ perSpiritViews กับ FightSkillConfig ราย spirit หาตัวขาด
- 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 หน้า)
- 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 - Diff → CBT2-only entries = content ที่ CBT8 ตัดออก (intro คัทซีน/รถไฟ อยู่ในนี้)
- Decode ตัวอย่าง (L50Payload) ตรวจว่าใช่ intro จริง (ค้นคำว่า Timeline/Train/S011/Scene ใน payload)
- Inject 2 ทาง:
a. ง่ายสุด: คัดลอกไฟล์ payload ของ CBT2 + เขียน
NoABFull.manifestใหม่ (ต่อท้าย entry array — รูปแบบ manifest ตามcode/L50Index.cs) b. ทางการ: CoreLibVFSBuilderBuildGroupสร้าง vfc ใหม่ (vault พิสูจน์แล้ว) - ทดสอบ: เปิดเกม (ไม่ต้องรอ 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 (สถานะปัจจุบัน)
- server push story root 60004938 (Accepted) + SyncCurrentTask แล้ว ✓
- client รับ S011 story node แล้ว ✓ (log: CreateClientNode rebuild)
- คัทซีนยังไม่เล่น → ขาด 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) + configGameplay.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 หน้า — ตามลำดับ)
- อ่าน reference:
Ananta.Proxy/proxy/tools/generate_client_config_patch.DRMK.reference.js(hook: env DRMK_DEV_MENU_DICT + โหลด dictionary + HookTable ~500 บรรทัด) - พอร์ต hook เข้า
generate_client_config_patch.jsของเรา (74KB — ห้ามทับ แก้จุดเดียว: สร้าง Lua hook ไฟล์ใหม่จาก dictionary + patch เข้า fastpatch) - แหล่ง dictionary:
config/localization/translation.en.txt(มีแล้ว) → generator อ่านแล้วฝัง - fastpatch.zip ที่เสิร์ฟ =
fastpatch_4229938.zipข้าง server.js (สร้างจาก generator) — regenerate แล้ว client ต้องอ่าน Lua ใหม่ (log: "read lua bytes from fast patch" ต้องมีไฟล์ hook) - ทดสอบ: 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)
คิวงานถัดไป (เรียงแล้ว)
- ทดสอบ build ปัจจุบัน (traffic AI-task + quest container + whitelist + fastpatch แปลภาษา)
- ถ้า intro ยังไม่เล่น: เทียบ StoryClientCommand stream จาก log (ReceiveCommands) กับที่ server ส่ง
- สกิลครบทุกตัว (COMBAT-CATALOG vs FightSkillConfig)
- Contacts app (NETWORK OF CONTACTS) — ผูก NPC จริงเข้า contact data
- CBT2 camera cutscene — ตามแผน NoAB injection
✅ 27 ก.ย. (ต่อ 12): audit "no dummy" รอบ 1 — Build 0/0
AskSceneDeviceRecordValue→ เพิ่ม mirrorSyncSceneDeviceValueChange(client apply ค่าจริงจาก Sync ไม่ใช่แค่ ack)AskReadCommodities→ handler จริง: push inventory ของร้านจาก ShopCommodityCache (เดิม fallback)AskBuyCommodityToBag/AskBuyCommoditiesToBag→ ack แบบ sandbox (เศรษฐกิจไม่จำกัด ตาม design)- placeholder ที่เหลือ (ทำไม่ได้จริงโดยไม่มี backend/document):
GadgetEntityInfo/DestructibleInfo/TaskEventInfoDTO 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 — คิวหลัง
ลำดับแก้ที่คิดว่าคุ้มสุด (รอบหน้า)
- กัน session ตายจาก handler exception — ครอบ try/catch ทุก handler ใหม่ + ตรวจ server log ตอน Reconnecting ว่า exception ตัวไหนยิงซ้ำ
- รถ: สลับทั้งระบบเป็น AI-task ล้วน — spawn เฉพาะ lane มี chain + min-gap ระหว่างรถ (แบบ Drmk line 814) + ลด churn
- คนขับ — ศึกษา NpcAdd ของ Drmk ว่าต้องมี AgentSyncClientInfo ประกอบไหม (แบบ static NPC ของเรา)
- 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 ทางต่อสั้น ๆ รถก็นั่งแห้ง) +SpawnMinLaneLength15→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 คุยได้) และ pushSyncTrackGPS(63861463) = หมุดแผนที่ (writer ตรงจาก RPCSerializeAuto.lua:TeamTrackGPS{TrackPosition, TrackGPSId, TrackGPSType}— ตัวนี้ยังไม่เคยมีใครส่ง ทั้งเราและ Drmk) — NPC ตัวเดียวกัน "เดิน" ไปจุดใหม่เมื่อเควสเปลี่ยน (ไม่รกแมพ)- DTO ใหม่:
TeamTrackGPS4229938ใน TaskAndDialogMethods.cs - ถ้า accept ตอน world-entry (transform ยังไม่พร้อม) จะข้ามแล้วจับตอน accept ถัดไปในเกม
- DTO ใหม่:
- Contacts app (NETWORK OF CONTACTS): แอปนี้อ่านจาก push เท่านั้น (ไม่มี C2S fetch — ตรวจ Lua delegate ครบ) → พอร์ต
AddSpiritPhoneInfos(64199774) จาก Drmk (PushSpiritHandbookAsync) ยิงตอน LoginGame หลัง SyncPlayerInfo: 2 เบอร์ summon + 24 คอนแทกต์ชื่อ NPC จริง (AgentConfig → NameTranslator EN) ต่อทุกตัวละคร + implement C2SAskPhoneAddContact/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เป็น nativeResourcePackageformat (ไม่มี ASCII เลย — records [u32 hash][u32 off][u32 len] ต้อง RE จาก UnityPlayerResourcePackage_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 รอบเดียวเช็คได้หมด)
- เสถียรภาพ: เล่น 10 นาที ดูว่า Reconnecting หาย (ถ้ายังมี — ส่ง server log มาดูบรรทัด
session kept/exception) - รถ: รถวิ่งต่อเนื่องกว่าเดิม ไม่หายตรงหน้า (lifetime 15 นาที) ไม่เกิดทับท้ายรถที่แยก คนขับ+ผู้โดยสารต้องเห็นในรถทุกคัน
- คนเดิน: อนิเมชั่นต้องเป็นท่ายืนธรรมชาติ (โทรศัพท์/มองรอบ/พิงผนัง) — ไม่มีวิ่ง原地/ตกใจอีก
- เควส: รับเควส → NPC quest giver โผล่ข้างหน้า + หมุดบนแมพ กด F คุยได้; ส่งเควสแล้ว NPC เดินไปจุดใหม่ของเควสถัดไป
- โทรศัพท์: แอป NETWORK OF CONTACTS ต้องมีรายชื่อ (142857/206337 + ชื่อ NPC ภาษาอังกฤษ ~24 ราย) และเพิ่ม/ลบคอนแทกต์ได้