1Giới thiệu & bản đồ tài liệu
Mục đích: kiến trúc Unity 6 để đội + agent AI dựng TOÀN BỘ game từ GDD này: bản đồ module, đường dữ liệu GDD JSON → Unity, lưu/di trú, hiệu năng 250 quái, tất định seed, runtime Tro Nhớ, Steamworks, input, loc, build/nhánh, công cụ debug, test/Autopilot, ngân sách theo máy, rủi ro. Phụ thuộc: 00-Design-Bible.md (INV-01..18, §4.1 mode, §10.4 hiệu năng/camera, §11 ID) + data/_bible.json; 01-Roadmap.md §5 (PlatformTuning, input, ngân sách máy yếu); 10-Combat-Core.md §1 (tick, toạ độ, hitbox), §7.4, §10 (trần); 14-Adaptive-AI.md §1, §7, §9–§10, §16; 15-Horde-Director-Encounters.md §1, §5; 16-Run-Structure-Map.md §10–§11; 31-VFX-Bible.md §2.4, §6, §8; 32-Audio-Bible.md §5, §11–§12; 51-Data-Schema.md §0–§3, §5, §8; digest code demo (Assets/_Project/Demo); rule code của dự án (code-style, core-system, data-persistence, third-party, compile-validation). Được dùng bởi: mọi lập trình viên + agent code (/run-backlog, /planning-system), QA (test, Autopilot, CI hiệu năng), build/release, agent dữ liệu (đường render §8), doc UI/HUD, doc Steam/marketing (thành tựu, leaderboard).
Mục lục chi tiết (## và ###)
- 0. Phạm vi & thứ bậc
- 1. Hiện trạng demo → đích 1.0
- 2. Bản đồ module
- 2.1 Assembly (asmdef) & namespace
- 2.2 Luật phụ thuộc (CI kiểm bằng script đọc .asmdef)
- 2.3 Vòng frame & thứ tự trong tick (khớp doc 10 §1.1 + doc 14 §1.1 + doc 15 §1)
- 3. Lõi mô phỏng (gameplay sim)
- 3.1 Đồng hồ & bộ đếm tick (fixed-point)
- 3.2 Bố cục thực thể
- 3.3 Phân giải trúng (Combat)
- 3.4 Trạng thái STS_ (12, statuses.json)
- 3.5 Hero runtime
- 3.6 Hệ hiệu ứng (Edge, Cộng Hưởng, Tiến Hoá, Relic, affix, kỹ năng)
- 3.7 Kẻ địch & AI
- 3.8 Director
- 4. Kiến trúc hiệu năng cho 250 quái
- 4.1 Quyết định ECS
- 4.2 Pool & 0 cấp phát
- 4.3 Vẽ bầy (sprite batching)
- 4.4 Texture & bộ nhớ
- 4.5 Hạ cấp dưới tải (1 đồng hồ đo, 3 thang)
- 5. Ngân sách hiệu năng theo máy
- 5.1 Máy đích
- 5.2 CPU main thread (Deck, 250 quái + Ult + 1 Tinh Anh + MESO = kịch bản MS01b)
- 5.3 Bộ nhớ & đĩa
- 6. Tất định seed (DAILY, Truy Tuần, CUSTOM)
- 6.1 Phạm vi tất định (Bible §4.1, doc 16 §10)
- 6.2 Luồng RNG (Bladewake.Core.RngStreams)
- 6.3 Nộp điểm (mọi bảng; nguồn = doc 43 §6.2–§6.3, doc này chỉ chép giá trị để code)
- 7. Runtime Tro Nhớ (Bladewake.Ash)
- 7.1 Lấy mẫu PSP (A01–A12, doc 14 §7)
- 7.2 Lưu xuyên run
- 8. Đường dữ liệu (GDD JSON → Unity runtime)
- 8.1 Chuỗi bước
- 8.2 Hot reload (chỉ Editor + build BW_DEV)
- 8.3 Luật nạp
- 9. Hệ lưu
- 9.1 File & thư mục
- 9.2 Khối đầu, ghi nguyên tử & chịu lỗi (doc 43 §7.2)
- 9.3 Phiên bản, di trú & xung đột
- 9.4 Tạm dừng & thoát giữa run
- 10. Tích hợp Steamworks (Bladewake.Platform)
- 11. Lớp input (Bladewake.Input)
- 12. Đường bản địa hoá (Bladewake.Loc)
- 13. Runtime VFX & âm thanh
- 14. UI (Bladewake.UI)
- 15. Telemetry & báo crash (Bladewake.Telemetry)
- 16. Build pipeline & nhánh
- 16.1 Nhánh git (remote origin, nhánh gốc main)
- 16.2 Cấu hình build (Build Profiles Unity 6)
- 16.3 Chuỗi CI (máy build Windows tự host + 1 Steam Deck nối mạng)
- 17. Công cụ debug & cheat (chỉ BW_DEV)
- 18. Test tự động & Autopilot
- 18.1 Test (Unity Test Framework)
- 18.2 Autopilot (Bladewake.Dev.Autopilot, mở rộng bot demo)
- 19. Rủi ro kỹ thuật
- Quy tắc cho dev/AI
- Quyết định đã chốt
20. Phạm vi & thứ bậc
- Doc này KHÔNG đặt số gameplay. Số gameplay ở
GDD/Full/data/*.json; trần hiệu năng copy từtuning.jsoncaps+platform(nguồn: Bible §10.4, doc 10 §10, doc 15 §5, doc 31 §6, doc 32 §5.3). Lệch → doc luật thắng; sửa doc này. - Thứ bậc: Bible +
_bible.json> doc 10–17, 20, 30–32, 51 > doc này > code. Code KHÔNG được là nguồn sự thật cho số/ID. - Engine khoá: Unity 6000.3.16f1 (dòng 6000.3 LTS tới 1.0; nâng dòng = mục
DEC-+ 1 sprint kiểm hiệu năng Deck). C# 9, .NET Standard 2.1, IL2CPP x64. - Nền tảng 1.0: Windows x64 (Steam), Steam Deck qua lớp tương thích của Steam (cùng build Windows). Console M36+, mobile cuối (doc 01 §3.11–3.12): kiến trúc chừa lớp
IPlatformService,PlatformTuning,InputFrame, không code nhánh riêng trước cổng port. - Nhãn
[ASSUMED]= số kỹ thuật lượt đầu, chốt ở MS01b (M3) bằng đo Deck.
31. Hiện trạng demo → đích 1.0
| Mảng | Demo (Bladewake.Demo, 28 file ~405 KB) | Đích 1.0 | Mốc |
|---|---|---|---|
| Khung | 1 Game MonoBehaviour + locator tĩnh G | RunContext (thể hiện, không tĩnh) + SimWorld thuần C# + Presentation tách | P0 |
| Hero | Hero.cs 58 KB, cứng varek | HeroBase + HeroKit dữ liệu + 12 IHeroVerbs mỏng | P0 → MS02 (3 hero) |
| Nâng cấp | switch(effect) + 32 chỗ Prog.Has | EffectRuntime trigger × primitive từ mechanics.json (46 × 186) | P0 |
| Quái | GameObject + SpriteRenderer mỗi con | SoA NativeArray + Burst + vẽ instanced; M/L/XL GameObject | MS01b |
| AI | chuỗi ai cứng, không thích nghi | FSM archetype (S) + utility/đồ thị đòn (M/L/XL) + AshMemory | MS01b → MS03 |
| Director | bảng sóng theo giờ | ngân sách TP + EncounterRunner (wave_scripts.csv) | MS02 |
| Dữ liệu | CSV tay GDD/Data → Resources/BW/Data, F5 | JSON → build_gdd.py → _unity/ → Bake SO + blob, hot reload dev | P0 |
| Lưu | không (PlayerPrefs + CSV run) | <steamId64>/save/ theo doc 43 §7.1: meta_<steamId>.json + run_active.json + ash_profile_<steamId>.json + settings_shared.json (Cloud) · local/settings_device.json (không Cloud), khối đầu, nguyên tử, di trú | MS02 |
| Âm | 24 nguồn round-robin, không mixer | BW_Main.mixer + SfxPlayer 48 + MusicDirector 12 + HordeAudio (doc 32 §12) | MS02 |
| Asset | Resources, RGBA32, Varek ~120 MB | Addressables theo hero/biome, trang chỉ mục RG8 + LUT | MS01b |
| Input | đọc thiết bị trực tiếp | BW_Input.inputactions → InputFrame, rebind, 5 bộ glyph | MS02 |
| Steam | không | SteamPlatformService (thành tựu, stat, leaderboard, Cloud, Rich Presence, Deck) | MS01 (trang) / MS02 (demo app) |
| UI | uGUI dựng bằng code, Text legacy, chữ cứng | uGUI + TMP + UIManager/FeatureBaseController, LocService | MS02 |
| RNG | UnityEngine.Random không seed | PCG32 10 luồng (doc 16 §10.1) | P0 |
Template mobile trong Assets/_Project (quảng cáo, IAP, mediation quảng cáo, SDK analytics/push di động, LiveOps): gỡ khỏi build PC (asmdef defineConstraints + gỡ package khỏi manifest bản PC; CI kiểm build report 0 assembly quảng cáo/IAP/analytics di động — danh sách tên assembly cấm ở script CI). Giữ: UIManager, DataPlayer/PlayerDataManager, DataManager, TimeManager, GameSystems, EasyEventManager, UniTask, DOTween, bộ thuộc tính Inspector của template (chỉ Editor).
42. Bản đồ module
2.1 Assembly (asmdef) & namespace
Gốc mã: Assets/_Project/Bladewake/<Module>/; namespace Bladewake.<Module>; Editor ở <Module>/Editor/ (asmdef .Editor).
| asmdef | Việc | Phụ thuộc được phép | Lớp chính |
|---|---|---|---|
Bladewake.Core | đồng hồ, tick, RNG, ID handle, toán sàn 3/4, pool, ring buffer, SimEvent | Unity.Mathematics, Collections, Burst | SimClock, TickMath, Pcg32, RngStreams, IdRegistry, FloorMath, Pool<T>, SimEventBus |
Bladewake.Data | nạp _unity/*.csv + *.json, SO, blob runtime, tuning, hot reload | Core | Db, DataBaker, TuningTable, HeroKitSO, EnemyTableSO, ContentManifest |
Bladewake.Sim | vòng tick, SimWorld, thứ tự hệ (§2.3) | Core, Data, các module sim dưới | SimWorld, SimLoop, RunContext |
Bladewake.Combat | hitbox/hurtbox, spatial hash, phân giải trúng, sát thương, Thế, đẩy, trạng thái STS_, log sát thương 30 s | Core, Data | SpatialHash, HitResolver, DamagePipeline, StatusSystem, DamageLog |
Bladewake.Heroes | HeroBase, FSM, buffer, ĐÁP, verb giữ, Lướt, tài nguyên, Dư Âm (ULT_GAUGE), kỹ năng, ult, Dạng Rèn | Core, Data, Combat, Upgrades | HeroBase, HeroKit, MoveRunner, IHeroVerbs (H01…H12) |
Bladewake.Upgrades | Edge/RS/EVO/Relic/affix/Then Chốt, draft, mẫu câu thẻ | Core, Data, Combat | EffectRuntime, TriggerBus, PrimitiveRegistry, DraftService, CardTextFormatter |
Bladewake.Enemies | EnemyWorld SoA, archetype S, brain M/L/XL, token, vòng vây, flow field, đạn/vùng địch, boss pha | Core, Data, Combat | EnemyWorld, SmallBrainJob, HeavyBrain, AttackTokens, FlowField, BossScript |
Bladewake.Ash | PSP A01–A12, W10/W20/ROOM/MESO/FIGHT, CV, Sổ Thù, Sẹo, ash_level | Core, Data | AshMemory, PspSampler, CounterSelector, AshLedger |
Bladewake.Director | ngân sách TP, SpawnPlanner, EncounterRunner, mục tiêu trận, PerfGovernor | Core, Data, Enemies, Ash | Director, SpawnPlanner, EncounterRunner, ObjectiveSystem, PerfGovernor |
Bladewake.Run | run state, bản đồ Hồi (MapGen), nút, thưởng, sạp, sự kiện, mode, Khăn Tang/mutator, tổng kết | Core, Data, Upgrades, Director | RunState, MapGen, NodeRunner, ModeRules, MourningRules, RunSummary |
Bladewake.Meta | hub, 8 trạm ST_*, Phục Dựng GD1–5, 4 tiền tệ, mở khoá, Thông Thạo MR01–20, codex, thoại NPC | Core, Data, Save | MetaService, StationService, MasteryService, CodexService, DialogueRunner |
Bladewake.Save | hồ sơ, run dở, hồ sơ Tro Nhớ, ghi nguyên tử, di trú | Core | SaveService, AtomicFile, MigrationChain |
Bladewake.Presentation | camera pixel, vẽ bầy instanced, actor lớn, VFX runtime, số sát thương, rung, LUT palette | Core, Data, Sim (chỉ đọc) | PixelCamera, CrowdRenderer, ActorView, FxRuntime, DmgTextRenderer, PaletteLut |
Bladewake.Audio | AudioDb, SfxPlayer, HordeAudio, TellAudio, MusicDirector, AudioSettingsService (doc 32 §12.1) | Core, Data, Sim (chỉ đọc) | như doc 32 |
Bladewake.UI | HUD, màn hình, thẻ draft, bản đồ, codex, cài đặt, recap chết | Core, Data, Loc, Meta, Run (chỉ đọc + lệnh) | HudController, *Controller : FeatureBaseController |
Bladewake.Input | InputActions → InputFrame, rebind, glyph | Core, Platform | InputService, InputFrame, GlyphService |
Bladewake.Loc | bảng chuỗi, mẫu {param}, font theo ngôn ngữ | Core, Data | LocService, TemplateFormatter |
Bladewake.Platform | Steamworks, Deck, Cloud, thành tựu, leaderboard | Core | IPlatformService, SteamPlatformService, NullPlatformService |
Bladewake.Telemetry | sự kiện run/ash/perf, ghi NDJSON, gửi opt-in | Core | TelemetryService, PerfMeter |
Bladewake.Dev | overlay F3, console, phím F, cheat, Autopilot | mọi module | DevConsole, DebugOverlay, Autopilot, BotProfile |
Bladewake.Tests.EditMode / .PlayMode | test §18 | mọi module | — |
2.2 Luật phụ thuộc (CI kiểm bằng script đọc .asmdef)
- Sim không biết hình/âm/UI:
Core, Data, Combat, Heroes, Upgrades, Enemies, Ash, Director, Run, SimKHÔNG tham chiếuPresentation, Audio, UI, Platform, Telemetry, Dev. Sim chạy được-batchmode -nographics(Autopilot cân bằng §18). - Sim → hiển thị/âm/UI/telemetry chỉ qua
SimEventBus(ring buffer struct, 0 cấp phát) đọc cuối frame. Sự kiện tần suất thấp (xong phòng, lên cấp, mở draft, đổi pha boss, hết run) đẩy thêm quaEasyEventManagervới hằngEventName(rule third-party). - UI ra lệnh cho sim chỉ qua
ICommandxếp hàng (chọn thẻ, mua sạp, chọn nút) áp ở ranh giới tick. Bladewake.DevcódefineConstraints: ["BW_DEV"]→ không có trong build phát hành.- Cấm trong sim (CI grep + analyzer):
UnityEngine.Random,System.Random,DateTime.Now(dùngTimeManager),Time.deltaTime(dùngSimClock),Keyboard.current/Gamepad.current/Mouse.currentngoàiBladewake.Input,GameObject.Find,SetActivecho màn hình (dùngUIManager),async void, Coroutine (dùng UniTask).
2.3 Vòng frame & thứ tự trong tick (khớp doc 10 §1.1 + doc 14 §1.1 + doc 15 §1)
Update() mỗi frame hiển thị (Real dt):
InputService.Poll() → InputFrame (dấu tick)
acc += min(realDt, 4/60) ; n = 0
while acc ≥ 1/60 ∧ n < 4: SimWorld.Tick(); acc −= 1/60; n++ // > 4 tick → bỏ phần dư (doc 10 §1.1)
Presentation.Sync() → CrowdRenderer, ActorView, FxRuntime, DmgText, Camera (Real)
HordeAudio.Flush() → SfxPlayer ; TellAudio ; MusicDirector (0.25 s Hero clock)
UI.Refresh() (chỉ khi dirty) ; Telemetry.Flush() (cuối phòng) ; PerfMeter.Sample()
SimWorld.Tick() (1/60 s):
1 InputFrame → buffer 9 tick (INV-07) 7 đạn (hero 300 · địch 12)
2 Hero FSM (Hero clock) + MoveRunner 8 vùng/hazard (hero 24 · địch 16)
3 SpatialHash.Build (Burst) 9 StatusSystem.Tick (DoT 30 nhóm lệch pha)
4 HitResolver: hitbox hero → quái (§3.3) 10 chết/rơi đồ/XP · EffectRuntime on_kill
5 Perception.Snapshot · Director (mỗi 15 tick) · Tokens.BeginTick · FlowField (10 Hz) ·
SmallBrainJob (LOD) + HeavyBrain · Tokens.Resolve · di chuyển + giãn cách (Burst)
6 đòn quái chạm hero: Hero.Receive (doc 10 §7.4) 11 AshMemory.Record (+ Evaluate 4 Hz) · SimEventBus.Commit
- Ví dụ bù tick: frame 40 ms → acc 2.4 tick → chạy 2, mang 0.4; frame 120 ms → cần 7.2 → chạy 4, bỏ 3.2 (không tua nhanh).
- Hiển thị khoá 60 fps (
PlatformTuning.target_fps): màn 60/120/180/240 Hz →vSyncCount = refresh/60; tần số lẻ (75, 144, 165 Hz) →vSyncCount = 0+targetFrameRate = 60. Không nội suy hiển thị (1 tick = 1 frame).
53. Lõi mô phỏng (gameplay sim)
3.1 Đồng hồ & bộ đếm tick (fixed-point)
- 3 đồng hồ
World·Hero·Real(doc 10 §1.1). Mỗi tick sim:scale_q16[c](65536 = 1.0) = min các nguồn chậm đang chạy (doc 10 §6.3; ngân sách chậm toàn cục 0.60 s/2.0 s doSlowBudgetgiữ). - Bộ đếm khung (startup/active/recovery, windup, i-frame, hồi chiêu, trạng thái) lưu Q16 tick (
int): mỗi tickremain_q16 −= scale_q16[clock]; sự kiện bắn khi qua 0. Chuyển động:pos += vel × scale/60(mượt trong slow-mo, không giật bậc). - Ví dụ: Né Hoàn Hảo World ×0.25 trong 0.35 s (21 tick). Quái windup còn 27 tick = 1.769.472 q16; 21 tick × 16.384 = 344.064 → còn 1.425.408 = 21.75 tick → glint 2 (7 tick trước va chạm) vẫn đúng hình, chỉ chậm.
- Đổi giây → tick lúc NẠP dữ liệu (doc 10 §1.1): cửa sổ có lợi người chơi
ceil(t×60 − 0.001), còn lạiround(t×60);TickMath.FavorPlayer/Round; code không tự đổi lúc chạy. - Hitstop cục bộ (doc 10 §6.1):
hitstop_q16riêng từng actor (hero + mục tiêu); actor đang hitstop bỏ bước bộ đếm + chuyển động, đồng hồ vẫn chạy. Ngân sách hitstop hero 0.20 s/1.0 s ởHeroBase. - Draft: World ×0.15 + hero bất tử, tách ngân sách chậm; Director cũng chạy trên World nên chậm theo (doc 15 §1). Tạm dừng:
SimLoop.Paused= không gọiTick().
3.2 Bố cục thực thể
| Loại | Lưu trữ | Trần (tuning caps) | Ghi chú |
|---|---|---|---|
| Quái S | EnemyWorld SoA NativeArray, sức chứa 256, chỉ số slot ổn định, free-list | alive_max 250 (Deck preset 180) | pos, vel, radius, mass, hp, poise, state, archetype, attack timers, status mask, token, slot ring, anim frame, flags |
| Quái M/L/XL | HeavyActor (class) + GameObject ActorView, ≤ 8 cùng lúc | Tinh Anh 2 (HUNT/MRN14: 3), mini 1, boss 1 | dùng chung SpatialHash qua slot ảo 256–263 |
| Hero | HeroBase (class), 1 | — | hurtbox CIRCLE r7 + hộp màn 7×26 (doc 10 §1.4) |
| Đạn | ProjectileWorld SoA | hero 300 · địch 12 | quét swept LINE w = 2r khi đi > r/tick |
| Vùng/hazard | ZoneWorld SoA | hero 24 · địch 16 | shape doc 10 §1.4, rehit_ticks |
| Rơi đồ | PickupWorld SoA | shard XP 120, cầu máu 6 | gộp vào cái gần nhất khi đầy (doc 15 §5.1) |
| Xác anim | CorpseWorld SoA | 80 | thứ 81+ chỉ tan tro 0.2 s |
- ID chuỗi (Thây Tro EN_B1_01, A3 · COMBO_A H01_COMBO_A_3) → handle
ushortquaIdRegistrylúc nạp; sim chỉ dùng handle; lưu file/telemetry/UI dùng chuỗi. Handle không bao giờ ghi ra đĩa.
3.3 Phân giải trúng (Combat)
- SpatialHash ô 32 px (Bible §10.4), dựng lại mỗi tick bằng counting sort Burst:
cellCount[]→ prefix sum →cellItems[]. Phòng lớn nhấtRM_*960×1.080 px → 30×34 = 1.020 ô; bộ nhớ ~12 KB. - Hitbox hero (≤ 64/tick): AABB(shape) nở
r_t_max28 → duyệt ô → phép thử Minkowski đúng shape (CIRCLE, ARC, RECT, LINE, RING, ELLIPSE; doc 10 §1.4). Cặp thử ≤ 4.000/tick: chạm trần → hitbox còn lại xử lý tick sau (không bỏ). Ví dụ ARC r48: AABB 152×152 px → ≤ 6×6 = 36 ô; 250 quái dồn r100 → ~70 ứng viên/hitbox. - Khử trùng "1 hitbox trúng 1 mục tiêu 1 lần": bitset 264 bit (5 ×
ulong) theo instance hitbox, xoá khi hitbox hết;rehit_ticks→ bộ đếm theo slot. 0 cấp phát (thayHashSetdemo). max_targets(48, ult 128) chọn theo khoảng cách tăng dần tới gốc đòn (tất định: hoà → slot nhỏ hơn).- Pipeline sát thương = doc 10 §3 (thứ tự cộng/nhân §2.3, làm tròn);
DamagePipeline.Resolve(in HitDesc, ref Target)thuần, Burst-được cho DoT; mọi kết quả ghiDamageLog(ring 30 s, INV-16) +SimEventBus.Hit. - Đòn địch → hero:
Hero.Receive(Strike)thứ tự cố định doc 10 §7.4 (bất tử → i-frame Lướt/Né Hoàn Hảo → i-frame trúng → ĐÁP 360° → verb giữ → trúng). Đạn: hộp màn quét liên tục. - Cùng tick: ưu tiên INV-07 Lướt > ĐÁP > Ult > Kỹ năng > Đánh; đòn hero xử lý trước đòn quái (bước 4 trước 6) → giết cùng tick với windup chạm = quái chết, không đòn.
3.4 Trạng thái STS_ (12, statuses.json)
- SoA
StatusWorld: tối đa 1.500 instance; mỗi quái ≤ 6 BUILD + 3 CONTROL (doc 10 §5.4). DoT tick 30 tick, nhóm pha =instance_id mod 30→ mỗi tick xử lý ~1/30 instance. - Phản ứng cặp (Nứt Nhiệt, Lan Độc, Hành Hình) chỉ khi
EffectRuntimebáo sở hữu SRS cặp; kiểm ởStatusSystem.Apply. - CONTROL lên L/XL đổi thành Thế (doc 10 §5.3) tại
StatusSystem, không ở kỹ năng.
3.5 Hero runtime
HeroBase(chung 12 hero): di chuyển, hurtbox, buffer 9 tick, ĐÁP (cửa sổ gốc +answer_window_bonus_s+ Thương Xót + Edge, kẹp ≥ 9 tick), Lướt (INV-12: i-frame 12 tick, ≥ 2 lượt, nạp 0.70 s), Hurt/Vỡ Thế, tài nguyên (RESOURCEdoc 12 §2.2), Dư ÂmULT_GAUGE, ult (UL1/UL2 bất tử toàn clip, UL3 chỉ 0.5 s kích).MoveRunner: đọchero_moves_<Hxx>.csv(startup/active/recovery tick, shape, coef,hit_class,poise_dmg, flags,vfx_id,sfx_id) + ma trận huỷ doc 12 §3.2. Hướng hitbox khoá ở tick bắt đầu startup (cận chiến) / tick nhả (tầm xa) — doc 10 §1.6.IHeroVerbs(1 class/hero, ≤ 400 dòng): CHỈ verb giữ + hành vi tài nguyên riêng + hook chữ ký; mọi số quaHeroKit. Kỹ năng/ult/Dạng Rèn =mechanic_key→PrimitiveRegistry(§3.6), không code riêng theo id.- Palette hero: slot hue (Bible INV-01b) → LUT hàng hero; skin = LUT (§4.4).
3.6 Hệ hiệu ứng (Edge, Cộng Hưởng, Tiến Hoá, Relic, affix, kỹ năng)
- Từ vựng đóng
mechanics.json: 46 trigger × 186 primitive. Build sinhMechanicIds.g.cs(enumTriggerId,PrimitiveId); testAllPrimitivesHaveHandlerchặn merge khi primitive thiếu handler. - Hàng dữ liệu →
EffectInstance {trigger, primitive, owner, rank, params(float[], đã chọn theo hạng)};TriggerBus.Raise(TriggerId, in TriggerCtx)gọi handler theo thứ tựpriorityrồi id (tất định). - Trần trigger (tuning
caps):icd_on_hit0.1 s ·on_kill_per_s8 ·icd_on_status0.25 s ·interval_min0.5 s ·blasts_per_frame16 · đạn từ nâng cấp 120 · vùng 24 · triệu hồi 8. Chạm trần → bỏ kích, không xếp hàng. action = CUSTOM_HOOK→params.tpl(TPL_Hxx_*) → class gắn[Hook("H01.wide_arc")], quét bằng reflection LÚC BUILD (sinh bảngHookTable.g.cs, không reflection lúc chạy, IL2CPP an toàn).- Chỉ số:
StatBlockcộng/nhân theo doc 10 §2.3 (bể INC cộng, MORE nhân riêng,inc_soft1.0,more_dmg2.5); dựng lại khi build đổi (không mỗi tick). - Thẻ:
CardTextFormatter= mẫu_vi/loc +paramshạng hiện tại → chuỗi (doc 51 §3).
3.7 Kẻ địch & AI
- S (FSM archetype, doc 14 §2):
SmallBrainJob(Burst,IJobFortrên slot): mỗi tick cho quái trong r200 hoặc đang windup/strike; ngoài r200 → 10 Hz (PerfGovernor bậc 2: 5 Hz) phân nhómslot mod 6. Tham số từai_archetypes.csv; roll quarng_ai(§6) bằng khoá(slot, tick)→ kết quả không phụ thuộc thứ tự worker. - Token (doc 14 §4, INV-08):
AttackTokenschạy main thread sau job: yêu cầu xếp theo(điểm ưu tiên, slot)→ cấp tất định; trần MELEE 4/2/0, PROJ 3 loạt + 12 đạn, P_FLOOR 3, P_TOTAL 5, HEAVY 1, OBJECTIVE 6; KHÔNG thay đổi bởi Khăn Tang/Ấn/mutator/Tro Nhớ. - Di chuyển: flow field lưới 16 px (
flow_cell_px), 1 trường chung + 1 "rộng" L/XL, Burst, 10 Hz hoặc khi địa hình đổi (hố H11, gờ sụt); giãn cách khối bầy (INV-04, doc 10 §4.4) dùng SpatialHash, 2 vòng lặp Burst. - M/L/XL:
HeavyBrain(managed, mỗi tick, không LOD): utilityDECIDE+ đồ thị đòn + chống lặp + pha (doc 14 §6); trọng số Tro Nhớ CHỈ quaAshMemory.Weights()(công thức §6.3 doc 14). Boss pha/kịch bản = dữ liệubosses/<id>.json(phases[],attacks[],links); không id cứng trong code. - Perception: ring snapshot hero 0.25 s × 8 (2 s); brain chọn đòn đọc snapshot ≥ 0.25 s tuổi; KHÔNG đọc input/buffer/cooldown/HP/build (doc 14 §1.2) — biên dịch ép: brain chỉ nhận
in HeroSnapshot, không tham chiếuHeroBase. - Mọi đòn địch mang
adaptive_sourceBASE|ASH; đòn ASH thiếudefault→DataBakerlỗi.
3.8 Director
Director.Tick()mỗi 0.25 s World (15 tick) theo doc 15 §1: ví TP,target = B_obj × phase × density_mult,SpawnPlanner.Pick/Place(MIX ⊕composition_bias), beat kịch bảnEncounterRunnertừwave_scripts.csv, ELITE/MINI/BOSS giữ chỗ TP, leash 600 px/3 s hoàn TP.- Spawn công bằng INV-14: ấn tro 0.6 s (không bao giờ hạ LOD), không trong r90, 0.8 s không windup.
- Roll Director qua luồng
spawnkhoá(act, row, lane)+ bộ đếm trong phòng. PerfGovernor(doc 15 §5.3) đọcPerfMeter.p95(2 s); bậc 3 chỉ ra ở phòng sau; ghi telemetryperf_tier.
64. Kiến trúc hiệu năng cho 250 quái
4.1 Quyết định ECS
| Phương án | Ưu | Nhược | Kết luận |
|---|---|---|---|
| Entities (ECS đầy đủ) | tối đa 10k+ thực thể | 2D sprite + uGUI + công cụ pixelfx không hợp; đội 5 dev; đổi API giữa phiên bản; demo không dùng | Không |
| GameObject mỗi quái (demo) | đơn giản | 250 × (GO + 2–3 renderer) = chi phí chính (digest §3); không Burst | Không cho S |
| Lai hướng dữ liệu | SoA NativeArray + Burst/Jobs cho S, đạn, vùng, trạng thái, hash, flow field; vẽ instanced; M/L/XL + hero = GameObject | Bible §10.4 đã khoá "Burst/Jobs mảng dữ liệu"; 250 nhỏ với Burst; giữ công cụ demo | Chọn |
- Package:
com.unity.burst,com.unity.collections,com.unity.mathematics,com.unity.jobs(bản đi kèm 6000.3). Khôngcom.unity.entities. - Job chỉ đọc/ghi
NativeArraycủa chính world;Complete()trong cùng tick (không job kéo dài qua tick → không race với lưu/UI).
4.2 Pool & 0 cấp phát
| Pool | Sức chứa | Cướp khi đầy |
|---|---|---|
| slot quái S | 256 | không (Director dừng ở alive_max) |
HeavyActor + ActorView | 8 | không |
| đạn hero / địch | 300 / 12 | hero: cũ nhất; địch: không (chờ token) |
| vùng hero / địch | 24 / 16 | hero: cũ nhất; địch: không (token P) |
| instance VFX | 320 (S 160 · M 64 · L 24 · XL 4) | cũ nhất cùng hạng, trừ tell (doc 31 §6.1) |
| số sát thương | 260 (1 mesh, 24 mới/tick) | vòng ghi đè |
| AudioSource | 48 one-shot + 12 nhạc + 4 ambience (doc 32 §5.3) | theo steal |
| instance trạng thái | 1.500 | thay cái sắp hết |
- Luật: trong giao tranh 0 B GC/frame (đo
GC.Allocprofiler marker; CI §18). Cấm LINQ,foreachtrên interface, closure,string.Format, boxing trong sim/presentation hot path; chuỗi HUD dùngStringBuildercache + TMPSetCharArray. - Mọi pool tạo ở màn tải (DOCK/hub → run); không
Instantiatetrong phòng (trừ M/L/XL đã pre-warm theo roster Hồi).
4.3 Vẽ bầy (sprite batching)
- Render pipeline: URP Universal Renderer (chuyển từ built-in RP ở P0, trước MS01b; 6 shader demo
SpriteActor/SpriteXray/SpriteFlash/SpriteAdd/SpriteBasic/Zoneviết lại HLSL unlit). Không đèn 2D, không post-process ngoài LUT màn + chớp. - Camera pixel (Bible §10.4): render vào RT 480×270 (16:9) hoặc 427×267 (16:10), filter Point, upscale nguyên ×4 / ×3 (1281×801 cắt 1 px); UI canvas riêng ở độ phân giải gốc. Toạ độ sàn → màn:
screenX = x − camX,screenY = (z − camZ) × 0.5 + y(doc 10 §1.3), snap 1 px nghệ thuật. CrowdRenderer: job Burst dựng buffer instance{pos_px, uvRect, page, flip, flashT, outlineFlags, lutRow, depth}→ mỗi trang atlas 1 lệnhGraphics.RenderMeshInstanced(quad chung). Sort bằng depth: sprite pixel alpha 0/1 → alpha-test + ZWrite,depth = f(z_chân)→ không sort CPU, gom lô tự do theo trang. FX cộng (SpriteAdd) + bán trong suốt vẽ sau, sort CPU (≤ 320).- 5 lớp VFX → sorting layer Unity đúng doc 31 §2.4 (
FXBot,GamePlayLayer1/2,FxLayer,Prioritize, overlay). X-ray hero: pass stencil (hero ref 1, quái ref 2)ZTest Greater55% (INV-09). - Mục tiêu draw call: quái S ≤ 1 lệnh/trang hoạt động (≤ 6), FX ≤ 12, sàn/tile ≤ 8, actor lớn ≤ 16, UI ≤ 30 → ≤ 80 draw call/frame [ASSUMED].
- Off-screen: bỏ instance ngoài khung + 32 px; anim quái ngoài khung đứng hình ở PerfGovernor bậc 1.
4.4 Texture & bộ nhớ
- Trang atlas 2048², màu chỉ mục: kênh R = chỉ số palette, G = mask điểm nhấn (INV-01d) → định dạng
RG88 MB/trang (RGBA32 16 MB). Shader traPaletteLut(256 × 64 hàng: hàng biome, hàng hero slot, alt tro-xương, skin) = palette LUT 1 pass (Bible §10.4). - Ngân sách: ≤ 21 trang thường trú (Bible §10.3) × 8 MB = 168 MB; hero đang chơi ≤ 8 trang (64 MB), bậc VFX 1.7/2.2 nạp lười (doc 31 §8).
- Addressables: nhóm
hero_<Hxx>,biome_<Bn>,boss_<id>,shared,ui,audio_<owner>,data,loc_<lang>; nạp hero ở chọn hero; nạp biome kế + dỡ biome cũ trong DOCK; "Thức Lại" hub → run ≤ 5 s (doc 01 §5.3). - Import (giữ luật demo): Point, không mip, không nén lossy, max 2048; UI 25 PPU, world 16 PPU (
PixelArtImportRules.cs).
4.5 Hạ cấp dưới tải (1 đồng hồ đo, 3 thang)
PerfMeter: thời gian frame CPU (FrameTimingManager), p95 trượt 1 s và 2 s, số quái sống.
| Thang | Chủ | Vào | Cắt | Không bao giờ cắt |
|---|---|---|---|---|
| VFX L0–L3 | doc 31 §6.2 | quái ≥ 180/220 hoặc p95 > 15.0/16.6/20 ms | hạt ×0.6/×0.35, trúng/chết mỗi frame, đuôi đạn | tell Y/P/N, chớp trắng, vòng chân, x-ray, ấn spawn, vùng đích đạn P |
| Director bậc 0–3 | doc 15 §5.3 | p95 > 16.6 ms 2/2/3 s | số phụ, VFX chết, AI ngoài r200 5 Hz, density_mult ×0.90, cap_count 180 | như trên + token, sát thương, va chạm |
| Âm thanh | doc 32 §5.3, §6 | voice > 56 | ảo hoá Pickup → Status → Ambience → EnemyCrowd | 29 voice dành chỗ (tell, ĐÁP, ult, nhạc) |
- Preset Deck khởi động ở VFX L1 +
fx_particle_cap1.050 (PlatformTuning).
75. Ngân sách hiệu năng theo máy
5.1 Máy đích
| Máy | Cấu hình | Đích |
|---|---|---|
| Steam Deck (trần thiết kế, doc 01 §5.3) | APU 4 nhân/8 luồng, 16 GB chung, 1280×800 | 60 fps, p95 ≤ 16.6 ms, CPU main ≤ 10 ms, 250 quái (preset Deck 180 tự hạ khi fps < 50 trong 10 s) |
| Desktop tối thiểu [ASSUMED] | CPU 4 nhân/4 luồng ~2.5 GHz (đời 2015+), 8 GB RAM, GPU DX11 2 GB VRAM hoặc tích hợp đời 2018+, Windows 10 x64, 4 GB đĩa | như Deck, 1080p |
| Desktop khuyến nghị [ASSUMED] | 6 nhân, 16 GB, GPU rời 4 GB | 60 fps khoá, p99 ≤ 16.6 ms, 1440p/4K (upscale nguyên) |
5.2 CPU main thread (Deck, 250 quái + Ult + 1 Tinh Anh + MESO = kịch bản MS01b)
| Hệ | ms | Hệ | ms |
|---|---|---|---|
| Input + buffer + Hero FSM | 0.4 | Đạn + vùng + hazard | 0.4 |
| SpatialHash + giãn cách (Burst) | 0.5 | Trạng thái (DoT lệch pha) | 0.4 |
| HitResolver + DamagePipeline | 1.0 | Chết/rơi đồ/XP/EffectRuntime | 0.5 |
| AI S (Burst) + token + flow field | 1.3 | AshMemory + Director (khấu hao) | 0.3 |
| HeavyBrain M/L/XL | 0.4 | Tổng sim | 5.2 |
| CrowdRenderer (job + submit) | 0.8 | FxRuntime + số sát thương | 1.0 |
| Âm (HordeAudio, SfxPlayer, Music) | 0.4 | UI/HUD (chỉ khi dirty) + camera | 0.6 |
| Tổng hiển thị | 2.8 | Dự phòng (GC 0, tick bù) | 2.0 |
- Tổng ngân sách = 10.0 ms. Tick bù 2 tick/frame chỉ được phép < 5% frame (p95 vẫn ≤ 16.6 ms). Worker Burst chạy trên 3 nhân còn lại, ≤ 4 ms/frame.
- GPU Deck ≤ 10 ms [ASSUMED]: RT nhỏ, chi phí chủ yếu overdraw FX cộng; ≤ 1 050 hạt (Deck, preset 70%); 1 500 desktop (tuning
platform.fx_particle_cap).
5.3 Bộ nhớ & đĩa
| Hạng mục | Trần | Nguồn |
|---|---|---|
| RAM tiến trình | ≤ 3 GB | doc 01 §5.3 [ASSUMED] |
| Texture thường trú | ≤ 1 GB (atlas ≤ 168 MB + UI + tile) | doc 01 §5.3 |
| SFX thường trú/Hồi | ≤ 40 MB giải nén; nhạc streaming ≤ 12 luồng, đệm ≤ 6 MB | doc 32 §11.3 |
| Cài đặt | ≤ 4 GB (âm ≤ 400 MB) | doc 01 §5.3, doc 32 §11.3 |
| Lưu | meta_<steamId>.json ≤ 512 KB · run_active.json ≤ 256 KB · ash_profile_<steamId>.json ≤ 512 KB · settings_shared.json ≤ 32 KB · local/settings_device.json ≤ 16 KB · quota Cloud 64 MB / 40 file [ASSUMED] | doc 43 §7.1, §9.1 |
| Tải | hub → run ≤ 5 s · DOCK → Hồi kế ≤ 3 s [ASSUMED] · boot → title ≤ 8 s trên Deck [ASSUMED] | doc 01 §5.3 |
86. Tất định seed (DAILY, Truy Tuần, CUSTOM)
6.1 Phạm vi tất định (Bible §4.1, doc 16 §10)
- Tất định = nội dung: hero, Dạng Rèn, ult, 2 kỹ năng khởi đầu, bản đồ 4 Hồi, icon thưởng, mục tiêu, hàng sạp, sự kiện, 3 Khăn Tang, 2 mutator, vector Tro Nhớ seed. KHÔNG replay tất định khung-theo-khung: luồng
spawn/loot/ailệch theo hành động người chơi; float trong sim được phép. - Hệ quả kỹ thuật: không cần lockstep, không cần float tất định giữa máy; CHỈ phần sinh nội dung phải giống hệt trên mọi máy (Windows, Deck, port sau).
6.2 Luồng RNG (Bladewake.Core.RngStreams)
seed64 (DAILY) = SplitMix64(FNV1a64("BW:DAILY:" + yyyy-mm-dd(UTC) + ":" + dec(content_version))) // vd "BW:DAILY:2028-01-15:7"
seed64 (WEEKLY) = SplitMix64(FNV1a64("BW:WEEKLY:" + yyyy-Www(ISO) + ":" + dec(content_version))) // vd "BW:WEEKLY:2028-W02:7"
// dec(int) = số nguyên thập phân, không đệm 0, không dấu cách; FNV1a64 = offset 0xcbf29ce484222325, prime 0x100000001b3, trên byte UTF-8
// SplitMix64(x): z = x + 0x9E3779B97F4A7C15; z = (z ^ z>>30) × 0xBF58476D1CE4E5B9; z = (z ^ z>>27) × 0x94D049BB133111EB; trả z ^ z>>31 (mod 2^64)
seed64 (STANDARD) = 64 bit ngẫu nhiên lúc RUN_START ; CUSTOM = 16 ký tự hex người chơi nhập
k = FNV1a64(UTF8("BW:" + stream + ":" + join(key, ","))) // key = số nguyên thập phân
state = SplitMix64(seed64 XOR k) ; inc = SplitMix64(k) OR 1
rng = PCG32(state, inc) // PCG-XSH-RR 32 bit
stream (tuning run.rng_streams) | key | Dùng |
|---|---|---|
map · mutator | act | MapGen.Generate(act, seed), mutator Hồi |
reward · shop · event | act·100 + row·10 + lane | icon thưởng, hàng sạp, sự kiện nút |
draft | act, draft_index_in_act | thẻ draft (không phụ thuộc số con đã giết) |
spawn · loot · ai | act, row, lane (+ bộ đếm trong phòng) | Director, rơi đồ, roll AI (rng_ai) |
fx | tự do | chỉ hình ảnh/âm; luồng DUY NHẤT được dùng không khoá |
- Rút trong job Burst:
Pcg32.Stateless(state, (uint)slot, (uint)tick)= băm(state, slot, tick)→ kết quả không phụ thuộc thứ tự worker. - Chỉ số nguyên cho sinh nội dung: trọng số đổi thành
int(×1000, làm tròn lúc nạp), rollNextBounded(totalWeight)không lệch; cấmfloatso sánh trongMapGen, draft, sạp, sự kiện, mutator. Chuỗi băm = UTF-8 không BOM. - Bộ đếm rút mỗi luồng lưu trong
run_active.json(doc 16 §11); vào lại phòng sau crash → luồngspawn/lootcủa nút reset về đầu phòng → cùng kịch bản. content_version= int đơn điệu (doc 43 §11.3; cùng số trong khối đầu save §9.2 + leaderboarddetails[9]int32).build_gdd.pyghi vào_unity/content_version.txt(1 dòng, số thập phân) chép từGDD/Full/content_version.txt(1 số nguyên, người giữ dữ liệu tăng tay; bắt đầu 1 ở MS02 [ASSUMED]); CHỈ tăng khi dữ liệu gameplay đổi theo cách cần migration hoặc làm điểm không so được (luật doc 43 §11.3, kèm mụcDEC-). Không tăng → seed DAILY/WEEKLY giữ nguyên. Máy chủ (§6.3) và client băm cùng chuỗi → cùng seed.content_hash= giá trị riêng: crc32 của_merged.json, 8 hex chữ thường (vd"3fa91c07"),build_gdd.pyghi_unity/content_hash.txt; client tính lại lúc khởi động trên gói dữ liệu đã nạp. Dùng cho kiểm (e) doc 43 §6.3 (danh sách hash live) và cờ "nội dung đã sửa" doc 43 §16. KHÔNG đưa vào chuỗi seed.- Test vàng (§18.1):
RngStreams.DailySeed("2028-01-15", 7)→ chuỗi"BW:DAILY:2028-01-15:7"→ FNV1a64 =0xb3dfbbdb7003a1cf→ seed64 =0xa11f150d17864318. - Ngày UTC lấy từ
TimeManagerđồng bộ giờ máy chủ Steam (SteamUtils.GetServerRealTime), không giờ máy (chống chỉnh đồng hồ). - Ví dụ:
draftHồi II lần draft thứ 3 → key"BW:draft:2,3"; 2 người cùng seed chọn cùng thẻ trước đó → cùng 3 thẻ, dù giết khác số quái.
6.3 Nộp điểm (mọi bảng; nguồn = doc 43 §6.2–§6.3, doc này chỉ chép giá trị để code)
- Mọi bảng Trusted (doc 43 §6.2): client KHÔNG ghi điểm, chỉ đọc (
FindLeaderboard,DownloadLeaderboardEntriesGlobal/AroundUser/Friends). Máy chủ ghi bằng Web APISetLeaderboardScore:KeepBestcho ENDLESS/BOSS_RUSH,ForceUpdatecho DAILY/WEEKLY. Khoá Web API publisher chỉ ở máy chủ, không bao giờ trong build. - Tên bảng (doc 43 §6.2):
DAILY_<YYYYMMDD>·WEEKLY_<YYYY>W<ww>·ENDLESS_FLOOR+ENDLESS_FLOOR_H01…_H12·BOSS_RUSH_<YYYY>W<ww>; buildqa/dev_leaderboard(defineBW_DEV_LB) → bảngDEV_*tương ứng. STANDARD/CUSTOM không bảng. - Bước 1 — kết run tính điểm →
RunSubmission{steam_id, board, seed64, daily_key, mode, hero, aspect, ult, do_tang, kills, perfect_answers, acts_cleared, seconds_played, tells_emitted_yellow, tells_emitted_purple, act_times_s[], alive_cap, content_hash, build_number, details[]};alive_cap= 250 | 180 (trần thấp nhất đã áp trong run);details[]int32 theo doc 43 §6.2 ([9]=content_versionint,[10]=build_number). - Bước 2 — vé
GetAuthTicketForWebApi("bw-score")→ POST HTTPS tới máy chủ điểm (URL trong config build; dịch vụ HTTP serverless của studio, skillbackend-communication). - Bước 3 — máy chủ:
AuthenticateUserTicket+ quyền sở hữu app → tracontent_hash→content_version→ đọcseedcap/<daily_key>/<content_version>(CIdaily_seed_bake, doc 43 §6.3 bước 0) → 6 kiểm: (a) điểm/giây mỗi Hồi ≤score_per_s_cap[act]· (b) kills/phút ≤spawn_max_per_min_250hoặc_180theoalive_cap· (c) mỗiact_times_s[i]≥act_min_s[i]· (d)perfect_answers≤tells_emitted_yellow + tells_emitted_purple· (e)content_hashthuộc danh sách hash live · (f) chưa có lượt chodaily_key/tuần. - Bước 4 — đạt →
SetLeaderboardScore→ client nhậnaccepted+ hạng; trượt →held(hàng duyệt tay, UI "Điểm đang chờ duyệt"). Top 50 mỗi bảng duyệt tay mỗi ngày. - Bước 5 — ngoại tuyến/máy chủ lỗi:
RunSubmissiongiữ tronglocal/score_queue/(§9.1, ≤ 5 MB), thử lại khi khởi động + mỗi 5 phút ở hub + 10 s sau resume. Hạn (giờ máy chủ nhận, doc 19 §5.2): DAILY trước 02:00 UTC ngày kế ngày củadaily_key; tuần trước 02:00 UTC thứ Hai sau tuần đó; quá hạn →expired, client xoá bài, báo "Đã quá hạn nộp". - Bước 7 — thưởng top 10% DAILY: client
GET /v1/daily/result?key=<daily_key>lúc khởi động + mỗi lần vào hub; điểm ≥cutoffvà key chưa có trongmeta.daily_rewards_claimed[]→ +5 Nến Canh CUR_VIGIL_CANDLE + thêm key trong CÙNG một lần ghi nguyên tửmeta_<steamId>.json(§9.2). - Điểm DAILY/WEEKLY =
(kills + 50 × perfect_answers + 2000 × acts_cleared) × (1 + 0.05 × Độ Tang) − seconds_played, sàn 0 (Bible §4.1); client tính để hiện, máy chủ là nguồn. ENDLESS =floor × 1 000 000 + (999 999 − min(999 999, giây chơi)); BOSS_RUSH = tổng ms 6 trận (doc 43 §6.2). - Khoá nộp (không gửi): CUSTOM · Thương Xót bật bất kỳ mục ·
content_hashlệch · chế độ ngoại tuyến không Steam (§10). Lượt DAILY/tuần bị tiêu khi bắt đầu run (daily_keyghi vàorun_active.json+ meta).
97. Runtime Tro Nhớ (Bladewake.Ash)
7.1 Lấy mẫu PSP (A01–A12, doc 14 §7)
- Nguồn =
SimEventBuscuối tick:TellResolved{answer|hold|dash|hit},HeroDamage{dist, targets, dmg_kind},DashStart{dir, t_to_impact},InputPress{kind}, vị trí hero (ô 4×4 sân),EnemyLifespan{role, t_visible},BrokenWindow{burst}. Chỉ ghi khi nguồn trong vùng an toàn 427×267 + 16 px. - Thùng 0.25 s = 15 tick. Kho SoA
[axis][bin]2 sốnum,den(+ nhánh A01 DEFLECT/HOLD/DASH):W1040 thùng,W2080 thùng, tổng chạy cộng thùng mới / trừ thùng hết hạn (O(1)/tick);ROOMcộng dồn;FIGHTcộng dồn trận boss Hồi. AshMemory.Read(axis)= snapshot ≥ 0.5 s tuổi: W20 bỏ 2 thùng mới nhất; MESO = bản chốt cuối phòng trước. Brain chỉ gọiRead/Weights, không chạm kho.- Cuối phòng:
x_roomhợp lệ khi mẫu ≥min_room(axis)→MESO_n = 0.85 × MESO_{n−1} + 0.15 × x_room; thiếu mẫu → giữ MESO, không reset streak. Evaluate()4 Hz: MICRO (bật ≥ 5 s, quên 30 s); MESO cuối phòng (bật ≥ 2 phòng liền); MACRO đầu trận boss. Mọi so ngưỡng quaAshMemory.Threshold(hero, axis, dir, row)(on/off hiệu lực theohero_baseline).L_eff = clamp(L_mode − mask − mercy, 0, 3); = 0 khi run < 60 s (MRN13: 30 s), < 3 run đời, Thương Xót Tro Nhớ 0. Ngân sách CV đồng thời theo Hồi I 1 · II 2 · III–IV 3; đổi CV chỉ ởREST; báo ≥ 60 tick trước hiệu lực.- Công tắc pháp lý
adaptive.legal_gate(Bible, FTO trước MS03): cờ dữ liệu → MACRO + Sẹo chỉ-thoại (thẻ/bark, không bật CV). Phải bật/tắt được không cần build mới (nằm trongtuning+ ghi đè từ xa qua bản vá dữ liệu). - Chi phí: ~0.15 ms/tick Deck (12 trục × 2 cửa sổ, cộng trừ thùng);
Evaluate4 Hz ≤ 0.2 ms.
7.2 Lưu xuyên run
- File
save/ash_profile_<steamId>.json(doc 14 §9.1):profile_version,kit_version,heroes[Hxx].meso+streak+runs_played,ledger[Hxx][BS_B1..BS_B4]ring 5,scars[BS_B1..BS_B4]≤ 2. - Ghi: chỉ STANDARD/ENDLESS + công tắc "Trí nhớ xuyên run" BẬT; ở điểm lưu (
MAP,BOSS_GATE, đầuDOCK) khi MESO đổi; cuối trận boss ≥ 20 s (ledger); cuối run (age_runs += 1). DAILY/BOSS_RUSH/CUSTOM không ghi. - Đọc MACRO:
w_k = r_rank(k) × 0.9^age_runs, bỏ bản ghiage_runs > 20, cần ≥ 2 bản ghi (doc 14 §9.2). Test vàng: 3 bản ghi 0.80/0.70/0.55, age 0/2/5 → 0.72. kit_versionkhác → bỏactive_cvlạ, giữaxes. "Xoá Sổ Thù" trong Cài đặt: theo hero hoặc tất cả, 2 bước xác nhận, không xoá Sẹo.- Ghi nguyên tử + Steam Auto-Cloud như §9.3.
108. Đường dữ liệu (GDD JSON → Unity runtime)
8.1 Chuỗi bước
| # | Bước | Công cụ | Đầu ra | Cổng |
|---|---|---|---|---|
| 1 | Sửa JSON | agent/dev | GDD/Full/data/**/*.json | schema tools/schema/* |
| 2 | Kiểm | py -3 GDD/Full/tools/validate.py --all --banned-terms <ngoài repo> | báo ERROR/WARN | 0 ERROR (CI chặn merge) |
| 3 | Render | py -3 GDD/Full/tools/build_gdd.py --only csv | GDD/Full/data/_unity/*.csv + *.json (doc 51 §8) + content_version.txt + loc_source.csv (khoá <file>.<id>.<field>, doc 51 §3) | không sửa tay bản render |
| 4 | Đồng bộ | menu Unity Bladewake/Sync GDD Data (thay bản demo) | chép vào Assets/_Project/Data/Generated/ (Addressables nhóm data) | file render được commit; CI so lệch render (build lại → git diff --exit-code) |
| 5 | Bake | DataBaker (Editor, chạy sau Sync + trước build) | SO: HeroKitSO ×12, MoveTableSO, EnemyTableSO, BossSO, UpgradeTableSO, EncounterSO, RunMapSO, AudioDbSO, TuningSO, PlatformTuningSO + IdRegistry | lỗi tham chiếu/handle → build fail |
| 6 | Codegen | DataBaker | MechanicIds.g.cs, HookTable.g.cs, TuningKeys.g.cs, EventName.g.cs (sự kiện doc 32 §12.2) | không sửa tay *.g.cs |
| 7 | Runtime | Db (module của DataManager, chỉ đọc) | blob struct (BlobArray trong NativeArray) cho Burst + SO cho managed | Db.Ready trước màn title |
- Parser CSV giữ hợp đồng demo: header theo tên (thứ tự tự do, cột lạ bỏ qua, thiếu = mặc định), ngoặc kép
"",#comment, bỏ BOM, số invariant, danh sách|; ôparams= JSON 1 ô. - Tuning:
TuningKeys.g.cssinh hằng từtuning.json→ vdDb.T(TuningKeys.Caps.AliveMax)=caps.alive_max250 (không chuỗi rơi). Lớp ghi đè nền tảng:PlatformTuning.csvcột nền tảng hiện tại → trống →DEFAULT(doc 01 §5.1); PC_KBM ↔ PC_PAD đổi sống theo thiết bị input cuối. ContentManifest(SO trong Unity, KHÔNG phải dữ liệu GDD): danh sách id cho phép theo build (MS02: H01 H02 H07, Hồi I, 12 SE, 10 RL, STANDARD; cờ build trước U3 — Bible §10). Id ngoài manifest: không vào pool draft/sạp/codex/chọn hero; dữ liệu vẫn nạp (để lưu không vỡ).
8.2 Hot reload (chỉ Editor + build BW_DEV)
| Loại đổi | Cách | Áp lúc |
|---|---|---|
| Số (tuning, coef, windup, TP, giá) | F5 hoặc theo dõi file _unity/ (FileSystemWatcher, debounce 0.5 s) → parse lại → thay bảng | ranh giới tick (giữa 2 SimWorld.Tick) |
| Cấu trúc (id mới, đòn mới, pha boss, wave script) | dev.reload --room | khởi động lại phòng hiện tại, giữ build run |
| Asset (sheet, âm) | Addressables "Use Asset Database" trong Editor | lần phát kế |
- Hot reload không chạy ở build phát hành (dữ liệu nằm trong Addressables, khoá theo
content_version). Hiển thị số dòng đã đổi + id lỗi trên overlay.
8.3 Luật nạp
- Mọi id tham chiếu phải có trong
IdRegistry; thiếu →Debug.LogError+ từ chối dòng (build dev) / build fail (bake). - Số tick tính lúc nạp (§3.1). Mẫu câu thẻ kiểm
{param}lúc bake (cùng luật validator). - Không
Resources.Loadcho dữ liệu/asset game (chỉ font khẩn cấp + màn lỗi).
119. Hệ lưu
Nguồn luật = doc 43 §7.1–§7.3 (tên file, cỡ trần, Cloud, khối đầu, xung đột); doc này chỉ thêm cách cài đặt. Lệch → doc 43 thắng.
9.1 File & thư mục
Gốc = Application.persistentDataPath/<steamId64>/ (LUÔN có thư mục steamId64, kể cả máy 1 tài khoản). Demo: thư mục save_demo/ thay save/ dưới cùng gốc (doc 43 §7.1; app riêng + productName riêng → Cloud riêng), cùng luật.
| File (dưới gốc) | Nội dung | Ghi khi | Cỡ trần | Steam Cloud |
|---|---|---|---|---|
save/meta_<steamId>.json | module DataPlayer: Meta (CUR_SOUL_ASH, CUR_ECHO_SHARD, CUR_VIGIL_CANDLE, UP_*, GD1–5, FLAG_*, END đã đạt, Lễ Vật, Thử Thách, mở khoá), Heroes (mở khoá, MR01–20, Dạng Rèn, ult, skin), Codex (CDX_* đã mở), Modes (lượt DAILY/Truy Tuần đã dùng theo daily_key, kỷ lục ENDLESS/BOSS_RUSH, daily_rewards_claimed[]), Stats (bộ đếm stat/thành tựu), achievements_pending[], active_run_id | điểm lưu run (MAP, BOSS_GATE, đầu DOCK), mua ở trạm hub, màn kết, thoát | 512 KB | có |
save/run_active.json | đúng danh sách doc 16 §11 (run_id, mode, seed64, daily_key, hero + loadout, Khăn Tang, act, bản đồ, HP, cấp/XP, kỹ năng/Edge/Relic/affix/RS/EVO + hạng, Dư Âm, Vàng Mã, Đổi/Xoá/Giữ, Lửa Gửi, đếm MRN14, bộ đếm luồng RNG, W20/MESO trong run, đồng hồ, thống kê) | vào MAP (sau màn thưởng), BOSS_GATE, đầu DOCK; KHÔNG trong phòng | 256 KB | có |
save/ash_profile_<steamId>.json | §7.2 (PSP MESO + Sổ Thù + Sẹo, doc 14 §9.1) | §7.2 | 512 KB | có |
save/settings_shared.json | module Settings phần theo NGƯỜI (khoá S doc 42): remap bindings.<family>.<map>.<action_id>[0..1] (doc 41 §8.1), preset 1 tay, trợ năng, Thương Xót, ngôn ngữ, phụ đề, rung, cỡ chữ, âm lượng, đồng ý telemetry/crash + telemetry_id (§15) | đóng Cài đặt, màn đồng ý | 32 KB | có |
save/*.bak | bản trước của mỗi file trên | mỗi lần ghi | = file | có |
local/settings_device.json | module Settings phần theo MÁY (khoá D doc 42): độ phân giải, preset đồ hoạ/FX, trần fps, vsync, chế độ cửa sổ, thiết bị âm, Vùng an toàn màn | đóng Cài đặt | 16 KB | không |
local/cloud_marker.json | save_seq cuối máy này ghi cho từng file + device_id | sau mỗi ghi save/ | 4 KB | không |
Application.persistentDataPath/local/last_user.json (cấp MÁY, ngoài mọi gốc steamId64) | steamId64 cuối đã đăng nhập (cho chế độ ngoại tuyến) | sau SteamAPI_Init thành công | 1 KB | không |
local/score_queue/ · local/telemetry_queue/ | hàng chờ mạng (§6.3, §15) | §6.3, §15 | ≤ 5 MB | không |
local/crash/ · local/logs/ · local/conflict/ · local/ash_debug.csv | dump, log, bản lưu bị loại (§9.3), debug Tro Nhớ | §15, §9.3 | ≤ 20 MB | không |
- Template
DataPlayer/PlayerDataManager: 5 moduleMeta/Heroes/Codex/Modes/Stats→ 1 filemeta_<steamId>.json; moduleSettingstách 2 file (settings_shared.json= khoáS,local/settings_device.json= khoáDdoc 42). Thay storage provider mặc định bằngJsonFileProvider(§9.2); mặc định mọi field trongSetupDefaultData()(rule data-persistence); KHÔNGSave()trong Update — gọiSaveService.RequestSave(reason), gộp (debounce 2 s, trừ thoát/suspend = ngay). - Tiền meta gửi vào
meta_<steamId>.jsonở MỖI điểm lưu run (crash/mất Cloud tối đa mất 1 phòng, doc 16 §11). - Không có Steam (
SteamAPI_Initthất bại, chế độ ngoại tuyến doc 43 §13): gốc =<steamId64>đọc từlocal/last_user.jsoncấp máy; không có file → gốcApplication.persistentDataPath/0/(steamId=0trong tên file). Lần mở kế có Steam:0/có dữ liệu và<steamId64>/save/RỖNG → chuyển0/save/*vào<steamId64>/save/(đổi tênmeta_0.json→meta_<steamId>.json,ash_profile_0.jsontương tự), xoá0/sau khi ghi nguyên tử xong;<steamId64>/save/KHÔNG rỗng → áp luật xung đột 2 (§9.3) giữa bản0/và bản<steamId64>/, bản không chọn chép vàolocal/conflict/. - Quota Steamworks: 64 MB, 40 file / người [ASSUMED] (dùng thực ~3 MB, 8 file).
9.2 Khối đầu, ghi nguyên tử & chịu lỗi (doc 43 §7.2)
- Mọi file
save/mở đầu bằng khốiheader= {schema_version(int),content_version(int, §6.2),save_seq(int, +1 mỗi lần ghi file đó),utc_written(ISO-8601 từTimeManager),device_id(UUID ngẫu nhiên lưu ởlocal/cloud_marker.json, không phải mã phần cứng),checksum(CRC32 phần thân, 8 hex chữ thường)}; thân = dữ liệu module.
snapshot = Serialize(state) // main thread, JSON UTF-8 không BOM; header.save_seq = marker[file] + 1; checksum = crc32(thân)
await UniTask.RunOnThreadPool:
write save/x.json.tmp ; Flush(true) (fsync)
File.Replace(x.json.tmp → x.json, backup x.json.bak) // .tmp → fsync → file cũ thành .bak → .tmp thành file chính (nguyên tử trên Windows)
cập nhật local/cloud_marker.json[x] = save_seq
Nạp: đọc x.json → checksum sai/JSON hỏng → luật 3 §9.3
- Ghi tối đa 1 lần/2 s/file; hàng đợi 1 phần tử (bản mới thay bản chờ). Thoát (
Application.quitting), mất focus, Deck suspend → ghi đồng bộ ngay nếu bẩn. Không ghi trong phòng chiến đấu. - Vượt cỡ trần §9.1 → log lỗi + vẫn ghi (không mất dữ liệu), CI test cỡ file với fixture "người chơi 1 000 giờ".
9.3 Phiên bản, di trú & xung đột
schema_version(int) mỗi file.MigrationChain: các bướcM<from>_<to>trên cây JSON động (package JSON đi kèm Unity 6, kiểu cây; không giải tuần tự giữa chừng), chạy tuần tự tới bản hiện tại trước khi giải tuần tự. Mỗi bước có fixtureTests/SaveFixtures/v<n>/*.json+ test EditMode (nạp → di trú → so kết quả mong đợi).- ID bất biến sau ship (doc 51 §2) → lưu chỉ bằng ID chuỗi. ID không còn trong dữ liệu: bỏ mục + hoàn tiền mở khoá đã trả (log
save_drop), không crash. - Lệch
kit_versionTro Nhớ → §7.2. - Luật xung đột (doc 43 §7.3, áp theo thứ tự):
| # | Tình huống | Luật |
|---|---|---|
| 1 | Steam phát hiện xung đột Cloud trước khi chạy | hộp thoại của Steam client quyết định; game không can thiệp |
| 2 | file tải vào có save_seq < local/cloud_marker.json của máy này | màn SaveConflict (doc 40 §2 #45): 2 thẻ {ngày giờ, giờ chơi, Tro Hồn, hero đã mở, Hồi xa nhất}; bản không chọn → local/conflict/<file>_<utc>.json (giữ 3 bản, 30 ngày) |
| 3 | checksum hỏng / JSON lỗi | dùng .bak; .bak hỏng → chép file hỏng vào local/conflict/, SaveNotice "Bản lưu hỏng, đã khôi phục bản gần nhất" (meta: tạo mặc định) hoặc bỏ run dở (run) |
| 4 | run_active.json có run_id ≠ meta.active_run_id | bỏ run dở (tiền meta đã gửi giữ nguyên, doc 16 §11), báo 1 dòng |
| 5 | header.content_version > build đang chạy | khoá chỉ-đọc: không ghi đè, SaveNotice "Hãy cập nhật game" |
| 6 | header.content_version < build | migration tuần tự v → v+1; lỗi → giữ bản gốc ở local/conflict/, không ghi; run_active.json không có bước di trú run → hộp "Bỏ run" (tiền meta giữ) |
| 7 | ash_profile lệch meta (khác máy) | không gộp; dùng bản có save_seq cao hơn |
- Không bao giờ tự xoá dữ liệu Cloud. "Xoá toàn bộ tiến trình": xác nhận 2 lần (giữ nút 2 s) + chép bản sao vào
local/conflict/. - Steam Auto-Cloud (cấu hình Steamworks): gốc
WinAppDataLocalLow, thư mục conBladewakeDev/Bladewake/<steamId64>/save(biến{64BitSteamID}của Auto-Cloud), mẫu*.json+*.bak(không.tmp, khônglocal/), quota 64 MB / 40 file [ASSUMED]. Demo:BladewakeDev/Bladewake Demo/<steamId64>/save_demo. Deck đồng bộ cùng luật vì cùng build Windows. PlayerSettings.companyName=BladewakeDev,productName=Bladewake(bản Demo:productName=Bladewake Demo) — chuỗi chữ cố định, đặt ở P0 (dự án hiện cònDefaultCompany/G008_Bladewake) và khoá trước MS02 (demo đầu tiên có Cloud); đổi sau đó = đổiApplication.persistentDataPath→ mồ côi mọi save + file Cloud. CI kiểm 2 chuỗi này trongProjectSettings.asset.
9.4 Tạm dừng & thoát giữa run
- Mất focus / Deck suspend / overlay Steam mở → Menu tạm dừng (World + Hero dừng), không cần lưu (doc 16 §11).
- Thoát giữa phòng (crash, mất điện) → lần mở sau chơi lại phòng từ đầu với trạng thái ở điểm lưu cuối; DAILY cộng
seconds_playedcả phần mất. - Bản build mới nạp run cũ: test di trú bắt buộc trong CI cho mọi
schema_versionđã ship.
1210. Tích hợp Steamworks (Bladewake.Platform)
- Thư viện: wrapper C# mã nguồn mở cho Steamworks SDK, đóng gói UPM nội bộ, khoá phiên bản SDK theo build. Mọi gọi qua
IPlatformService(doc 43 gọiIPlatformServices, cùng interface);SteamPlatformService·OfflinePlatformService(khiSteamAPI_Initlỗi, doc 43 §13) ·NullPlatformServicecho Editor/CI (không Steam) và nền port sau.
| Tính năng | Cách làm | Dữ liệu |
|---|---|---|
| Khởi tạo | SteamAPI_Init (KHÔNG gọi RestartAppIfNecessary, doc 43 §13); thành công → ghi local/last_user.json (§9.1); lỗi → OfflinePlatformService = chế độ ngoại tuyến doc 43 §13: lưu cục bộ cùng định dạng (gốc §9.1, không Cloud), thành tựu vào achievements_pending[] trong meta_<steamId>.json (đẩy lên Steam ở lần Init thành công kế), stats cộng cục bộ, leaderboard ẩn, rich presence tắt, DAILY = luyện tập (seed vẫn tất định, không nộp); không chặn chơi | AppID 1.0 · AppID demo riêng (Next Fest MS02) · AppID playtest |
Thành tựu (60 ACH_*) | AchievementService nghe SimEventBus/EventName + bộ đếm Stats; SetAchievement(steam_api_name) → StoreStats NGAY (thành tựu mới được miễn trần 60 s, doc 43 §6.1) | achievements.json (steam_api_name, hidden, mercy_blocked = không mở khi Thương Xót bật) |
| Stat | SetStat ở sự kiện; StoreStats CHỈ ở điểm lưu run (MAP, BOSS_GATE, DOCK), hub, màn kết, thoát game, tối đa 1 lần/60 s (doc 43 §6.1); không bao giờ trong vòng lặp frame; API name STAT_* doc 43 §6.1, đồng bộ với module Stats của meta_<steamId>.json | sinh steam_stats.vdf từ achievements.json + bảng doc 43 §6.1 lúc build |
| Leaderboard | mọi bảng Trusted, client chỉ đọc; máy chủ ghi (§6.3) | tên bảng doc 43 §6.2: DAILY_<YYYYMMDD> · WEEKLY_<YYYY>W<ww> · ENDLESS_FLOOR / ENDLESS_FLOOR_H01…_H12 · BOSS_RUSH_<YYYY>W<ww> · DEV_* (build qa/dev_leaderboard) |
| Cloud | Auto-Cloud §9.3 (mẫu *.json + *.bak dưới <steamId64>/save) | — |
| Rich Presence | steam_display = 1 trong 13 token doc 43 §9 (#Menu #Hub #Yard #Run #RunMourning #Boss #Finale #Secret #Daily #Weekly #Endless #BossRush #Custom) + token tham số #Hero_H01…_H12, #Biome_B1…_B4; khoá tham số hero, act (I–IV), biome, dotang, floor, fight; cập nhật chỉ khi đổi trạng thái, ≤ 1 lần/5 s; demo chỉ #Menu #Hub #Yard #Run #Boss | file token Rich Presence sinh từ LocService (1 khối/ngôn ngữ đã ship) |
| Overlay | GameOverlayActivated_t → tạm dừng; mua/web không dùng | — |
| Steam Deck | SteamUtils.IsSteamRunningOnSteamDeck() → nền DECK (PlatformTuning, glyph Deck, ui_text_scale_min 1.25, VFX L1); bàn phím nổi ShowFloatingGamepadTextInput cho seed CUSTOM | — |
| Giờ máy chủ | SteamUtils.GetServerRealTime → TimeManager (ngày DAILY) | — |
| Xác thực máy chủ | GetAuthTicketForWebApi("bw-score") cho §6.3 (mọi bảng) | — |
- Checklist Deck (Verified ở 1.0, Playable ở EA): cấu hình tay cầm mặc định, glyph Deck đúng mọi màn, chữ đọc được ở 1280×800, bàn phím ảo khi cần nhập, không launcher, 60 fps kịch bản MS01b, suspend/resume không mất run.
1311. Lớp input (Bladewake.Input)
- 1 asset
BW_Input.inputactions(Unity Input System 1.19), 3 mapGameplay·UI·Hub(doc 01 §5.2); bố cục nút khoá Bible §1.1 (Đánh X/LMB · ĐÁP-chạm/verb-giữ RB/RMB · Lướt A/Space · Kỹ năng LB·LT·RT / Q·E·F · Ult Y/R · Bình d-pad xuống/1 · Xem Tro Nhớ View/Tab · Bản đồ-tạm dừng d-pad lên·Menu / M·Esc · Tương tác B/G). InputService.Poll()mỗi frame →InputFrame(struct):move(vec2),aim(vec2 sàn, 360° float), cờpressed/held/released+ tick dấu của 11 hành động,device(KBM/PAD/DECK),aimMode. Sim chỉ đọcInputFrame; Autopilot + test bơmInputFrame(không thiết bị).- Chạm/giữ RB/RMB: phân loại trong
InputServicetheo ngưỡng tuning (doc 12 §1 sở hữu số); chạm = ĐÁP, giữ = verb giữ riêng hero (INV-11). Buffer 9 tick ởHeroBase(INV-07), không ở input. - Ngắm: tay cầm RS ≥ 0.30 → RS, không thì LS ≥ 0.20, không thì giữ (doc 10 §1.6); chuột → điểm sàn
x = mouseX + camX,z = mouseY / 0.5 + camZ. Trợ ngắm theoaim_assist_cone_deg/aim_assist_strength(PlatformTuning). - Steam Input: mặc định chế độ giả lập tay cầm chuẩn (Input System đọc thiết bị ảo); xuất bản cấu hình Steam Input mặc định (mẫu "Gamepad" cho Deck; nút lưng Deck = L4
POTION· R4ULT· L5ASH_INSPECT· R5INTERACT— bản sao của nút chính, không phải ô remap, doc 41 §8.4 / doc 43 §8.1; gyro tắt; trackpad phải = chuột chỉ trong menu). Khi Steam Input bật:GetInputTypeForHandlechọn bộ glyph; không bật: theo layout thiết bị của Input System. - Glyph 5 bộ: bàn phím/chuột · ABXY · ký hiệu hình học · Deck · chạm (mobile, cổng port). Đổi sống theo thiết bị cuối (
GlyphService.OnDeviceChanged), HUD/thẻ đọc glyph qua thẻ<glyph=dash>. - Remap toàn bộ (
PerformInteractiveRebinding), lưu trongsave/settings_shared.json(§9.1) dưới khoábindings.<family>.<map>.<action_id>[0..1](doc 41 §8.1; 2 ô/action/họ thiết bị; theo người, đi Cloud); xung đột → hoán đổi; nút Menu/Esc không remap. Preset 1 tay + giữ/bật-tắt verb giữ ở Trợ năng. - CI grep cấm
Keyboard.current|Gamepad.current|Mouse.current|Touchscreen.currentngoàiAssets/_Project/Bladewake/Input/.
1412. Đường bản địa hoá (Bladewake.Loc)
| Khoá | Luật |
|---|---|
| Nguồn | mọi field _vi trong JSON → loc_source.csv (key = <file>.<id>.<field>, cột vi, context, max_len, params) do build_gdd.py; chuỗi UI ngoài GDD → Assets/_Project/Localization/ui_strings.csv (cùng định dạng, khoá ui.<màn>.<slug>) |
| Ngôn ngữ | MS02: EN, VI · EA: EN VI zh-CN JA DE · 1.0: 10 (5 ngôn ngữ thêm chốt ở cổng loc 1.0) |
| Dịch | chỉ dịch MẪU có {param}, không dịch câu đã thay số (doc 51 §3); bảng dịch loc_<lang>.csv (key, text) → Addressables loc_<lang> |
| Runtime | LocService.Get(key) → mẫu ngôn ngữ hiện tại → thiếu → EN → VI; TemplateFormatter thay {param} (+ placeholder toàn cục doc 51 §3), số theo văn hoá ngôn ngữ: % nguyên, giây ≤ 2 chữ số thập phân |
| Font | Latin + tiếng Việt: font pixel phủ đủ dấu tiếng Việt (OFL, kiểm glyph tự động) · CJK: font pixel glyph ≥ 12 px, canvas UI riêng (Bible §10.3) · TMP font asset tĩnh cho Latin/VN, động cho CJK; chuỗi fallback theo ngôn ngữ |
| Kiểm | pseudo-loc xx-PS (+40% độ dài + ký tự có dấu chồng, doc 40 §8 / doc 43 §12.2) chạy Autopilot chụp mọi màn; CI báo khoá thiếu/thừa, {param} lệch giữa ngôn ngữ, vượt max_len |
| Đóng băng | 4 tuần trước mỗi MS (Bible §10.3); chuỗi mới sau đó → bản vá |
- Cấm chuỗi hiển thị cứng trong code (CI grep chuỗi có dấu tiếng Việt trong
*.csngoàiDev/).
1513. Runtime VFX & âm thanh
- VFX (
FxRuntime): idVFX_<owner>_<slug>→ sheet Addressables + specav/*.json(khung, fps 30, pivot, layer,size_class, phases). Phát bằng instanced sprite (cùng atlas/materialSpriteAdd/SpriteActor), không ParticleSystem cho FX gameplay (doc 31 §6.1). Tint:TELLFIXED (#ffd84a/#b14cff),HERO= hàng LUT slot hero. Nâng hạng tất định: bậc sheet{1.0, 1.3, 1.7, 2.2},k = argmin |ln(t/s_k)|, seedfnv1a32(VFX id) mod 2^16(doc 31 §8). Trần + thứ tự cướp doc 31 §6.1; LOD §4.5. - Âm (doc 32 §12.1):
AudioDb(từaudio_sfx.csv+audio_music.json) ·SfxPlayerpool 48 ·HordeAudiogộp hit/kill mỗi frame sau Combat ·TellAudiolịchPlayScheduled(tell không bao giờ bị cắt) ·MusicDirectorcường độImỗi 0.25 s, stem lượng tử ô nhịp, 12 AudioSource xen kẽdspTime· mixerBW_Main.mixer17 group + 9 snapshot (doc 32 §5.1–5.2). Pitch World clockmax(0.75, 0.5 + 0.5 × scale). Real Voices 64 / Virtual 256, DSP buffer 256 mẫu. - Gameplay chỉ bắn sự kiện (
SimEventBuscho hit/kill/spawn/tell tần suất cao;EasyEventManager+EventNamecho sự kiện thưa); không gọi AudioSource/FX trực tiếp từ sim.
1614. UI (Bladewake.UI)
- uGUI + TextMeshPro; màn hình qua
UIManager(khôngSetActive), controller kế thừaFeatureBaseController; DOTween UISetUpdate(true), xử lýKill/OnComplete(rule third-party). - Màn = enum
UiScreendoc 40 §2 (#1–#40 + phần thêm #41–#46:PhotoWarning,QuickSetup,ChoosePath,HubPause,SaveConflict,SaveNotice); mỗi dòng = 1 prefabUI_<Name>.prefab+ 1 controller<Name>Controller : FeatureBaseController. Chọn hero = tab Kẻ Thức củaHearth; Khăn Tang =TestBell; Codex = tab củaLedger; recap chết =DeathRecap(INV-16, từDamageLog). Doc này không giữ danh sách màn riêng. - HUD canvas riêng neo vùng an toàn gameplay 427×267 (Bible §10.4);
ui_text_scale_mintheo nền; số sát thương ở lớpBattleDmgText(1 mesh, doc 10 §6.5) không thuộc uGUI. - Điều hướng tay cầm mọi màn (không màn nào cần chuột); focus mặc định khai báo trong controller; glyph qua
GlyphService. - HUD cập nhật theo cờ dirty (máu, Dư Âm, hồi chiêu = 10 Hz; bộ đếm mục tiêu = khi đổi), không rebuild layout mỗi frame.
1715. Telemetry & báo crash (Bladewake.Telemetry)
Nguồn luật: danh mục sự kiện + vỏ chung = doc 60 §3 (tên canon); đồng ý, quyền riêng tư, crash = doc 43 §15. Doc này chỉ đặt cách cài đặt.
| Sự kiện (tên canon doc 60 §3.3) | Trường chính | Nguồn luật |
|---|---|---|
session_start · session_end · consent_changed · settings_changed | build_number, content_version, nhánh, ngôn ngữ, is_deck, bậc CPU/GPU/RAM, lựa chọn đồng ý | doc 43 §15.2, doc 60 §3.3 |
run_start · run_state · run_end | run_id, mode, seed64/daily_key, hero, Dạng Rèn, ult, Độ Tang, MRN, MUT, kết quả, thời lượng, build cuối | doc 16 §9.3, doc 17 §11.1 |
room_start · room_end | act, node, objective, thời lượng, sát thương nhận/gây, kills/phút, max alive | doc 15 §14, doc 17 §11.1 |
draft_offer · draft_pick · shop_purchase · event_choice · rest_choice | thẻ hiện/chọn, món mua, giá | doc 13 §16.2, doc 16, doc 17 §11.1 |
hero_died | last5 (tên đòn, Y/P/N, giây, % sát thương, cv_id) + killing_blow + top_source + one_shot | INV-16, doc 14 §15, doc 17 §11.1 |
ash_room · ash_cv_on/off · ash_budget_block · ash_spawn_shift · ash_hit · ash_fight · ash_death · ash_trick · ash_settings | doc 14 §15 | doc 14 §15 |
perf_tier · perf_room | bậc, alive, fx_active, p50/p95/p99 frame, voice thật, RAM, GC byte | doc 15 §5.3, §4.5 |
crash · bug_report | chữ ký stack, log_tail 2 000 dòng, 30 sự kiện cuối | doc 43 §15.3, doc 60 §3.3 |
- Danh tính: mọi sự kiện mang
telemetry_id= UUID v4 ngẫu nhiên tạo lần chạy đầu, lưu trongsave/settings_shared.json(§9.1; "Tạo mã ẩn danh mới" ở Cài đặt › Quyền riêng tư sinh UUID mới). KHÔNG BAO GIỜ gửisteamId, băm củasteamId, tên tài khoản, IP lưu lâu, đường dẫn thư mục người dùng, tên máy (doc 43 §15.1–15.2). - Ghi local: NDJSON
local/telemetry_queue/<run_id>.ndjson(mọi build; không rời máy nên không cần đồng ý) → bản dev/QA đọc bằngbalance_calc.py. - Gửi xa: chỉ khi người chơi chọn "Đồng ý" ở màn
Consent(doc 43 §15.1: mặc định CHƯA chọn, 2 nút ngang nhau) hoặc buildPlaytest(kèm thông báo). Gửi theo lô ở hub / màn kết / thoát, không trong phòng chiến; gzip ≤ 50 KB/run (doc 43 §15.2, doc 60 Q3); hàng chờlocal/telemetry_queue/≤ 5 MB, đầy → bỏ lô cũ nhất; endpoint HTTPS của studio (cùng hạ tầng máy chủ điểm §6.3, khác endpoint). - Crash (IL2CPP, gồm crash native):
- Bộ bắt crash của engine ghi minidump +
Player.logvào thư mục crash theo dự án của engine (%LOCALAPPDATA%\Temp\<companyName>\<productName>\Crashes\);Application.logMessageReceivedThreadedghi thêmlocal/logs/(vòng 2 000 dòng) + vòng 30 sự kiện run cuối vàolocal/logs/last_events.ndjson(ghi đè mỗi 5 s, không trong vòng lặp frame). - Lần mở kế:
CrashCollector(trước mànTitle) tìm thư mục crash mới hơnlocal/crash/last_seen→ chép minidump + 2 000 dòng log cuối +build_number+content_version+content_hash+ preset + 30 sự kiện cuối vàolocal/crash/<utc>/; xoá đường dẫn người dùng (C:\Users\<tên>→<USER>) và tên máy khỏi log trước khi lưu. - Theo lựa chọn crash doc 43 §15.1: "Luôn gửi" → gửi ngay; "Hỏi mỗi lần" → hộp thoại "Game đã gặp lỗi lần trước. Gửi báo lỗi?" (Gửi / Không); "Không bao giờ" → không gửi.
- Gửi:
POST /v1/crash(multipart:meta.json+log.txt+dump.dmp, nén ≤ 2 MB; vượt → bỏ dump, giữ log). Chưa gửi được → giữlocal/crash/7 ngày rồi xoá. Máy chủ giữ 90 ngày, gộp theo chữ ký stack. - Symbol IL2CPP (
GameAssembly.pdb+il2cppline mapping) lưu nội bộ theobuild_number(doc 43 §11.1, không trong depot) để giải dump.
- Bộ bắt crash của engine ghi minidump +
- Demo
Telemetry.cs(CSVbladewake_runs.csv) giữ ở buildBW_DEVtới MS02 rồi bỏ.
1816. Build pipeline & nhánh
16.1 Nhánh git (remote origin, nhánh gốc main)
| Nhánh | Việc | Luật |
|---|---|---|
main | trunk, luôn build xanh | merge qua review + CI §16.3; không push thẳng |
feat/<id> · fix/<id> | việc ngắn ≤ 5 ngày | rebase lên main; nhánh agent theo skill run-backlog |
release/<ms> (vd release/ms02-demo, release/ms03-ea) | cắt ở ngày đóng băng loc (4 tuần trước MS) | chỉ sửa lỗi; mỗi sửa cherry-pick ngược về main |
hotfix/<ver> | vá sau phát hành | từ tag v<x.y.z>; merge vào release/* + main |
- Tag:
v<MAJOR.MINOR.PATCH>+<build_number>(vdv0.1.0+1842);build_number= số CI tăng đơn điệu toàn dự án (doc 43 §11.3), in ở góc màn title, watermark buildqa/rc/dev_leaderboard, mọi telemetry + crash + leaderboarddetails[10]. - Phiên bản (doc 43 §11.3): chuỗi hiển thị
MAJOR.MINOR.PATCH (build_number); demo0.9.x· EA MS030.1.0· U10.2.0· U20.3.0· U30.4.0· 1.01.0.0· hero hậu 1.01.1.0,1.2.0… · vá nóng tăng PATCH.PlayerSettings.bundleVersion=MAJOR.MINOR.PATCH;content_version(int) tách riêng (§6.2). - Git LFS:
*.png,*.wav,*.ogg,*.psd,Audio_Src/**(doc 32 §11.1). Dữ liệu JSON/CSV không LFS.
16.2 Cấu hình build (Build Profiles Unity 6)
| Profile | Define | Nội dung | Đích Steam |
|---|---|---|---|
Dev | BW_DEV BW_STEAM | đủ + overlay/console/cheat/Autopilot, hot reload, Development Build, nộp điểm vào DEV_* | nhánh qa (mật khẩu, mỗi đêm, doc 43 §11.2) |
Playtest | BW_STEAM BW_TELEMETRY_REMOTE | ContentManifest playtest, telemetry bật kèm thông báo | AppID playtest |
Demo | BW_STEAM BW_DEMO | MS02: H01 H02 H07, Hồi I, STANDARD, EN + VI | AppID demo |
Release | BW_STEAM | ContentManifest của MS hiện tại | rc → beta (7–14 ngày trước bản lớn) → default (qua checklist doc 43 §11.4) |
Release + BW_RC_WATERMARK | BW_STEAM BW_RC_WATERMARK | như Release + watermark góc "build_number + nhánh"; không cheat | nhánh rc (mật khẩu) |
Release + BW_DEV_LB | BW_STEAM BW_DEV_LB | như Release + watermark; nộp điểm vào bảng DEV_* (doc 43 §6.2) | nhánh dev_leaderboard (mật khẩu) |
- Chung: Windows x64, IL2CPP, .NET Standard 2.1, Managed Stripping Medium (
link.xmlchoHookTable), Burst AOT, Addressables build trong pipeline, giữ PDB/symbol nội bộ cho crash.
16.3 Chuỗi CI (máy build Windows tự host + 1 Steam Deck nối mạng)
mỗi PR: validate.py --all (0 ERROR) + từ cấm → build_gdd.py → so lệch render
→ credits_check: audio_sfx.csv × sfx-credits.json × licenses/ → Assets/_Project/Data/credits.json; thiếu dòng CC BY → FAIL (doc 40 §5.7.1, AT-08)
→ Sync + Bake (batchmode)
→ biên dịch → EditMode test → PlayMode smoke (3 phút Autopilot H01) → kiểm asmdef/grep cấm §2.2
mỗi đêm: + build Dev + Playtest → Autopilot 12 hero × 1 run sim-only → báo cân bằng (CSV) → SteamPipe nhánh qa
hằng tuần: + Deck: Autopilot + F10 (+150 quái) 10 phút, MS01b scene → p95 lùi > 10% → chặn merge (doc 01 §5.3)
release: + build Release/Demo → test di trú lưu mọi schema đã ship → smoke Deck tay → SteamPipe nhánh `rc` → (checklist doc 43 §11.4) `beta`/`default`
- Lệnh build:
Unity -batchmode -quit -projectPath . -executeMethod Bladewake.Editor.BuildPipeline.Run -profile <Dev|Playtest|Demo|Release> [-define BW_RC_WATERMARK|BW_DEV_LB] -buildNumber <n>; upload:steamcmd +login <builder> +run_app_build app_build_<appid>.vdf(depot Windows x64). - Build report CI: kích thước cài đặt ≤ 4 GB, không assembly mobile (quảng cáo/IAP/analytics di động), không
Bladewake.Devở Release/Demo.
1917. Công cụ debug & cheat (chỉ BW_DEV)
| Phím | Việc (giữ từ demo) | Phím | Việc |
|---|---|---|---|
| F1 | bật/tắt Autopilot | F6 | +1 cấp (mở draft) |
| F3 | overlay: trang AI · BRAIN · PSP · PERF · DIRECTOR · HITBOX | F7 | sinh Tinh Anh roster Hồi |
| F4 | bất tử | F8 | vào boss Hồi |
| F5 | hot reload dữ liệu (§8.2) | F9 | World ×0.25 |
| F10 | +150 quái (stress) | ~ | console |
- Lệnh console:
run.seed <hex>·run.goto <act> <row>·hero <Hxx>·give.edge <id> [rank]·give.relic <RLxxx>·give.cur <CUR_*> <n>·give.skill <Hxx_SKnn> [rank]·spawn <EN_/EL_/MB_/BS_id> [n]·mrn.set <MRNnn> <rank>·mut.set <MUTnn>·ash.set <axis> <v>·ash.force <cv_id>·ash.level <0-3>·ash.ledger.fake <boss> <n>·ash.ledger.clear·ash.trace on·ai.tokens <T> <n>·dir.budget <x>·perf.tier <0-3>·vfx.lod <0-3>·hitbox.show·save.dump·save.corrupt.test·loc.lang <code>·loc.pseudo·time.scale <x>·dev.reload --room. - Overlay HITBOX vẽ hình sàn hitbox/hurtbox 1 px (QA khớp VFX ↔ hitbox, doc 31 §7). PERF: p50/p95 frame, ms từng hệ (ProfilerMarker), alive, voice, hạt, sheet, draw call, GC/frame.
- Cheat ghi cờ
cheated = truevàorun_active→ không thành tựu, không leaderboard, không ghi Sổ Thù.
2018. Test tự động & Autopilot
18.1 Test (Unity Test Framework)
| Bộ | Kiểm | Ví dụ test vàng |
|---|---|---|
EditMode: TickMath | ceil/round đổi tick | 0.14 s → 9 tick; 0.12 s glint → 7 |
EditMode: DamagePipeline | doc 10 §3.6 ví dụ tính | số khớp bảng doc 10 |
EditMode: HitShapes | 6 shape Minkowski + quét | điểm biên ± 0.01 px |
EditMode: RngStreams | PCG32/FNV/SplitMix vector cố định; chuỗi seed DAILY | 1.000 số đầu của seed 0x5A17C3E1 khớp file vàng; DailySeed("2028-01-15", content_version 7) → "BW:DAILY:2028-01-15:7" → FNV1a64 0xb3dfbbdb7003a1cf → seed64 0xa11f150d17864318 (cùng giá trị ở test máy chủ điểm) |
EditMode: MapGen | doc 16 §2.5 hợp lệ 10.000 seed; doc 16 §2.7 | bản đồ Hồi I seed 0x5A17C3E1 = ví dụ doc 16 |
EditMode: AshMemory | EMA, ngưỡng hiệu lực, MACRO | §7.2 → 0.72; H07 A02 on = 1.05 → không bật (doc 14 §7.3) |
EditMode: Save | nguyên tử, .bak, khối đầu (checksum), 7 luật xung đột §9.3, di trú mọi v<n>, chuyển 0/ → <steamId64>/ | fixture hỏng → nạp .bak; save_seq 380 < marker 410 → SaveConflict |
EditMode: Data | mọi CSV nạp, mọi id giải, AllPrimitivesHaveHandler, mẫu {param} | 0 lỗi |
EditMode: Loc | khoá đủ, {param} khớp giữa ngôn ngữ | — |
| PlayMode: smoke | boot → hub → run → 1 phòng → chết → recap → hub | < 3 phút |
PlayMode: Invariants | INV-02/03/08/11/12/14/15 đo runtime (windup ≥ sàn, token ≤ trần, ĐÁP 360°, i-frame 12 tick, ấn spawn 0.6 s, dịch trọng số ≤ trần) | 0 vi phạm trong 10 phút Autopilot |
| Hiệu năng | GC.Alloc = 0 B/frame trạng thái ổn định; p95 theo §5.2 | Deck hằng tuần |
18.2 Autopilot (Bladewake.Dev.Autopilot, mở rộng bot demo)
- Chơi qua
InputFrametừThreat(không đọc sim nội bộ ngoài thứ người chơi thấy);BotProfilemỗi hero (12) × lối:DEFLECT,DASH,HOLD,AOE,RANGED,MASH→ kéo trục PSP để thử Tro Nhớ. - Chế độ:
showcase(quay clip marketing, render thật) ·balance(sim-only-batchmode -nographics, N run → CSV chobalance_calc.py, doc 17 §11) ·stress(F10, 250 quái) ·soak(2 giờ, RAM tăng ≤ 50 MB) ·daily(30 ngày seed → CRC nội dung, so giữa Windows và Deck) ·loc(pseudo-loc, chụp mọi màn). - Tốc sim-only: ≥ 20× thời gian thực trên máy CI [ASSUMED] (1 run 32 phút ≈ 1.5 phút).
2119. Rủi ro kỹ thuật
| # | Rủi ro | Xác suất · tác động | Giảm thiểu | Cổng |
|---|---|---|---|---|
| R1 | Deck trượt 10 ms CPU ở 250 quái | TB · cao | SoA + Burst từ P0; MS01b đo sớm; dự phòng preset 180 (Bible §10.4) | MS01b M3 |
| R2 | Hệ hiệu ứng 186 primitive phình code/lỗi tương tác | cao · cao | từ vựng đóng + test handler + trần trigger; CUSTOM_HOOK có template | P0 |
| R3 | Chuyển URP làm lệch shader/stencil/x-ray | TB · TB | chuyển ở P0 trước nội dung; ảnh vàng so pixel 8 hướng | P0 |
| R4 | Hỏng lưu / xung đột Cloud | thấp · cao | ghi nguyên tử + .bak + crc + save_seq + test di trú | MS02 |
| R5 | Nội dung DAILY lệch giữa máy | TB · TB | chỉ số nguyên trong sinh nội dung, content_version trong seed, test CRC chéo máy | MS03 |
| R6 | Font pixel thiếu dấu tiếng Việt / CJK | cao · TB | chọn font OFL phủ VN ở P0 (font demo chỉ Latin cơ bản — cần kiểm); CJK font riêng ≥ 12 px | MS02 / MS03 |
| R7 | Glyph sai khi Steam Input bật trên Deck | TB · TB | GetInputTypeForHandle; test tay trên Deck mỗi MS | MS03 |
| R8 | Tro Nhớ bị cổng pháp lý chặn | TB · cao | cờ adaptive.legal_gate đổi bằng dữ liệu → chỉ-thoại | trước MS03 |
| R9 | Bộ nhớ asset 12 hero × 8 trang | TB · TB | RG8 + LUT, Addressables nạp theo hero/biome, ≤ 21 trang thường trú | MS01b |
| R10 | Hot reload làm lệch trạng thái run | TB · thấp | áp ở ranh giới tick; cấu trúc = khởi động lại phòng | P0 |
| R11 | GC giật trong bầy | TB · cao | 0 B/frame là cổng CI; pool pre-warm | MS01b |
| R12 | Nâng Unity giữa dự án | thấp · cao | khoá 6000.3 LTS; nâng = DEC- + sprint đo Deck | — |
| R13 | Gian lận leaderboard | TB · TB | mọi bảng Trusted + máy chủ 6 kiểm (a)–(f) doc 43 §6.3 + seedcap CI + duyệt top 50 | MS03 |
| R14 | Quyền riêng tư telemetry | thấp · cao | đồng ý rõ (doc 43 §15.1), telemetry_id UUID ngẫu nhiên (không suy từ steamId), xoá đường dẫn/tên máy khỏi log crash | MS02 |
| R15 | 2 trần voice lệch (doc 15 §5.1 ghi 24, doc 32 §5.3 ghi 64 Real) | — · thấp | theo doc 32 (chủ mixer): 64 Real, pool 48 one-shot; doc 15 cần sửa dòng SFX | đã ghi Quyết định |
22Quy tắc cho dev/AI
- Số/ID chỉ từ dữ liệu (
Db,TuningKeys.g.cs,IdRegistry); không hard-code id quái/đòn/hero/Edge trong code (thay"thrall","BS01_B1",CODE="varek"của demo). Thêm cơ chế = sửamechanics.json+ doc 12 §9–§10 trước, rồi handler. - Sim (
Core…Run,Sim) không tham chiếuPresentation/Audio/UI/Platform/Telemetry/Dev; sim → hiển thị quaSimEventBus; UI → sim quaICommandở ranh giới tick. - 1 tick = 1/60 s; bộ đếm Q16 theo đồng hồ; tối đa 4 tick bù/frame; đổi giây → tick lúc nạp (ceil cho cửa sổ có lợi người chơi). Không
Time.deltaTimetrong sim. - RNG: chỉ
RngStreams(10 luồng PCG32);fxlà luồng duy nhất tự do; cấmUnityEngine.Random/System.Random; sinh nội dung chỉ số nguyên. - Async = UniTask (không Coroutine, không
async void); thời gian =TimeManager(khôngDateTime.Now); màn hình =UIManager(khôngSetActive); cấu hình chỉ đọc quaDataManager; người chơi quaDataPlayer/PlayerDataManager; khôngSave()trong Update; mặc định ởSetupDefaultData(); sự kiện thưa =EasyEventManager+EventName; DOTween UISetUpdate(true)+Kill. - Hot path (sim + vẽ + âm trong giao tranh): 0 B GC/frame; cache
GetComponent/Findở Awake;StringBuildercho chuỗi động. - Không bao giờ hạ dưới tải: tell Y/P/N (glint, vùng, SFX), va chạm, sát thương, token, vòng chân, x-ray, ấn spawn, vùng đích đạn P (doc 10 §10, doc 15 §5.3, doc 31 §6.2).
- Trần token/INV không đổi bởi Khăn Tang/Ấn/mutator/Tro Nhớ; brain chỉ nhận
HeroSnapshot≥ 0.25 s tuổi; PSP chỉ quaAshMemory.Read()≥ 0.5 s; mọi ngưỡng quaAshMemory.Threshold(). - Lưu: ghi nguyên tử
.tmp→ fsync → replace +.bak; mỗi file cóschema_version; mỗi bản ship thêm fixture + test di trú; lưu bằng ID chuỗi, không handle. - Input chỉ trong
Bladewake.Input; sim đọcInputFrame; mọi màn điều hướng được bằng tay cầm. - Chuỗi hiển thị chỉ qua
LocService(khoá<file>.<id>.<field>hoặcui.*); không chuỗi cứng trong.cs. - Mỗi PR:
validate.py --all0 ERROR, render khớp, test EditMode xanh, grep cấm sạch; sửa.cs→ kiểm biên dịch qua Unity MCP (rule compile-validation). - Không tên game/studio/nhà phát hành/thương hiệu/nhân vật có thật trong code, dữ liệu, chuỗi, tên asset.
23Quyết định đã chốt
| Q | Quyết định | Lý do |
|---|---|---|
| 2026-11-07 — ECS? | Lai hướng dữ liệu: SoA NativeArray + Burst/Jobs cho quái S, đạn, vùng, trạng thái, hash, flow field; vẽ instanced; M/L/XL + hero GameObject; không package Entities | Bible §10.4 khoá Burst/Jobs; 250 nhỏ với Burst; đội 5 dev; giữ công cụ demo |
| 2026-11-07 — Render pipeline? | Chuyển built-in RP → URP Universal Renderer ở P0 (trước MS01b), 6 shader viết lại unlit; RT 480×270/427×267 Point, upscale nguyên | built-in RP ở chế độ bảo trì; port console M36+ cần SRP; chuyển trước nội dung rẻ nhất |
| 2026-11-07 — Sort bầy? | alpha-test + ZWrite, depth = z chân; FX cộng sort CPU | pixel art alpha 0/1 → gom lô theo trang, không sort 250 sprite |
| 2026-11-07 — Texture? | trang 2048² màu chỉ mục RG8 (chỉ số + mask điểm nhấn) + PaletteLut | INV-01d đã đòi LUT; 8 MB/trang thay 16 MB; ≤ 21 trang = 168 MB |
| 2026-11-07 — Bộ đếm slow-mo? | Q16 tick theo đồng hồ; chuyển động × scale | giữ số tick nguyên của dữ liệu mà slow-mo vẫn mượt |
| 2026-11-07 — Sự kiện tần suất cao? | SimEventBus struct ring buffer 0 cấp phát cho hit/kill/spawn/tell; EasyEventManager + EventName cho sự kiện thưa | tinh chỉnh doc 32 §12.1 ("sự kiện dùng EasyEventManager"): 250 quái × hit/frame không được cấp phát; luật "gameplay chỉ bắn sự kiện" giữ nguyên |
| 2026-11-07 — Hiển thị > 60 Hz? | khoá 60 fps (target_fps), không nội suy; vSyncCount = refresh/60 khi chia hết, không thì targetFrameRate 60 | Bible/PlatformTuning khoá 60; pixel art 1 tick = 1 frame |
| 2026-11-07 — Khoá RNG? | k = FNV1a64("BW:"+stream+":"+key), state = SplitMix64(seed64 XOR k), inc = SplitMix64(k) OR 1, PCG32; job dùng băm (state, slot, tick) | doc 16 §10.1 ghi PCG32(hash(seed64, stream, key)) chưa định nghĩa hash; cần độc lập worker + máy |
2026-11-07 — content_version? | int đơn điệu (doc 43 §11.3) → _unity/content_version.txt, chuỗi seed dùng số thập phân; content_hash = crc32 _merged.json 8 hex → _unity/content_hash.txt, chỉ cho kiểm (e) + cờ mod (thay bản chuỗi <MS>-<yyyymmdd>-<crc32> cũ) | khớp khối đầu save + details[9] int32 + doc 60 §3.2; client và máy chủ băm cùng chuỗi seed |
| 2026-11-07 — Nộp điểm? | theo doc 43 §6.2–§6.3: mọi bảng Trusted, client chỉ đọc; vé bw-score → máy chủ 6 kiểm → SetLeaderboardScore (KeepBest ENDLESS/BOSS_RUSH, ForceUpdate DAILY/WEEKLY); hàng chờ local/score_queue/, hạn 02:00 UTC ngày kế (doc 19 §5.2) (thay bản cũ: LB_<MODE>_<key>, client ghi KeepBest, 4 kiểm, hạn +24 h) | doc 43 là chủ Steam; 1 đường nộp cho mọi bảng |
| 2026-11-07 — Trần voice? | theo doc 32 §5.3: Real 64 / Virtual 256, pool 48 one-shot + 12 nhạc + 4 ambience | doc 15 §5.1 còn ghi "24 voice" (số demo); doc 32 là chủ mixer → cần sửa dòng SFX doc 15 |
| 2026-11-07 — Hồ sơ lưu? | theo doc 43 §7: gốc luôn <steamId64>/; save/ = meta_<steamId>.json + run_active.json + ash_profile_<steamId>.json + settings_shared.json + *.bak (Cloud); local/ không Cloud; khối đầu + 7 luật xung đột; không Steam → last_user.json hoặc 0/; companyName BladewakeDev khoá trước MS02 (thay bản profile.json cũ) | doc 21/40/41/42/43 đã theo bố cục này; không cần ô lưu |
| 2026-11-07 — Nền 1.0? | 1 build Windows x64 IL2CPP cho PC + Deck (lớp tương thích Steam); không build Linux gốc [ASSUMED] | Deck Verified đạt được với build Windows; giảm ma trận QA |
| 2026-11-07 — Gating nội dung theo mốc? | ContentManifest SO trong Unity theo build profile; dữ liệu GDD luôn đủ | không thêm file render ngoài doc 51 §8; lưu không vỡ khi id bị ẩn |
| 2026-11-07 — Telemetry xa? | đồng ý rõ (màn Consent, mặc định chưa chọn), bật ở playtest có thông báo; telemetry_id UUID ngẫu nhiên, không gửi steamId hay băm của nó; gzip ≤ 50 KB/run; tên sự kiện canon doc 60 (hero_died, shop_purchase) | doc 43 §15, doc 60 Q1/Q3; KPI doc 14/17 vẫn có từ playtest + người đồng ý |
| 2026-11-07 — Credits trong CI? | thêm bước credits_check vào chuỗi mỗi PR §16.3 ngay sau render: audio_sfx.csv × sfx-credits.json × licenses/ → Assets/_Project/Data/credits.json; thiếu dòng CC BY → FAIL | doc 40 §5.7.1 + AT-08 (doc 32 §14) + doc 60 bước 9b đã đòi bước build này; chuỗi CI ở đây thiếu |