← Deepstair GDD⚙️ Chương 13 · Kỹ thuật

13 — Technical Design

Kế thừa 00 (INV-01…16, bản đồ module §11, sổ ID §14), 01 (schema MoveDef/HitDef/RouteDef, DefenseResolver), 03, 06 (Echo DSL §8). Bảng số giữa marker tech_* sinh từ GDD/Tools/data/tech.py — sửa ở đó, không sửa tay. Kiểm tra module: py -I GDD/Tools/build_tables.py --module tech. Rule project áp dụng nguyên văn: UniTask (không Coroutine/async void), TimeManager (không DateTime.Now), UIManager (không SetActive cho UI), DataPlayer/PlayerDataManager, TigerForge EventId, Odin cho inspector, DOTween SetUpdate(true) cho UI, /compile-check sau mỗi thay đổi .cs, skill feature-cheat cho mọi feature có trạng thái.

22 mục 32 bảng ~48 phút đọc Nguồn: 13-Technical.md DERIVED công thức/mô phỏng · ASSUMED khởi điểm · BENCHMARK thị trường

1Nền tảng & package

1.1 Thông số khóa

Hạng mục Giá trị
Unity 6000.3.16f1 (ProjectSettings/ProjectVersion.txt)
Render URP 17.3 + 2D Renderer, Pixel Perfect Camera của URP (§2)
Scripting IL2CPP, ARM64, .NET Standard 2.1, stripping Low + link.xml
Android minSdk 26, targetSdk 36, AAB + Play Asset Delivery, Vulkan → GLES3.0
iOS 15.0+, Metal, iPhone + iPad (dọc)
FPS hiển thị 60 (mọi tier), 30 khi "Tiết kiệm pin" hoặc Adaptive Performance báo nhiệt; sim luôn 60 Hz (INV-14)
Backend Offline-first (INV-09). Supabase + Cloudflare Worker chỉ cho bảng xếp hạng v1.1 và BugHub; ghi luôn qua Worker (directWriteBanned)
Dịch vụ Firebase (Analytics, Crashlytics, Remote Config, Messaging), LevelPlay 9.5 (quảng cáo), Unity IAP 5.4 qua com.blackface.purchasing

1.2 Hiện trạng project → việc phải sửa

Hạng mục Hiện trạng project Deepstair cần Việc làm (task T03)
Render pipeline Built-in (GraphicsSettings.m_CustomRenderPipeline = 0) URP 17.3 + Renderer2D (13 §2) Thêm com.unity.render-pipelines.universal; tạo URP_High/Mid/Low.asset + Renderer2D_*.asset
Bundle id com.defaultcompany.deepstair (mọi platform) com.blackface.deepstair (00 §1) Sửa ProjectSettings + mọi pack_id trong CSV IAP template (com.defaultcompany.deepstair.*)
Android minSdk 25 (7.1) 26 (8.0, 00 §1) Player Settings → Minimum API Level 26
Scripting backend IL2CPP (Android) IL2CPP Android + iOS Managed stripping Low (giữ) + link.xml (13 §8.6)
Graphics API Android Vulkan + GLES3 (Automatic) Vulkan, fallback GLES3.0 Bỏ Automatic, thứ tự Vulkan → GLES3
Color space Gamma Gamma (giữ — 13 §2.5) —
Aspect Android min 1.0 / max 2.1 max 2.4 (màn 20:9, 21:9) androidMaxAspectRatio 2.4
Gói không dùng Spine (Plugins/), Cinemachine 2.10, VisualScripting Không dùng trong Gameplay Giữ trong project (template), cấm reference từ 3.GAMEPLAY; gỡ Spine ở M4 nếu không feature nào dùng

1.3 Package

Quyết định Package Ghi chú
Dùng UniTask, TigerForge, Odin, DOTween Pro (Plugins) DOTween/TigerForge chỉ ở 2.BUS (UI, ranh giới); Sim cấm cả hai
Dùng blackface.gamekernel 0.1.1 EffectRegistry.Configure đăng ký id 1001–1007 (01 §13) cho icon/meta; Sim dùng bảng riêng cùng id
Dùng b-uimanager, b-poolingmanager, progressthread, timemanager, csvreaders, security, localization, firebase, purchasing, ads, buglogger, qa-probe, updatemanager, mobilenotifications 2.BUS/4.CORE theo pattern template
Dùng Addressables 2.9 + addressables.android, feature.2d (tilemap, sprite) Group §16.6; PixelPerfectCamera bản URP thay bản 2d.pixel-perfect
Thêm com.unity.render-pipelines.universal 17.3.x §2
Không dùng blackface.combat 0.1.1 Engine Survivor/Spine, Update-driven, Burst — không tất định theo tick, không có phòng thủ theo khung
Không dùng blackface.rpgstats 0.0.3 Reflection + Dictionary (cấp phát, rủi ro strip). Giữ đúng ngữ nghĩa 4 lớp (01 §3.2) trong StatSheet mảng phẳng
Cấm trong Gameplay Cinemachine, Spine, VisualScripting Camera pixel tự viết (§2.2)

2Render pipeline & Pixel Perfect

2.1 Quyết định: URP 2D Renderer

Project đang ở Built-in. Chọn URP + Renderer2D vì: (1) 00 §1 đã khóa; (2) Built-in bị đóng băng trong vòng đời Unity 6 — Light 2D, Sprite-Lit, Render Graph, SRP Batcher chỉ có ở URP; (3) Light 2D cho ánh lửa/đèn hồn trên nền tối (00 D1) mà không vẽ tay biến thể sáng; (4) PixelPerfectCamera URP có Upscale Render Texture: thế giới vẽ vào RT 270×H rồi phóng lên → fill-rate ~1/16 so với 1080×1920, đèn 2D và VFX cộng rẻ cả trên máy Low.

2.2 PixelViewport — tỉ lệ nguyên ưu tiên, thế giới rộng 270–384 px

Chạy lúc khởi động và khi đổi độ phân giải (Screen.width/height). Quy tắc thống nhất với 02 §2 và 11 §3:

k = min(⌊W/270⌋, ⌊H/480⌋)
if k >= 3 and W/k <= 384:                // chế độ NGUYÊN (đa số điện thoại, iPad)
    rtW = ⌊W/k⌋ ; rtH = ⌊H/k⌋ ; filter = Point
else:                                    // chế độ LẺ (màn < 810 px ngang, hoặc quá rộng)
    kf  = min(W/270, H/480)
    rtW = min(384, round(W/kf)) ; rtH = round(H/kf) ; filter = RetroAA (sharp-bilinear)
if W/(scale) > 384: pillarbox phần vượt 384 px thế giới (FrameCam vẽ khung Địa Tầng)
ppc.assetsPPU = 16 ; ppc.refResolution = (rtW, rtH)
ppc.gridSnapping = UpscaleRenderTexture ; ppc.cropFrame = None

Ví dụ: 1080×1920 → k 4 → 270×480 · 1080×2400 → 270×600 · 1440×3200 → k 5 → 288×640 · iPad 1536×2048 → k 4 → 384×512 · 720×1280 → lẻ 2.667 → 270×480.

  • Chiều cao dư (rtH − 480) mở rộng vùng trần/khung dưới (01 §2), sàn luôn ở 66% vùng chiến đấu.
  • Zoom 2× (Tuyệt Sát, Tuyệt Kỹ — 01 §2): đặt refResolution = (rtW/2, rtH/2) và tâm camera tại điểm va trong cùng tick khung va; trả về sau slow-mo. Không bao giờ zoom lẻ.
  • Rung camera: offset tính bằng px nguyên (01 §14.2), cộng sau khi snap → không rung nửa pixel.
  • Safe area chỉ áp cho HUD; thế giới vẽ tràn màn hình.

2.3 Camera & UI

Lớp Render Độ phân giải
WorldCam (base, Renderer2D, PixelPerfectCamera) Nền, sân, actor, VFX, World UI (số sát thương, thanh HP/Thế trên đầu, chữ "GẠT!") RT rtW×rtH (270–384 × 480–640)
FrameCam (base, depth −1) Khung bên khi thế giới vượt 384 px (pillarbox) native
uGUI Screen Space – Overlay (UIManager) HUD, EchoPick, menu meta native, CanvasScaler 1080×1920, UI PPU 25

Không stack overlay camera lên WorldCam (Upscale RT của PPC không hỗ trợ stack ổn định). Nếu prefab UIManager template đang dùng Screen Space – Camera, task setup đổi sang Overlay.

2.4 Sorting layer

Background (parallax xa) → Stage (tilemap sàn/tường/cầu thang) → Props → Decals (vết máu/tro, trần theo tier) → Actors → FX (cộng) → WorldUI → Overlay (letterbox, nháy toàn màn). Trong Actors: order = 1000 − laneOffsetY×10 + actorId%10; actor đang ở khung va hoặc khung chạm phòng thủ được +200 trong 6 tick để pha đọc được (P1).

2.5 Ánh sáng 2D & color space

  • Blend style 0 Multiply (bóng tối Địa Tầng), 1 Additive (lửa, đèn hồn, phép). Không normal map, không shadow caster.
  • Mỗi phòng: 1 Global Light (cường độ từ ArtStrata.csv), đèn hồn gắn anh hùng (luôn bật), 2–4 đuốc tĩnh, đèn VFX lấy từ pool, trần theo tier (tech_tiers). Light texture scale 0.5 (Low 0.25, đèn động tắt — chỉ Global + sprite glow cộng).
  • Giữ Gamma: palette pixelfx và UI pixel kit đã chỉnh trong không gian sRGB; blend cộng ở Gamma khớp ảnh xem trước của pixelfx gallery (cái thấy khi duyệt act = cái thấy trong game).

2.6 Material & batching

  • Thế giới dùng Sprite-Lit-Default; VFX Sprite-Unlit cộng; World UI unlit.
  • Hiệu ứng trạng thái đổi sharedMaterial giữa vài material dùng chung (Mat_Lit, Mat_LitFlash trắng 1 khung, Mat_OutlineGold Dòng Chảy, Mat_OutlineWhite Vỡ Thế) + tint bằng vertex color — không MaterialPropertyBlock (giữ SRP Batcher/dynamic batching).
  • Atlas: 1 atlas/anh hùng, 1/Địa Tầng (địch), 1 Thủ Vệ+Boss, VFX chung + VFX Địa Tầng (§16.3). Import theo PixelArtImportRules.cs (point, không nén, không mipmap, PPU 16 thế giới / 25 UI).

3Assembly & ranh giới

3.1 Sơ đồ phụ thuộc

                   ┌──────────────────────────── 2.BUS (Features/) ── meta, UI, kinh tế, adapter Ports, TigerForge
                   │            │ refs ▼                      ▲ không bao giờ ngược lại
4.CORE (Core/) ◄───┘   3.GAMEPLAY (Gameplay/) ── driver, Pixel, Feel, View, Ports (interface)
   ▲ hạ tầng template          │ refs ▼
   │                   3.GAMEPLAY.Sim (Gameplay/Sim/) ── noEngineReferences: true — mô phỏng thuần C#
   │                           ▲
5.PLUGINS, packages    3.GAMEPLAY.Editor (importer, validator, codegen) · 3.GAMEPLAY.Tests.EditMode/PlayMode
Assembly References Cấm
3.GAMEPLAY.Sim không gì (chỉ BCL) UnityEngine (compile-time nhờ noEngineReferences), UniTask, TigerForge, static mutable
3.GAMEPLAY Sim, BlackFace.GameKernel, UniTask, URP Runtime, Addressables, Odin attributes 4.CORE, 2.BUS, DOTween, TigerForge
3.GAMEPLAY.Editor Gameplay, Sim, CsvReaders(.Editor), Odin Editor —
2.BUS + 3.GAMEPLAY, 3.GAMEPLAY.Sim —
2.BUS.Run.Editor (Features/Run/Editor/) 2.BUS, Gameplay, Sim AutoSim CLI dùng RunSetupService thật (§17)

Tách Sim khỏi engine là cách ép tất định bằng compiler: Mathf, Random, Time, Debug không tồn tại trong Sim; headless autosim và EditMode test chạy Sim mà không cần scene.

3.2 Ports

Sim không gọi ra ngoài: nó ghi vào hàng đợi đầu ra (sự kiện trình diễn, cue âm thanh, telemetry) và dừng ở trạng thái chờ khi cần quyết định (AwaitingEchoPick, AwaitingLanding, AwaitingRevive). RunDriver (3.GAMEPLAY) đọc hàng đợi mỗi frame và gọi các port; 2.BUS hiện thực port.

Port (3.GAMEPLAY) Hiện thực (2.BUS / 4.CORE) Hợp đồng
IRunSetupProvider RunSetupService (Features/Run/_Session) RunSetup Build(RunRequest req) — chỉ số meta 4 lớp đã gộp, anh hùng/cấp/sao/Thông Thạo/skin, nút Cây Đèn Hồn, bộ Di Vật, nhóm Hồi Âm chung đã mở, Bậc Kinh Hoàng, tầng bắt đầu, seed, mode, preset, tốc độ, cờ FTUE, RunTuning (RC)
IRunResultSink RunResultService void Commit(in RunResult r) — gọi đúng 1 lần ở RunEnd; trả Vàng/Tro Hồn/EXP/XP Thông Thạo/rương qua RewardManager, kỷ lục, first-kill, Sổ Ký Ức, nhiệm vụ, Pass, Heo Đất
IRunAnalytics FirebaseRunAnalytics (→ TrackingManager) Gọi có kiểu: RunStart, FloorClear, RouteUsage, DefenseStats, EchoOffer/Pick/Reroll, Execution, BossAttempt, Downed, Revive, RunEnd — struct tham số, adapter tự lấy mẫu (§14)
IRunAudio SoundManager adapter PlaySfx(SfxId id, float pitch, Vector2 px), SetMusic(StratumId s, MusicLayer layers), Duck(float s) — id int, không string
IRunLocalization GameSystems.Localize adapter string Get(int keyId), string Format(int keyId, int a) — key id nạp sẵn lúc vào run
IRunPrompt Features/Run/* UI (EchoPick, Landing, Downed, BossGate) UniTask<EchoChoice> PickEcho(EchoOfferView v, CancellationToken ct), PickLanding, AskRevive, ShowBossIntro — headless thay bằng AutoPick policy
IRunSnapshotStore SecureRunSnapshotStore (4.CORE save backend) UniTask Save(ReadOnlyMemory<byte> blob), bool TryLoad(out byte[] blob), void Clear() — ghi nguyên tử (§11.3)
IRunClock UnityRunClock / HeadlessRunClock Nguồn thời gian thực cho CombatClock (unscaledDeltaTime); headless trả bước cố định

Luật: (1) DTO qua port (RunSetup, RunResult, EchoOfferView) là struct/class bất biến của Sim, không chứa UnityEngine.Object; (2) IRunResultSink.Commit gọi đúng một lần, idempotent theo runId (app chết giữa commit → khôi phục snapshot phase = Ended và commit lại, sink bỏ qua runId đã nhận); (3) port không được ném exception vào driver — lỗi log BugLogger, run tiếp tục.

4Cây thư mục

4.1 Assets/_Project/Gameplay/

Gameplay/
├── 3.GAMEPLAY.asmdef
├── Sim/                     3.GAMEPLAY.Sim.asmdef (noEngineReferences)
│   ├── Core/      SimWorld, SimTick, Rng (Xorshift128Plus, SplitMix64, RngStreams), Hash64, RingBuffer, SimOutput, SimChecksum
│   ├── Data/      Generated/ (model + parser sinh từ header CSV), GameplayDb, SymbolTable, CombatTuning, DbValidator
│   ├── Run/       RunStateMachine, RunSetup, RunResult, RunSnapshot (+Writer/Reader/Migrations), RunRewardCalc, RevivePolicy
│   ├── Floor/     FloorDirector, WaveDirector, SpawnQueue, SpawnPoints, LandingRunner (LD00–LD08), VendorStock
│   ├── Actors/    ActorTable (struct-of-arrays), HeroState, EnemyState, ProjectileTable, SummonTable, StatSheet, ActorMotor
│   ├── Brains/    HeroBrain, TargetSelector, EnemyBrain + Behaviors/, BossBrain + PatternRunner, CombatDirector, AreaAvoid
│   ├── Combat/    DefenseResolver, ClashDetector, HitPipeline, HitboxQuery2D, DamageService, PostureService,
│   │              PoiseService, ReactionSelector, StatusService, EnemyDefense
│   ├── Combo/     MoveRunner, RouteRunner, ComboDirector, CondCompiler, ComboTree, SkillService, ExecutionService,
│   │              StyleService, OverdriveService, HeroGaugeService, ComboCounter
│   ├── Echoes/    EchoDatabase, EchoDslParser, CompiledEffect, Ops/ (§8.3), EffectExecutor, BuildState, RuleSet,
│   │              HeroRules/, TriggerTable, RunEventBus, HealCap, OfferGenerator, AutoPick
│   └── Telemetry/ RunStats (defense_stats, route_usage, execution), RunHash
├── Ports/         8 interface §3.2
├── Driver/        RunController (MonoBehaviour BattleScene), RunDriver, SimToViewBridge, RunAssetLoader
├── Pixel/         PixelClipPlayer, BodyClipLibrary, CombatClock, PixelViewport, ShakeService, TintService,
│                  AfterimagePool, DamageNumberPool, SpriteFont, VfxPlayer, LightRig, ActorView, ProjectileView
├── Feel/          FeelDirector, FeelSettings          View/  ArenaView, DescentView, TelegraphView, WorldUiView, DebugOverlay
├── Data/          Config/ (CSV §12), Clips/<HERO>/, Vfx/, Strata/<Sxx>/
├── Scenes/        BattleScene, CombatSandbox, PixelLab
├── Editor/        3.GAMEPLAY.Editor: BodyClipImporter, ActSyncValidator, GameplayCsvCodegen, EchoValidator,
│                  DeterminismGolden, DefenseCaseRunner, LinkXmlGenerator
└── Tests/         EditMode/, PlayMode/, Golden/

4.2 Feature mới trong Assets/_Project/Features/

Nhóm mới Meta/, Run/ (+ Run/_Session/: RunSetupService, RunResultService, RunSessionAdapter, FirebaseRunAnalytics) và feature mới trong Monetization/ — danh sách đúng bằng enum §13.3. Scaffold /new-feature, UI /new-ui + mockup. Dùng lại template: Shop, GloryPass (= Lantern Pass), PiggyBank, Vip30Day (= Thẻ Tháng), StarterPack, FirstPurchase, RemoveAds, DailyLogin, SevenDayLogin, Quest, LuckySpin, MailBox (Hộp thư G5/G9), FeatureUnlock, Tutorials, Settings, RewardPopup, Account.

5Mô phỏng tất định

5.1 Mô hình

SimWorld là đối tượng C# thuần chứa toàn bộ trạng thái trận (actor dạng struct-of-arrays, bảng đạn, RNG, build Hồi Âm, meter). Một tick = SimWorld.Step(). Không đối tượng Unity nào đọc/ghi trạng thái sim ngoài SimToViewBridge (chỉ đọc, sau tick). Nhiều SimWorld chạy song song được (autosim đa luồng) vì Sim không có static mutable.

5.2 CombatClock (Pixel/) — thời gian thực → tick

const float DT = 1f / 60f;
void Tick(float realDelta) {                       // gọi từ RunController.Update (UpdateManager)
    if (paused || awaitingInput) return;
    realDelta = min(realDelta, 0.10f);             // về từ background: không bù dồn
    float rate = speed * slowMo.Scale(realTime);   // speed 1/1.5/2/3; slow-mo đo theo GIÂY THỰC
    acc += realDelta * rate;
    int n = 0;
    while (acc >= DT && n < maxSteps) { world.Step(); acc -= DT; n++; }   // maxSteps = RC 8
    if (n == maxSteps) acc = 0;                    // máy quá yếu: sim chậm lại, không bao giờ nhảy cóc
    bridge.Present(acc / DT);                      // nội suy vị trí hiển thị rồi snap px nguyên
}
  • Hit-stop là cục bộ trong sim (freezeTicks của từng actor, 01 §14.1) → tất định, độc lập tốc độ.
  • Slow-mo toàn cục chỉ đổi nhịp bước (bao nhiêu tick mỗi giây thực), không đổi logic → không ảnh hưởng kết quả (INV-13/14). Ở x2/x3, rate = speed × scale (slow-mo tương đối với tốc độ hiện tại), thời lượng thực giữ nguyên.
  • Headless: không có CombatClock; driver gọi Step() liên tục.

5.3 Đơn vị & lượng tử tick

Thời gian trong Sim là số nguyên tick. Mọi khoảng thời gian dữ liệu (giây) đổi sang tick lúc nạp (round half-up). Bảng dưới kiểm tra các hằng số chính rơi đúng lưới tick:

Hằng số Giây Tick (×60) Tick dùng Lượng tử Ghi chú
DEFENSE_LEAD 0.150 9.00 9 chính xác commit = hit_tick − 9
CLASH_WINDOW 0.050 3.00 3 chính xác |Δhit| ≤ 3 tick
Cửa sổ hit-time ±3 tick (01 §5.2) 0.050 3.00 3 chính xác anh hùng 'đang ra đòn' quanh hit
DEFLECT_CHAIN_WINDOW 1.200 72.00 72 chính xác
COUNTER_ICD.deflect 0.800 48.00 48 chính xác
COUNTER_ICD.block 1.500 90.00 90 chính xác
COUNTER_ICD.evade 1.200 72.00 72 chính xác
DEFLECT_OFFBALANCE.light / heavy 0.350 21.00 21 chính xác heavy = 42 tick
GUARD_BREAK_STUN 0.900 54.00 54 chính xác
Nhịp chồng đòn địch (01 §11.1) 0.250 15.00 15 chính xác Director giãn hit-frame ≥ 15 tick
Mốc 12 fps của act 0.083 5.00 5 chính xác 1 khung sprite = 5 tick
Hit-stop lớp L (M/H/F/K/S/U/X = 3/5/7/6/3/12/15 tick) 0.033 1.98 2 chính xác
HITSTOP_CRIT_BONUS 0.017 1.02 1 chính xác

Tiến trình clip tấn công dùng fixed-point: progressMilli += aspdMilli mỗi tick (aspdMilli = round(AtkSpeed × 1000), tính lại khi chỉ số đổi); hit kích hoạt khi progressMilli ≥ hitTick × 1000. Không có float trong timing đòn → hit-frame không bao giờ trôi (INV-05). Đòn ngắn hơn 0.08 s sau AtkSpeed → hit dư âm (01 §6.6).

5.4 Thứ tự cập nhật trong một tick

tick++ → WaveDirector (spawn, actorId tăng dần) → CombatDirector (token mỗi 30 tick, giãn hit-frame lính ≥ 15 tick) → Brains (anh hùng trước, rồi địch theo actorId) → MoveRunner (bỏ qua actor freezeTicks > 0; phát AttackCommit/HitFrame/CancelWindow/ClipEnd) → DefenseResolver theo (hitTick, actorId, hitIndex) → ClashDetector → HitPipeline theo (actorId, hitIndex): hitbox → phòng thủ/sát thương → Thế/Poise/phản ứng → trạng thái → trigger Hồi Âm (độ sâu ≤ 2, thứ tự id thẻ) → đạn (theo projectileId) → chuyển động kinematic → trạng thái mỗi 30 tick, hồi HP/Khí Thủ/Thế → meter (Combo, Phong Độ, Bộc Phá AUTO, Cổng) → chết & dọn, hết wave → ghi SimOutput; debug SimChecksum mỗi 60 tick.

5.5 RNG — xorshift128+ theo luồng con

StreamSeed(runSeed, stream, floor, sub) = SplitMix64( runSeed ^ Fnv1a64(stream)
                                         ^ (ulong)floor * 0x9E3779B97F4A7C15 ^ (ulong)(uint)sub * 0xD1B54A32D192ED03 )
Xorshift128Plus(seed): s0 = SplitMix64(seed); s1 = SplitMix64(s0); (s0|s1 == 0 → hằng khác 0)
Next(): x = s0; y = s1; s0 = y; x ^= x << 23; s1 = x ^ y ^ (x >> 17) ^ (y >> 26); return s1 + y
NextUnit(): (Next() >> 11) * 2^-53 (double)   ·   NextInt(n): (int)(((Next() >> 32) * (ulong)n) >> 32)

Mỗi luồng có khóa con riêng nên lựa chọn ở tầng N không làm đổi spawn tầng N+1; luồng chiến đấu khóa theo (floor, wave) nên khôi phục đầu wave tái tạo đúng các roll. runSeed 64-bit = Hash64(installId, runIndex, TimeManager.UtcNowMs) tạo ở RunPrep, lưu snapshot; Thử Thách Ngày dùng daily_seed.

Luồng Khóa con Người dùng Khôi phục Nguồn
offer (floor, rerollIndex) OfferGenerator, Banish redraw pendingOffer lưu trước khi hiện (INV-11) thiết kế (constants)
spawn (floor, 0) WaveDirector: thành phần wave, điểm spawn, Tinh Anh + affix, thứ tự hàng đợi tái sinh từ seed — không lưu thiết kế (constants)
loot (floor, 0) Rương/thưởng rơi trong run, phương sai Than Hồng tái sinh từ seed thiết kế (constants)
combat (floor, wave) Chí mạng, U(0.95,1.05), proc chance, Combo Director, bể Phản Đòn, status chance tái sinh từ checkpoint đầu wave thiết kế (constants)
defense (floor, wave) Roll phân vùng Gạt/Chặn/Né/Trúng, Phản Đòn Chặn/Né, Tránh Vùng 70% tái sinh từ checkpoint đầu wave thiết kế (constants)
landing (floor, 0) Chọn LD01–LD08 ở x3/x7, kết quả Mimic/Giếng Con Bạc/Cửa Thử Thách landingState lưu thiết kế (constants)
vendor (floor, 0) Hàng Thương Nhân (LD01, LD00), giá dao động vendorStock lưu thiết kế (constants)
ai (floor, wave) EnemyBrain (hành vi vòng vây, đổi bên 20%), chọn pattern Boss/Thủ Vệ, tie-break token tái sinh từ checkpoint đầu wave thiết kế (constants)
fx — (không seed) Hạt tia lửa, rung lệch, jitter số sát thương, biến thể SFX KHÔNG BAO GIỜ đọc bởi sim thiết kế (constants)
Luồng meta Seed / trạng thái Ghi chú
summon xorshift128+ tài khoản, state lưu trong PlayerSummon Triệu Hồi: kết quả ghi save TRƯỚC animation
relic_chest xorshift128+ tài khoản, state lưu trong PlayerRelics Rương Di Vật: như trên
daily_seed hash("daily", yyyymmdd UTC từ TimeManager) Thử Thách Ngày: mọi người chơi cùng seed

5.6 Luật float & tất định

  1. Vị trí/vận tốc float px, dt hằng 1f/60f, biểu thức viết theo một thứ tự cố định. Chỉ + − × ÷, MathF.Sqrt (IEEE làm tròn chuẩn), Min/Max/Abs/Floor, Math.Round(x, MidpointRounding.AwayFromZero) tường minh.
  2. Cấm lúc chạy trong Sim: Sin/Cos/Pow/Exp/Log (bảng tính sẵn lúc nạp; tăng trưởng tầng đọc từ FloorScaling.csv do Python tính), DateTime, Guid, System.Random, string.GetHashCode (dùng FNV-1a 64), duyệt Dictionary/HashSet (chỉ tra cứu; duyệt bằng mảng có thứ tự), LINQ, async, đa luồng bên trong một world.
  3. HP/sát thương double (Vực Không Đáy vượt 1e15 — hiển thị 1.23aa).
  4. Góc cung hitbox: so sánh tích vô hướng với cos² tính một lần lúc nạp; không atan2.
  5. Phạm vi bảo đảm: bit-exact với cùng build + cùng kiến trúc CPU (INV-14: replay, khôi phục wave, hồi quy). Giữa ARM64 (máy) và x64 (Editor/autosim), trình biên dịch C++ của IL2CPP có thể gộp FMA → autosim là thống kê, không replay chéo máy; kiểm tra chéo bằng phân phối (§5.7). Không thêm cờ tối ưu float cho IL2CPP.

5.7 Hash & hồi quy 1,000 seed

  • SimChecksum mỗi 60 tick: FNV-1a 64 trên (tick, với mỗi actor theo id: (int)(x·16), (int)(y·16), bit HP, Thế, state, clip, progressMilli; số lần rút của mỗi luồng RNG) → gộp thành RunHash.
  • Golden: Tests/Golden/determinism.json = RunHash của seed 1…1,000 × {H01, H02} × B1–B10, meta profile cố định, Auto-Pick "Cân Bằng". Khóa theo ContentHash (hash mọi CSV Gameplay) + SimVersion. Đổi nội dung có chủ đích → chạy menu Deepstair/Golden/Rebuild, commit file golden cùng thay đổi (code-reviewer kiểm).
  • Song sinh: cùng seed chạy 2 lần trong một process → cùng hash (bắt static). Song song: 8 world trên 8 luồng = kết quả tuần tự.
  • Tương đương khôi phục: chạy tới đầu wave X, ghi snapshot, nạp vào world mới, chạy tiếp → RunHash từ điểm đó trùng run không gián đoạn.
  • Chéo máy: 50 seed × B1–B5 trên 1 máy Android Mid (PlayMode trên thiết bị) so với Editor: báo tỉ lệ lệch hash (thông tin) + KS-test phân phối tầng chết p > 0.05 (bắt buộc).

6DefenseResolver

6.1 Trình tự (01 §5)

Địch E (MoveRunner)      CombatDirector      DefenseResolver                    Anh hùng H                 Trình diễn
 │ bắt đầu lấy đà t0 ───────────────────────────────────────────────────────────────────────────────────► glyph/nháy đỏ (01 §11.3)
 │                        (đã giãn ≥15 tick
 │                         với hit lính khác)
 │ AttackCommit{E,hit,kind,hitTick} @ hitTick−9 ─►│
 │ E → Committed (khóa hitTick: bỏ qua hit-stop,   │ ReadyState(H) theo bảng 01 §5.2
 │      Chill, slow cho tới hitTick)               │ H có hit trong [hitTick−3, hitTick+3]? → Clash pending, không roll
 │                                                 │ hệ số loại đòn × trạng thái → trần → r = defense.NextUnit()
 │                                                 │ kết quả O + clip phòng thủ c (contact = k tick)
 │                                                 │──── BeginDefending(c, start = hitTick − k) ──►│ Defending (khóa đòn mới,
 │                                                 │                                              │ clip miễn hit-stop)
 │ HitFrame @ hitTick ───────────────────────────► │ Apply(O) CÙNG tick: Gạt → Thế ×mult + Mất Thăng Bằng; Chặn → ST×(1−BlockRed), Khí Thủ;
 │                                                 │ Né → 0 + i-frame; Trúng → ST đầy đủ, Poise, Combo đứt ─────────► tia lửa/đóng băng/chữ/SFX @ hitTick (INV-05)
 │                                                 │ roll Phản Đòn (Gạt: luôn; Chặn/Né: rate, ICD) ─► QueueCounter(hitTick+1)
 │                                                 │ RunEventBus: on_deflect/on_perfect_deflect/on_block/on_evade/on_clash …

6.2 Căn khung chạm

  • Clip phòng thủ (DEFLECT, BLOCK, EVADE_*) mang contact (tick). Chuẩn dữ liệu: contact = 9 cho Gạt/Chặn → bắt đầu đúng tick cam kết. k > 9: clip bắt đầu ở tick cam kết, nhảy cóc k − 9 tick đầu. k < 9: anh hùng giữ guard key 9 − k tick rồi mới chạy clip.
  • Trong Defending, đồng hồ clip phòng thủ không bị hit-stop (hit-stop của đòn khác đè vào anh hùng bị hoãn tới sau hitTick). Debug assert defense_contact_error_ticks = 0; log tối đa ±1 tick ở bản phát hành.
  • Đạn: cam kết khi thời gian tới va (khoảng cách / tốc độ, tính bằng tick) ≤ 9; từ đó đạn bị khóa tới hitTick (bám mục tiêu ≤ 2 px).
  • Đòn area: không roll; AreaAvoid quyết định lúc bắt đầu báo hiệu (≥ 0.8 s → luôn rời vùng nếu tự do; 0.6–0.8 s → roll 70% trên luồng defense).

6.3 Hủy & va chạm

Tình huống Xử lý
E bị gián đoạn trước hitTick (hất, choáng, chết, Vỡ Thế, túm) DefenseCancelled → H thoát Defending ngay về guard key, không tiêu ICD
Hai cam kết cùng tick (01 C10) Xử lý theo (hitTick, actorId); đòn sau khi H đã Defending chỉ được Né ×0.6
Va Kiếm ClashDetector ở hitTick:
Địch gạt anh hùng (enemy_deflect) Roll trên luồng defense trong HitPipeline; H khựng 0.3 s; đòn phản của địch đi lại qua resolver
Tất cả tỉ lệ = 0 (01 C12) Bỏ roll, không rút RNG (giữ ổn định luồng)

6.4 Cấu trúc dữ liệu (0 GC)

AttackCommit, DefensePlan { enemyId, hitIndex, hitTick, outcome, clipId, startTick, counterRolled } là struct trong mảng vòng cố định 32 phần tử; ReadyState tính từ HeroState (bit cờ Attacking/HitWindow/Airborne/Armor/Stunned/Grabbed/IFrame). Hệ số loại đòn đọc từ CombatConfig.csv (attack_*_mods), trần từ cap_*.

7Combo runtime

Thành phần Trách nhiệm Ghi chú kỹ thuật
MoveRunner Chạy MoveDef: progress fixed-point, hit, cancel, armor/iframe, root motion theo đường cong khung (từ export) Một instance/actor, không cấp phát
RouteRunner Chuỗi đòn của RouteDef, warp ≤ 24 px trong 4 tick, route carry, tạm dừng khi Defending, tiếp nếu ≤ 36 tick 01 §6.6
ComboDirector Ứng viên → ưu tiên tuyệt đối → score = weight × situ × novelty → bốc trên luồng combat cond biên dịch lúc nạp thành mảng token (CondCompiler); lịch sử 3 lộ trình gần nhất; hàng "ra mắt" lưu snapshot
ComboTree ≤ 64 slot lộ trình/anh hùng (06 §4.5), mảng cố định route_mod chưa sở hữu → lưu chờ (06 K4)
SkillService, ExecutionService Kỹ Năng A/B (điều kiện AUTO), Tuyệt Sát (tầm, hồi, 10% Boss) 01 §9
StyleService, OverdriveService, ComboCounter Phong Độ D→SSS, Bộc Phá/Thức Tỉnh, đếm hit Số từ CombatConfig.csv
HeroGaugeService Khung chung đọc HeroGauges.csv (gain/decay/threshold/spend); phần riêng qua IHeroRule 8 Cổng 01 §9.1

8Echo runtime

8.1 Nạp

HeroEchoes.csv + EchoesShared.csv → EchoDatabase.Load() (một lần khi vào BattleScene, ~420 thẻ, mục tiêu < 15 ms Mid): mỗi effect qua EchoDslParser (Lexer → AST → Compiler) thành CompiledEffect = mảng OpInstr { OpCode op; short argStart; byte argCount; } + mảng OpArg { ArgKind kind; float v1, v2, v3; int sym; } (giá trị theo cấp Lv1/2/3). Ký hiệu (RPGStatType, id lộ trình/đòn, sự kiện, rule, nguyên tố) được intern thành int qua SymbolTable. Ký hiệu không giải được → thẻ bị vô hiệu + BugLogger (bản dev: lỗi đỏ), EditMode test bắt trước khi build. Lexer chấp nhận − (U+2212) như -, hậu tố % và s.

8.2 Áp dụng

  • Thẻ áp từ tầng kế (06 §5): ở Descent, BuildState.Rebuild() dựng lại toàn bộ từ danh sách (echoId, lv, pickOrder) theo thứ tự nhặt — không "gỡ" từng phần → không lệch khi lên cấp/thay thế. Chi phí ~0.2 ms, mảng cấp phát trước.
  • Chỉ số: StatSheet giữ 4 lớp (BaseAdd, BasePercent, TotalAdd, TotalPercent) cho 52 RPGStatType, tính lại khi bẩn: Final = ((Base + ΣBaseAdd)·(1+ΣBasePercent) + ΣTotalAdd)·(1+ΣTotalPercent) → kẹp CAPS. Meta (lớp 1–2) đến trong RunSetup, Hồi Âm (lớp 3–4) từ BuildState.

8.3 Handler theo op (06 §8.2) — bảng tĩnh, không reflection

OpCode Handler Thời điểm Ghi vào
stat, statflat, curse StatOp Rebuild StatSheet lớp 3/4 (tỉ lệ cộng điểm)
route_add / route_mod RouteOp / RouteModOp Rebuild ComboTree (weight 1.5/2.0/2.5, EchoLv ST) / bảng sửa theo selector
move_add / move_replace / skill_mod MovePoolOp / MoveReplaceOp / SkillModOp Rebuild Bể đòn (counter…), ô kỹ năng, tham số kỹ năng
trigger TriggerOp Rebuild → chạy trong combat TriggerTable[event] (+cond, icd tick, chance)
infuse InfuseOp Rebuild Bảng phủ nguyên tố theo selector (1 nguyên tố/selector)
rule RuleOp Rebuild RuleSet (bit + tham số int); <CODE>_* → HeroRuleRegistry
gauge GaugeOp Rebuild Tham số Cổng
economy EconomyOp Ngay khi nhặt (embers, reroll…) / Rebuild (%) Ví run, hệ số Kết Quả
summon SummonOp Trigger SummonTable
awaken AwakenOp Rebuild Cấu hình Thức Tỉnh
evolve EvolveOp Rebuild (trước route_add cùng thẻ) Gỡ lộ trình gốc của thẻ COMBO

OpCode → handler là mảng tĩnh khởi tạo trong code (switch), hiệu ứng con (burst, wave, proj, status, heal, shield, buff, meter, cdr, ember, slowmo, clone, posture, exec_ready) chạy trong EffectExecutor.Execute(in OpInstr, ref EffectContext) bằng switch — không delegate cấp phát, không reflection mỗi hit.

8.4 Trigger & RunEventBus

  • Sự kiện là struct CombatEvent { EventKind kind; int source, target; float value; int tag; } đẩy vào mảng frame; TriggerTable lập chỉ mục theo EventKind → danh sách (echoIndex, opIndex) đã sắp theo id thẻ (tất định).
  • Độ sâu kích hoạt ≤ 2 (06 §8.6), cờ "đang xử lý" theo thẻ (K7), ICD lưu bằng tick, chance roll trên luồng combat.
  • HealCap: cửa sổ trượt 60 tick, tổng hồi từ trigger ≤ 2% Max HP/s; ICD mặc định burst/wave 12 tick/mục tiêu, slowmo 240, clone 60, heal 18.

8.5 Quy tắc riêng anh hùng

rule(<CODE>_<NAME>) → class sealed hiện thực IHeroRule (hook: OnCommit, OnHit, OnDefense, OnGauge, ModifyScore…) đăng ký thủ công trong HeroRuleRegistry (một dòng/rule). Anh hùng mới = dữ liệu + rule class + một dòng đăng ký (03 §9).

8.6 IL2CPP

Sim không dùng reflection. link.xml (sinh bởi LinkXmlGenerator) chỉ giữ: kiểu save 2.BUS/4.CORE (Newtonsoft TypeNameHandling.All của DataPlayerBase), model CsvReaders của 2.BUS, Newtonsoft.Json converters. Build IL2CPP thử ở mỗi milestone + test smoke trên máy.

9Trình diễn pixel

9.1 Import act (M1 pixelfx → Unity)

  • node tools/body-sheet.mjs <HERO> [--costume SK…] → <HERO>_body.png + .json: frames[{rect, pivot, startTick, tags[hit_i | contact | guardKey | airReady]}], clips{<MoveId>{frames[], durTicks, hits[tick], contact, root[(dx,dy)/khung], weaponTip[]}}.
  • node tools/move-export.mjs <HERO> → moves_<HERO>.json (act.hits[].t, stop, shake, điểm neo VFX).
  • Exporter lấy mẫu act theo lưới 12 fps và chèn khung khóa đúng tick của mỗi hit và contact → mỗi hit-frame bắt đầu đúng một ranh giới khung.
  • BodyClipImporter → BodyClipLibrary (SO/anh hùng). ActSyncValidator (lỗi = dừng): tick hit act ≠ tick hit HeroHits.csv; dur lệch > 1 tick; khung đầu/cuối của đòn trong lộ trình mặt đất khác hash guard key (INV-16); clip Gạt/Chặn contact ≠ 9.

9.2 PixelClipPlayer

Mỗi frame: lấy (clipId, progressMilli) của actor từ bridge → con trỏ khung tăng dần (startTick·1000 ≤ progress) → chỉ gán SpriteRenderer.sprite khi đổi index. AtkSpeed tự đúng vì progress là thời gian clip. Vị trí: nội suy giữa 2 tick rồi làm tròn px nguyên + lệch làn 0–6 px (01 §2).

9.3 Feel & pool (cấp phát trước, trần theo tier)

Dịch vụ Nội dung
FeelDirector Đọc PresentEvent của tick: slow-mo toàn cục, zoom 2× (PixelViewport), letterbox 16 px, impact frame, camera punch 2 px
ShakeService Công thức pixelfx (01 §14.2), px nguyên, "Giảm nhấp nháy" ×0.5
TintService Đổi shared material (nháy trắng 1 khung, viền vàng/trắng) + vertex color trạng thái
AfterimagePool Tối đa 12 renderer, tắt ở Low
DamageNumberPool Glyph sprite (SpriteFont Tiny5/VI), định dạng số vào char[] (K/M/B/aa) — không string
VfxPlayer Sheet pixelfx đã recolor theo anh hùng lúc export (không recolor runtime), pool theo id

Sim không biết hình ảnh: hit-stop hiển thị đúng vì progress clip của actor đứng yên trong sim.

10Sự kiện

  • Trong trận: chỉ RunEventBus (struct, không boxing) + SimOutput. TigerForge không xuất hiện trong 3.GAMEPLAY.
  • Ranh giới: RunSessionAdapter (2.BUS) dịch đầu ra của driver thành EventManager.EmitEvent(nameof(EventId.X), data). EventId thêm (giữ quy ước template: chỉ dùng qua nameof):
EventId mới (TigerForge) Phát bởi Nghe bởi
RunStarted RunSessionAdapter HUD, Quest (bắt đầu run), Auto-Rerun
FloorCleared RunSessionAdapter Quest/Pass tiến độ, Heo Đất (tích theo tầng)
EchoPicked RunSessionAdapter Codex (đã nhặt), nhiệm vụ 'chọn N Hồi Âm'
BossDefeated RunSessionAdapter first-kill, FeatureUnlock queue, Gói Mốc Độ Sâu
RunEnded RunResultService Home refresh, Auto-Rerun, popup bán hàng sau run
DepthRecordChanged RunResultService FeatureUnlock, tốc độ x1.5/x2, Auto-Pick (B25)
HeroUnlocked HeroService HeroHall badge, thưởng sở hữu
HeroLeveled HeroService badge, nhiệm vụ
HeroStarred HeroService badge, thưởng sở hữu ★4
MasteryLeveled RunResultService EchoUnlockReveal
TrainingUpgraded TrainingController nhiệm vụ, power
LanternNodeUpgraded LanternTreeController power, Auto-Pick E05
RelicChanged RelicService power, bộ Di Vật
SummonCompleted SummonService HeroHall, nhiệm vụ
CampfireClaimed CampfireController nhiệm vụ
CodexUpdated CodexService badge
FeatureQueueChanged FeatureUnlockQueue Home
PerfTierChanged PerfTierService Settings, RunTuning lần run sau
SaveRecovered SecureSaveStore toast + BugLogger

11Lưu dữ liệu

11.1 Module (PlayerDataManager.<Module>)

Module Nguồn Trường chính Tài liệu Thời điểm flush
PlayerAccount template level, exp, name, avatar, installUtc, runCount, lastLoginUtc 07 §2 giao dịch
PlayerResource template ví MoneyTypes → long (gồm Mảnh 700–711 + 750–758, Vạn Năng 720–722) 08 giao dịch
PlayerBattle template, mở rộng depthRecord (Bậc 0), recordByDread[20], abyssRecord, bossFirstKill[], wardenFirstKill[], sharedEchoGroups[], milestonesClaimed[], runIndex, bossDefeats{BSxx:n} 05, 06 §7 RunEnd
PlayerHeroes mới selected, owned{Hxx:{lv, star, masteryXp, masteryLv, skins[], skin, personalRecord, runs, firstUnlockUtc}} — Mảnh nằm ở PlayerResource 03 §5, 07 §6 giao dịch
PlayerTraining mới levels{stat:lv} 07 §3 giao dịch (debounce)
PlayerLanternTree mới nodes{K01..E10: lv}, ashSpent, respecCount 07 §4 giao dịch
PlayerRelics mới items[{uid, tpl, rarity, lv, main, subs[{k,v}], locked}], equipped{A,R,T,S}, nextUid, chestRng{s0,s1}, chestPity{} 07 §5, 09 §4 giao dịch (flush ngay)
PlayerCodex mới echoPicked[] (bitset theo index), echoSeen[], enemyKills{ENxx:n}, bossKills{}, firstPickGemClaimed[] 07 §7, 06 §7 RunEnd
PlayerCampfire mới lastClaimUtc, bankedSec, adBoostUsedDay 07 §8 giao dịch
PlayerSummon mới rng{s0,s1}, pity{banner:{sinceEpic, sinceLegend, featuredGuarantee}}, totalPulls, tutorialDone 09 §4 giao dịch (flush ngay)
PlayerRunPrep mới hero, dread, startFloor, preset, customTags[], autoPick, odManual, speed, autoRerun, autoRerunCount 05, 06 §6 debounce 1 s
PlayerFtue mới step, flags{cinematicSeen, summonTutorialDone, …}, featureQueue[], featuresReleased[] 00 §7–8, 11 mỗi bước
PlayerDailyChallenge mới dateKey, attempts, best, rewardsClaimed 05, 10 RunEnd
PlayerSetting template, mở rộng + reduceFlash, showDmgNumbers, perfTierManual, batterySaver, haptics 11 debounce 1 s
LiveOps / Monetization template Quest, DailyLogin, SevenDayLogin, GloryPass (= Lantern Pass), PiggyBank, Vip30Day (= Thẻ Tháng), MailBox, DailyAds, LuckySpin, PackageManager 09, 10 theo feature

Mảnh anh hùng là tiền tệ (ví PlayerResource, MoneyTypes 700–711/720–722) để RewardManager/CSV thưởng (res_type = 1) cấp được trực tiếp; PlayerHeroes không lặp số mảnh.

11.2 File & mã hóa

  • Template hiện lưu mỗi module vào PlayerPrefs (JSON, không mã hóa) → thay bằng SaveHub (4.CORE): module vẫn kế thừa DataPlayerBaseGeneric<T> (giữ API Load/Save/SetupDefaultData), nhưng Save() chỉ đánh dấu bẩn; SaveHub ghi một file persistentDataPath/save/profile.dsv chứa mọi module → giao dịch nhiều module (Triệu Hồi: KC + mảnh + pity) nguyên tử.
  • Định dạng: header {magic "DSV1", schemaVersion, installId, seq, savedUtc (TimeManager), payloadLen} + payload = AES-256-CBC(gzip(JSON {module: json})) + HMAC-SHA256(header ‖ ciphertext). Khóa = PBKDF2(bí mật ứng dụng obfuscate qua com.blackface.security, installId). Mục tiêu: chống sửa tay & phát hiện hỏng, không phải chống hacker quyết tâm (game offline).

11.3 Ghi nguyên tử, backup, phục hồi

  • Ghi: serialize JSON trên main thread → gzip + AES + HMAC + IO trên thread pool (UniTask.RunOnThreadPool) → profile.dsv.tmp + Flush(true) → File.Replace(tmp, profile.dsv, profile.bak1). profile.bak2 cập nhật tối đa mỗi 30 phút và khi mở app (hai mốc thời gian khác nhau → backup không cùng hỏng).
  • Đọc: profile.dsv → bak1 → bak2 → SetupDefaultData(); mỗi bước kiểm HMAC + parse. Dùng backup → toast "Đã khôi phục dữ liệu từ bản sao lưu" + save_recovered + BugLogger kèm file hỏng.
  • Nhịp: giao dịch có giá trị (IAP grant, Triệu Hồi, Rương, tiêu KC) → SaveHub.FlushNow() trước khi hiện kết quả; còn lại debounce save_debounce_s (ProgressThread timer); OnApplicationPause(true)/quit → flush đồng bộ. Không bao giờ trong Update (rule data-persistence).

11.4 Migration

schemaVersion toàn cục + v mỗi module; ISaveMigration { int From; void Apply(JObject root); } chạy tuần tự, ghi profile.v<n>.bak một lần trước khi áp. v0 → v1 (máy dev đã chạy template): nhập các key PlayerPrefs cũ vào file mới, bỏ số dư Energy (3), đặt PlayerBattle.depthRecord = 0, xóa key PlayerPrefs cũ sau khi ghi thành công. Mỗi migration có EditMode test với JSON mẫu. Quyết định này đóng nhóm "save-migration" mà /planning-system bắt buộc hỏi.

11.5 Run snapshot

  • File riêng run.dsr (cùng phong bì mã hóa), nhị phân có version (RunSnapshotWriter vào byte[] pool, 4–8 KB) — ghi được giữa trận mà không giật.
  • Schema nghiệp vụ theo 05 §16; phần kỹ thuật bắt buộc: snapVersion, simVersion, contentHash, runId, runSeed, RunSetup đầy đủ (kể cả RunTuning chụp từ RC), floor, wave, phase, HeroState (vị trí, HP, Khí Thủ, Bộc Phá, Cổng, Phong Độ, Combo, hồi chiêu, ICD, chuỗi Gạt, trạng thái), Hồi Âm (id, lv, pickOrder), Loại bỏ, pity, Đổi thẻ đã dùng/còn, pendingOffer + rerollIndex, lộ trình chờ ra mắt + lịch sử 3 lộ trình, hồi sinh đã dùng (miễn phí/trả phí), trạng thái Chiếu Nghỉ/Thương Nhân, bộ tích lũy Kết Quả, RunStats telemetry, cờ detail lấy mẫu.
  • Điểm ghi: (1) pendingOffer vừa sinh — trước khi hiện (INV-11); (2) sau khi chọn → Descent; (3) checkpoint wave: tick đầu tiên của wave khi không còn địch sống (wave chồng → lùi về đầu tầng); (4) Downed; (5) mua ở Chiếu Nghỉ; (6) phase = Ended trước IRunResultSink.Commit.
  • Khôi phục sau cập nhật app: snapVersion còn hỗ trợ → nạp; contentHash khác → vẫn nạp (không bảo đảm replay), thẻ bị xóa xử lý theo 06 K5.

12CSV config

Luồng: GDD/Tools/data/*.py → GDD/Config/*.csv (nguồn thiết kế) → py -I GDD/Tools/sync_unity_config.py (task setup viết) chép theo bảng dưới, đổi phần thưởng dạng chữ thành dòng Resource 6 cột (res_type, res_id, res_number, bonus, stage_bonus, custom_value — skill csv-config) cho feature template → importer:

  • 2.BUS: pipeline CsvReaders sẵn có (CSV → SO collection → DataManagerAutoGenerate).
  • 3.GAMEPLAY.Sim: GameplayCsvCodegen sinh model + parser C# từ header (không reflection, AOT-safe) vào Sim/Data/Generated/; CSV nhúng dạng TextAsset trong group Addressables core_boot, Sim parse lúc vào BattleScene. Header CSV đổi → codegen + test schema báo lỗi.

Bảng ánh xạ (tự quét mọi module data lúc build; dòng (REVIEW) = CSV chưa có luật, phải thêm luật trong tech.py trước khi hiện thực):

Đích (dưới Assets/_Project/) Assembly CSV (module data) Model / cách nạp
Gameplay/Data/Config/ 3.GAMEPLAY CombatConfig — combat.py CombatConfigModel (key/value → struct CombatTuning)
Gameplay/Data/Config/ 3.GAMEPLAY TechPerfTiers — tech.py PerfTierModel
Gameplay/Data/Config/Heroes/ 3.GAMEPLAY HeroEchoes, HeroGauges, HeroHits, HeroMoves, HeroRoutes, HeroTraits, Heroes — heroes.py ‹Tên›Model
Features/Meta/HeroHall/CsvConfig/ 2.BUS HeroLevel, HeroMastery, HeroStars — heroes.py ‹Tên›Model
Features/Monetization/Summon/CsvConfig/ 2.BUS FeaturedRotation, HeroSummon, SummonBanners — monetization.py ‹Tên›Model
Gameplay/Data/Config/Echoes/ 3.GAMEPLAY EchoesShared — echoes_shared.py ‹Tên›Model → EchoDatabase
Gameplay/Data/Config/Enemies/ 3.GAMEPLAY BossChest (economy), BossIntel (enemies), BossPhases (enemies), Bosses (enemies), Enemies (enemies), EnemyAffixes (enemies), EnemyArt (enemies), EnemyAttacks (enemies), EnemyConfig (enemies), EnemyProjectiles (enemies), Wardens (enemies) ‹Tên›Model
Features/Meta/DepthMilestones/CsvConfig/ 2.BUS AbyssMilestones (run), DepthMilestones (economy) ‹Tên›Model (RunResultService trao thưởng)
Features/Meta/DailyChallenge/CsvConfig/ 2.BUS DailyRewards — run.py ‹Tên›Model
Gameplay/Data/Config/Run/ 3.GAMEPLAY ArtStrata — art.py gộp vào StratumModel (đèn, ambient, sương, nhạc)
Gameplay/Data/Config/Pixel/ 3.GAMEPLAY ArtVfxMap — art.py VfxMapModel (sự kiện/lớp hit → id pixelfx, ramp, layer)
— (chỉ thiết kế, không nhập Unity) — ArtAssetTotals, ArtHeroActs — art.py theo dõi sản xuất act (14 §5)
Core/Modules/Sound/Config/ 4.CORE AudioMusic, AudioSfx — art.py SoundConfig (id int ↔ clip, bus)
— (chỉ thiết kế, không nhập Unity) — UiScreens — art.py đối chiếu EnumBase.Features (13 §13.3)
Gameplay/Data/Config/Run/ 3.GAMEPLAY DailyModifiers (run), Dread (run), FloorLayout (run), FloorScaling (run), LandingPrices (run), Landings (run), RunRules (run), Strata (run), StrataEnemyWeights (enemies), StrataHazards (run) ‹Tên›Model
Features/Meta/Training/CsvConfig/ 2.BUS Training, TrainingCost — meta.py ‹Tên›Model
Features/Meta/LanternTree/CsvConfig/ 2.BUS LanternTree — meta.py ‹Tên›Model
Features/Monetization/RelicChest/CsvConfig/ 2.BUS RelicChestRates — monetization.py ‹Tên›Model
Features/Meta/Relics/CsvConfig/ 2.BUS RelicRarity, RelicSets, RelicSubstats, RelicTemplates, RelicUpgrade — meta.py ‹Tên›Model
Features/Meta/Wardrobe/CsvConfig/ 2.BUS SkinSkus — monetization.py ‹Tên›Model (+ catalog IAP)
Features/Meta/HeroHall/CsvConfig/ 2.BUS Skins — meta.py ‹Tên›Model
Features/Meta/Codex/CsvConfig/ 2.BUS Codex — meta.py ‹Tên›Model
Features/Meta/Campfire/CsvConfig/ 2.BUS Campfire — meta.py ‹Tên›Model
Features/Social/Account/CsvConfig/ 2.BUS AccountLevel — meta.py thay CSV template cùng tên (schema mới)
Features/Onboarding/FeatureUnlock/CsvConfig/ 2.BUS FeatureUnlock — meta.py thay CSV template (schema mới)
Features/Monetization/Shop/CsvConfig/ 2.BUS IapCatalog — monetization.py IapProductModel → catalog PurchaseManager
— (chỉ thiết kế, không nhập Unity) — RegionalPricing, ValueTable — monetization.py nhập tay Play Console / App Store Connect; balance
Features/Monetization/Package/CsvConfig/ 2.BUS DailyDeals — monetization.py thay DailyShop.csv template
Features/Monetization/DescentFund/CsvConfig/ 2.BUS GrowthFund — monetization.py ‹Tên›Model
Features/Monetization/MonthlyCard/CsvConfig/ 2.BUS CardPacks — monetization.py Vip30Day model (template) mở rộng
Features/Monetization/Package/CsvConfig/ 2.BUS PiggyBank — monetization.py thay PiggyBank.csv template
Features/Monetization/OfferTrigger/CsvConfig/ 2.BUS OfferTriggers — monetization.py OfferTriggerModel
Features/Monetization//CsvConfig/ 2.BUS DepthPacks — monetization.py model template của feature (tách dòng Resource)
Features/LiveOps/DailyAds/CsvConfig/ 2.BUS DailyAds — monetization.py thay DailyAds.csv template
Features/_Shared/Systems/Ads/CsvConfig/ 2.BUS AdPlacements, InterstitialRules — monetization.py AdPlacementModel / AdRulesModel
Features/LiveOps/Quest/CsvConfig/ 2.BUS Achievements, DailyQuest, QuestMilestones, WeeklyQuest — liveops.py model template Quest
Features/LiveOps//CsvConfig/ 2.BUS DailyLogin28, SevenDayLogin — liveops.py model template
Features/Monetization/GloryPass/CsvConfig/ 2.BUS LanternPass, PassPoints — liveops.py GloryPassModel (template)
Features/LiveOps/Events/CsvConfig/ 2.BUS Comeback, EventAccents, EventFrames, EventRules, EventTrack, FestivalShop, Festivals, SeasonRoadmap, WeekendBuffs — liveops.py ‹Tên›Model
Features/LiveOps/LuckySpin/CsvConfig/ 2.BUS LuckySpin — liveops.py thay LuckySpin*.csv template
Features/_Shared/Systems/Resources/NotiScript.csv 2.BUS PushNotifications — liveops.py gộp vào NotiScript
Assets/Resources/remote_config_defaults.xml Editor tool RemoteConfigKeys (liveops), TechRemoteConfig (tech) RcDefaultsExporter sinh XML + RemoteKeys.g.cs
Features/System/Tracking/AnalyticsEvents.g.cs Editor tool TechAnalyticsEvents — tech.py codegen hằng tên event/param
Gameplay/Tests/EditMode/Data/ 3.GAMEPLAY.Tests TechDefenseCases — tech.py DefenseCase (test vector)
— (chỉ thiết kế, không nhập Unity) — EconomyConfig — economy.py balance_sim
Gameplay/Data/Config/(REVIEW) 3.GAMEPLAY Currencies, GoldPacks — economy.py ‹Tên›Model — chưa có luật, cần xếp chỗ

13Enum: MoneyTypes, ResourceTypes, Features — kiểm tra trùng

Đã đối chiếu Assets/_Project/Core/GameSystem/EnumBase.cs + quét toàn bộ 30 CSV template (res_type = 1 chỉ dùng id 1, 2, 3, 17). validate() của tech.py khóa các kiểm tra dưới đây.

13.1 MoneyTypes

Số Member MoneyTypes Tên VN Kiểm tra trùng
5 BattleScroll Quyển Chiến trong dải 4–16 đã gỡ → Deepstair nhận lại (không save/CSV nào dùng)
12 RelicKey Chìa Di Vật như trên
13 SummonSigil Ấn Triệu Hồi như trên
14 RelicDust Bụi Di Vật như trên
16 ReviveTicket Vé Hồi Sinh như trên
80 SoulAsh Tro Hồn dải trống
207 EventCandy Kẹo sự kiện sát dải gỡ 200–206 nhưng trống
700–707 HeroShardKai/Ald/Vex/Brn/Lyr/Rok/Mor/Sab Mảnh H01–H08 dải trống (khác 600–609 đã gỡ)
708–711 HeroShardZer/Fen/Mom/Iso Mảnh H09–H12 dải trống
750–758 HeroShardAur/Sor/Tsu/Jun/Ign/Elo/Kaz/Tau/Vul Mảnh H13–H21 (750–769 dành H13–H32) dải trống
720 UniversalShardRare Mảnh Vạn Năng R dải trống
721 UniversalShardEpic Mảnh Vạn Năng E dải trống
722 UniversalShardLegend Mảnh Vạn Năng L dải trống
17 LuckySpinCoin Xu Vòng Quay đã có trong template — dùng lại, KHÔNG thêm
3 Energy (không dùng) Deepstair không có năng lượng: giữ member, xóa mọi dòng res_id 3 khỏi CSV

Phát hiện & cách xử lý:

  1. 5/12/13/14/16 nằm trong dải 4–16 mà comment enum ghi "Đã gỡ (không tái dùng số)", trong khi cùng comment lại gọi 4–16 là "dải trống cho game". Không code/CSV/save nào dùng các số này → Deepstair nhận lại có chủ đích (00 §14 đã khóa); sửa comment enum thành "4–16: Deepstair dùng 5, 12, 13, 14, 16"; migration v0→v1 xóa mọi key ví 4–16 lạ trong save máy dev.
  2. 17 đã là LuckySpinCoin = "Xu Vòng Quay" của 00 → dùng lại, không thêm member.
  3. Energy = 3 đang được 13 CSV template thưởng (DailyAds, DefaultResource 60, LuckySpin, Quest, SevenDayLogin, GemRefillPack, FirstPurchase, Vip30Day, AccountReward, GloryPass, ResourceExchange, DailyShop, MiniShopConversion) + GeneralConfig.csv cột năng lượng. Deepstair không có năng lượng: giữ member (code template tham chiếu), thay toàn bộ các CSV đó bằng bản sinh từ 08–10, ẩn Energy khỏi HomeResourceView; sync_unity_config.py từ chối dòng res_id 3.
  4. 207 sát dải gỡ 200–206 nhưng trống; 700–722 không chạm dải gỡ 600–609 → hợp lệ.
  5. Bundle id lệch: ProjectSettings + mọi pack_id IAP template dùng com.defaultcompany.deepstair.* → đổi thành com.blackface.deepstair.* (SKU theo IapCatalog.csv của 09).
  6. Than Hồng (Embers) không là MoneyTypes — chỉ sống trong snapshot run (INV-03).

13.2 ResourceTypes (thưởng không phải tiền)

Số Member ResourceTypes Dùng cho
11 Hero Mở khóa trọn anh hùng (quà B10/B20, gói) — res_id = số anh hùng 1..12
12 Relic Di Vật cụ thể — res_id = index mẫu, custom_value = độ hiếm
13 Skin Trang Phục — res_id = index trong Skins.csv
14 RelicChest Lượt mở Rương Di Vật — res_id = id rương

Số 3 (Hero) đã gỡ → dùng 11. Thưởng "tặng Aldric" (00 §7.1 bước 11) = res_type 11, res_id 2.

13.3 Features (tên member = tên prefab, nạp bằng Resources.Load(name))

Thư mục Features/… Số + member (= tên prefab)
Meta 200 HeroHall · 201 HeroDetail · 202 RunPrep · 203 Training · 204 LanternTree · 205 Relics · 206 RelicDetail · 207 Codex · 208 Campfire · 209 Wardrobe · 210 DepthMilestones · 211 DreadSelect · 212 DailyChallenge · 213 HeroUnlockReveal · 214 EchoUnlockReveal
Run 230 RunHud · 231 EchoPick · 232 BuildView · 233 Landing · 234 BossGate · 235 Downed · 236 PauseMenu · 237 RunResults · 238 BossIntro · 239 FirstLaunchCinematic · 240 ExpressDescentPick
Monetization 250 Summon · 251 SummonResult · 252 RelicChest · 253 StarterPack · 254 DepthPack · 255 DescentFund · 256 MonthlyCard · 257 PiggyBank · 258 FirstPurchase

Không trùng số đang dùng/đã gỡ (3, 18, 20, 22, 31, 47, 56, 57, 75, 78, 91, 98, 100, 114, 123, 124, 127, 177). Codex và StarterPack là tên cũ với số mới (207, 253) — hợp lệ vì enum serialize theo số. Lantern Pass dùng lại GloryPass/GloryPassPremium (129/130), chỉ đổi chuỗi hiển thị. UiScreens.csv (12) phải đối chiếu đúng danh sách này.

14Analytics (Firebase qua TrackingManager)

FirebaseRunAnalytics hiện thực IRunAnalytics; tên event/param sinh thành hằng AnalyticsEvents.g.cs từ TechAnalyticsEvents.csv (validator: ≤ 40 ký tự, ≤ 25 param, không tên dành riêng của Firebase — ví dụ ad_reward bị cấm nên giữ ads_reward của template). Dictionary tham số chỉ tạo ở ranh giới (cuối tầng/run), không mỗi hit. Lấy mẫu chi tiết: detail = Hash64(runId) mod 10⁴ < analytics_detail_sample × 10⁴, quyết định ở run_start, lưu snapshot.

Sự kiện Khi Tham số Lấy mẫu
tutorial_step Mỗi bước FTUE 00 §7.1 step_id, step_name, elapsed_s 100%
run_start Vào Run run_id, mode, hero_id, hero_lv, hero_star, mastery_lv, skin_id, dread, start_floor, record, acc_lv, power, auto_pick, preset, speed, od_manual, seed_hash, detail, perf_tier 100%
floor_clear FloorClear run_id, floor, hero_id, time_game_s, time_real_s, hp_pct, kills, elites, style_max, combo_max, echoes_owned, routes_owned, embers x0, x5, kỷ lục mới + run chi tiết (RC analytics_detail_sample) + luôn ở x0/x5
route_usage FloorClear (gộp cả tầng) run_id, floor, hero_id, routes_top, unique_routes, starts_total, echo_route_pct, new_route_debuted, director_idle_fallbacks run chi tiết (RC analytics_detail_sample) + luôn ở x0/x5
defense_stats FloorClear (gộp cả tầng) run_id, floor, hero_id, attacks, deflects, perfect, blocks, evades, hits, clashes, counters, guard_breaks, flow_states, area_avoids, exp_deflect_pct, real_deflect_pct, defense_gap_s_max run chi tiết (RC analytics_detail_sample) + luôn ở x0/x5
echo_offer Lượt chọn được sinh (sau Save INV-11) run_id, floor, offer, slot_tiers, reroll_index, source, pity run chi tiết (RC analytics_detail_sample) + luôn ở x0/x5
echo_pick Chọn / Bỏ qua run_id, floor, hero_id, offered, picked, picked_slot, picked_lv, kind, rarity, rerolls_used, banished, auto, preset, time_to_pick_ms 100%
echo_reroll Đổi thẻ run_id, floor, source, gem_cost 100%
landing Kết thúc Chiếu Nghỉ/Cổng Boss run_id, floor, landing_id, choice, embers_spent, outcome 100%
execution Tuyệt Sát trúng run_id, floor, hero_id, target_kind, target_id, move_id, break_source, style_rank Boss/Thủ Vệ 100%; lính: run chi tiết, ≤ 3/tầng
boss_attempt Boss/Thủ Vệ kết thúc (thắng/gục/bỏ) boss_id, floor, dread, hero_id, hero_lv, hero_star, mastery_lv, result, time_s, boss_hp_pct_left, breaks, executions, revives_used, deflects, hits_taken, attempt_no, power 100%
downed HP = 0 run_id, floor, wave, cause_enemy, cause_kind, revives_left 100%
revive Hồi sinh run_id, floor, source 100%
run_end RunEnd run_id, mode, hero_id, floor_max, floors_cleared, cause, duration_real_s, duration_game_s, gold, soul_ash, exp, mastery_xp, chests, new_record, build_cluster, top_tags, echoes_owned, routes_owned, avg_speed, auto_pick_pct 100%
results_x2 Nhân đôi Kết Quả run_id, placement 100%
earn_resource Mọi nguồn tiền tệ (template) currency, amount, source, source_id, balance 100%
spend_resource Mọi chi tiền tệ (template) currency, amount, sink, sink_id, balance 100%
upgrade Nâng cấp meta (template) system, id, level, cost_currency, cost_amount 100%
hero_unlock Có anh hùng mới hero_id, source, days_install 100%
mastery_up Lên Thông Thạo hero_id, mastery_lv, echoes_unlocked 100%
summon_pull Triệu Hồi xong (đã ghi save) banner_id, count, payment, cost, results, pity_epic, pity_legend, featured_hit, new_heroes 100%
relic_chest Mở Rương Di Vật chest_id, count, payment, cost, rarities, set_ids, dust_gained 100%
campfire_claim Nhận Lửa Trại hours, gold, soul_ash, scrolls, ad_boost 100%
feature_unlock Phát tính năng ở Home feature, queue_wait 100%
iap_view Hiện gói/ưu đãi sku, placement, trigger, price_usd 100%
iap_purchase Giao dịch thành công (đã grant) sku, price_usd, local_price, currency, placement, trigger, first_purchase, tx_hash 100%
iap_fail Giao dịch lỗi/hủy sku, reason, placement 100%
ads_reward Quảng cáo thưởng kết thúc (template) placement, network, format, result 100%
ad_impression ILRD LevelPlay (template, chuẩn Firebase) ad_platform, ad_source, ad_format, ad_unit_name, value, currency 100%
perf_tier Phát hiện/đổi tier tier, source, fps_avg, fps_p5, ram_mb, gpu, thermal, sim_ms_p95 100%
save_recovered Đọc save lỗi → dùng backup source, schema_from, error_code 100%
daily_challenge Kết thúc Thử Thách Ngày date_key, hero_id, floor_max, attempt 100%
settings_change Đổi cài đặt key, value 100%
pass_level Lên cấp Lantern Pass season, level, premium 100%

User properties:

User property Giá trị
acc_level Cấp tài khoản
depth_record Kỷ lục độ sâu Bậc 0
dread_max Bậc Kinh Hoàng cao nhất
abyss_record Kỷ lục Vực Không Đáy
heroes_owned Số anh hùng
main_hero Anh hùng chơi nhiều nhất 7 ngày
mastery_max Thông Thạo cao nhất
payer_tier none/minnow/dolphin/whale
days_install Ngày từ cài
perf_tier High/Mid/Low
auto_pick 0/1 đang bật
ftue_step Bước FTUE cuối
lang vi/en
ads_removed 0/1
monthly_card 0/1 đang hiệu lực

Đồng thuận: Google UMP (EU/UK) → Firebase consent mode v2 → ATT (iOS) → khởi tạo quảng cáo cá nhân hóa. run_id = 16 hex ngẫu nhiên, không chứa định danh thiết bị.

15Remote Config

Khóa gameplay/LiveOps do 10 §Remote Config sở hữu; dưới là khóa kỹ thuật. Fetch lúc boot (timeout 3 s → giá trị cache → remote_config_defaults.xml sinh từ TechRemoteConfig.csv). Khóa cột "Chụp" được đọc một lần vào RunTuning khi bắt đầu run và lưu snapshot — RC đổi giữa run không bao giờ tác động trận đang chạy (tất định). Mọi khóa của 10 ảnh hưởng sim phải khai báo cùng cờ này.

Key Kiểu Mặc định Chụp vào RunSetup Ý nghĩa
perf_tier_override string "" Ép tier cho mọi máy ('' = tự phát hiện)
perf_downgrade_fps int 50 FPS trung bình dưới ngưỡng này trong cửa sổ → hạ 1 tier
perf_downgrade_window_s int 30 Cửa sổ đo FPS (giây thực trong Combat)
max_alive_high int 10 ✔ Địch sống đồng thời — High
max_alive_mid int 8 ✔ — Mid
max_alive_low int 6 ✔ — Low
afterimage_min_tier string Mid Tier thấp nhất bật dư ảnh
light2d_min_tier string Mid Tier thấp nhất bật đèn 2D động
combat_overrides_json json {} ✔ Ghi đè key CombatConfig (whitelist, kẹp ±50%) — hotfix cân bằng
sim_max_steps_per_frame int 8 Trần bước sim mỗi frame (chống spiral of death)
snapshot_wave_checkpoint bool true Ghi snapshot đầu mỗi wave
save_debounce_s float 1.0 Debounce ghi meta
analytics_detail_sample float 0.1 Tỉ lệ run gửi sự kiện chi tiết mỗi tầng
analytics_exec_grunt bool true Gửi execution cho lính trong run chi tiết
run_hash_sample float 0.02 Tỉ lệ run gửi seed_hash+hash kết quả để giám sát tất định
min_app_version int 1 Dưới mức này → popup cập nhật (không chặn chơi offline)
force_update_version int 0 Dưới mức này → chặn tới khi cập nhật (chỉ khi có mạng)
crash_safe_mode bool false Tắt đèn 2D + dư ảnh + hạt toàn bộ (khẩn cấp)
bughub_enabled bool true Cho BugLogger gửi báo cáo

combat_overrides_json: chỉ nhận key có trong CombatConfig.csv, kẹp ±50% giá trị gốc, log hash vào run_start.seed_hash để phân tích.

16Hiệu năng

16.1 CPU (đo bằng ProfilerMarker, máy tham chiếu §16.2)

Hệ thống (mỗi tick) High ms Mid ms Low ms ProfilerMarker
Brains (HeroBrain, EnemyBrain, CombatDirector) 0.06 0.10 0.15 DS.Sim.Brains
Combo (MoveRunner, RouteRunner, ComboDirector) 0.04 0.07 0.10 DS.Sim.Combo
HitboxQuery2D + ClashDetector 0.04 0.08 0.12 DS.Sim.Hitbox
DefenseResolver 0.01 0.02 0.03 DS.Sim.Defense
Damage/Posture/Poise/Reaction/Status 0.05 0.09 0.13 DS.Sim.Damage
Echo triggers (RunEventBus + op handlers) 0.04 0.08 0.12 DS.Sim.Echo
Projectiles + motion + bounds 0.04 0.07 0.10 DS.Sim.Motion
Gauge/Style/Overdrive/Combo counter 0.02 0.04 0.05 DS.Sim.Meters
Tổng 1 tick sim 0.32 0.58 0.85 DS.Sim.Step
Mục (mỗi frame) High ms Mid ms Low ms Marker / trần
PixelClipPlayer + đồng bộ sprite 0.15 0.25 0.35 DS.Pix.Clips
Feel (Shake/Tint/Afterimage/DamageNumber) 0.15 0.25 0.30 DS.Pix.Feel
PixelViewport + camera 0.03 0.05 0.07 DS.Pix.Camera
HUD uGUI (canvas động) 0.50 0.80 1.10 DS.UI.Hud
Audio 0.20 0.30 0.40 DS.Audio
Render CPU (URP 2D: cull, batch, submit) 1.50 2.50 3.50 URP
Khác (UniTask, UpdateManager, input) 0.30 0.50 0.70 —
Sim ở x1 (1 tick/frame) 0.32 0.58 0.85 DS.Sim.Step
Sim ở x2 (2 tick/frame) 0.64 1.16 1.70 DS.Sim.Step
Sim ở x3 (3 tick/frame) 0.96 1.74 2.55 DS.Sim.Step
Tổng CPU main ở x1 3.15 5.23 7.27 ≤ 75% × 16.6 ms
Tổng CPU main ở x3 3.79 6.39 8.97 ≤ 75% × 16.6 ms

GPU (Mid): ≤ 8 ms/frame. Thế giới vẽ ở 270×H nên overdraw VFX cộng + đèn ≤ 3× diện tích RT là rẻ; chi phí GPU chính là uGUI ở độ phân giải gốc → overdraw UI ≤ 2.5×, tách canvas tĩnh/động, không Layout lồng trong HUD. Draw call (Mid): thế giới ≤ 40, UI ≤ 30, tổng ≤ 80, SetPass ≤ 25.

16.2 Tier thiết bị

Tự phát hiện lần đầu (RAM, SoC whitelist, model iPhone) → đo FPS trung bình trong perf_downgrade_window_s giây Combat; dưới perf_downgrade_fps hai lần liên tiếp → hạ 1 tier (lưu, không tự nâng). Người chơi đổi được trong Cài đặt. Đổi tier có hiệu lực từ run sau (giới hạn địch là tham số sim).

Tier Phát hiện Máy tham chiếu Địch sống Đạn Dư ảnh Đèn 2D động Light tex scale Số ST cùng lúc VFX instance Vết sàn FPS FPS tiết kiệm pin
High RAM ≥ 6 GB và (iPhone ≥ 12 / Android SoC hạng cao trong bảng whitelist) Pixel 7 / Galaxy S21 / iPhone 12 10 60 bật 8 0.5 24 48 24 60 30
Mid RAM 4–6 GB (mặc định khi không chắc) Galaxy A25 / Redmi Note 13 (Helio G99, 4–6 GB) 8 45 bật 6 0.5 18 36 16 60 30
Low RAM < 4 GB, hoặc FPS TB < perf_downgrade_fps trong 2 cửa sổ liên tiếp Galaxy A05 / Redmi 12C (Helio G85, 3–4 GB) 6 30 tắt 0 0.25 12 24 8 60 30

Adaptive Performance (module có sẵn): cảnh báo nhiệt → hiển thị 30 FPS + tắt dư ảnh; sim không đổi.

16.3 Bộ nhớ

Sprite thế giới để RGBA32 không nén (luật import pixel, ASTC làm vỡ pixel ở 1×) → atlas là chi phí chính. Một atlas/anh hùng; Trang Phục là body sheet riêng thay atlas gốc khi mặc.

Id Anh hùng Khung Nguồn số khung Ô TB (px) Atlas RAM MB (RGBA32) Đĩa MB (PNG)
H01 Kaito 571 dữ liệu (66 đòn) 44×38 2048×1024 8.0 1.76
H02 Aldric 650 dữ liệu (70 đòn) 42×40 2048×1024 8.0 1.76
H03 Vex 603 dữ liệu (75 đòn) 38×36 1024×1024 4.0 0.88
H04 Brann 704 dữ liệu (68 đòn) 56×46 2048×2048 16.0 3.52
H05 Lyra 617 dữ liệu (69 đòn) 64×42 2048×1024 8.0 1.76
H06 Rook 725 dữ liệu (83 đòn) 36×36 2048×1024 8.0 1.76
H07 Mora 677 dữ liệu (71 đòn) 58×48 2048×2048 16.0 3.52
H08 Sable 642 dữ liệu (70 đòn) 68×42 2048×2048 16.0 3.52
Hạng mục (tier Mid, trong run) MB Ghi chú
Anh hùng đang chơi + 1 ô đồng minh (Hồn Đồng Đội LD06) 32.0 atlas lớn nhất roster 16 MB × 2 (skin THAY atlas gốc, không cộng)
Địch thường của Địa Tầng (6 loại) 4.0 1 atlas 1024²
Thủ Vệ + Boss của Địa Tầng 8.0 1 atlas 2048×1024 (ô 96–128 px)
VFX chung (impacts, parry, status) 4.0 1 atlas 1024²
VFX Địa Tầng + VFX chữ ký anh hùng 2.0 1 atlas 1024×512
Nền: tileset + 3 lớp parallax + prop 3.0
UI pixel kit (3 atlas) + icon thẻ động 12.0 PPU 25, point
Font (Tiny5 + font VI fallback, glyph pixel) 3.0
RT thế giới 270×H ×2 + light textures 1.5 H ≤ 640
Âm thanh (SFX nạp sẵn + 1 nhạc stream) 10.0 Vorbis, SFX ≤ 220
Managed heap gameplay (pool, dữ liệu CSV, op list) 24.0 cấp phát trước
Mesh/khác 5.0
Tổng nội dung trong run 108.5
Unity runtime + SDK (Firebase, LevelPlay, IL2CPP metadata) 210 ASSUMED đo ở M0
Tổng tiến trình ước tính 318 High ≤ 600 / Mid ≤ 450 / Low ≤ 350

Trần ô khung: anh hùng/địch ≤ 96×80 px, Thủ Vệ ≤ 128×96, Boss ≤ 160×128 (ô vượt → act bị validator chặn). Anh hùng cần > 2048×1024 → tách 2 atlas 2048×1024: bộ gốc (luôn nạp) + đòn Hồi Âm (nạp bất đồng bộ trong Descent khi nhặt thẻ đầu tiên cần nó) — đa số run chỉ giữ 8–12 MB/anh hùng; bảng trên là trường hợp xấu nhất.

16.4 0 GC trong Combat

Cấm trong đường nóng (Sim + Pixel + Feel + HUD cập nhật): new class, closure/lambda bắt biến, LINQ, string format/concat (HUD số dùng TMP_Text.SetCharArray hoặc glyph), boxing (enum làm key Dictionary → mảng theo index), foreach trên interface, GetComponent (cache ở Awake), Instantiate (pool đủ trần tier lúc vào tầng). Kiểm bằng PlayMode test Recorder("GC.Alloc") 600 frame CombatSandbox = 0 B và performance-reviewer trên mọi diff chạm đường nóng.

16.5 Thời gian nạp

Vào BattleScene ≤ 2.5 s (Mid): nạp group anh hùng + Địa Tầng song song (Addressables), EchoDatabase + GameplayDb ≤ 60 ms, pool dựng trước. Chuyển tầng không nạp gì (cùng Địa Tầng); sang Địa Tầng mới: tải trước group kế tiếp từ tầng x8.

16.6 Addressables & phân phối

Group Nội dung Phân phối MB đĩa (ước tính)
core_boot Home/UI kit, font, VFX chung, âm thanh chung, Localization, H01 Kaito install-time (base AAB / IPA) 38
stratum_S01 Tileset, parallax, EN S01, WD01, BS01, nhạc S01 install-time 12
stratum_S02 … S02 (WD02, BS02) install-time 12
hero_H02 Aldric (quà B10) install-time 5
hero_H03..H08 Mỗi anh hùng 1 group: body sheet, move JSON đã nén, VFX chữ ký, SFX fast-follow (PAD) 30
stratum_S03..S10 Mỗi Địa Tầng 1 group fast-follow (PAD) 96
skins_ Trang Phục (1 group/anh hùng) fast-follow (PAD) — on-demand khi > 20 skin 24
cinematic FirstLaunchCinematic, BossIntro art lớn install-time 6
Tổng nội dung install-time 73 MB (+ code/SDK ≈ 70 MB) 223

Android: addressables.android ánh xạ group → asset pack PAD (install-time/fast-follow). Chưa tải xong group cần dùng → chặn ở cầu thang với tiến độ (R14). iOS: mọi group nằm trong IPA (không ODR). Không remote catalog ở v1.0 (offline-first); dành cho v1.1+.

17Headless autosim

Unity -batchmode -nographics -projectPath . -executeMethod Deepstair.Run.Editor.AutoSimCli.Run
      -hero H01 -seeds 1-1000 -profile f2p:d7 -policy autopick:BALANCED -dread 0 -startFloor 1 -maxFloor 100
      -speed max -threads 8 -perFloor -out Builds/autosim/H01_f2p_d7 -quit
Tham số Ý nghĩa
-hero H01…H08 hoặc all
-seeds Dải seed; seed i luôn cho cùng run bất kể -threads
-profile Hồ sơ meta của 08 (f2p/dolphin/whale : dN) → RunSetupService.BuildFromProfile() dựng save ảo rồi đi đúng đường tính chỉ số của game thật
-policy autopick:<preset> · random (fuzz Hồi Âm) · fixed:<file> (build cố định)
-dread, -startFloor, -maxFloor, -mode Bậc, Xuống Nhanh, Vực Không Đáy, Thử Thách Ngày
-perFloor, -routes, -hash Xuất thêm CSV theo tầng, theo lộ trình, RunHash

Đầu ra: runs.csv (seed, hero, profile, policy, floor_max, cause, enemy gây gục, time_game_s, kills, attacks, deflects, perfect, blocks, evades, hits, clashes, counters, executions, kết quả từng Boss/Thủ Vệ + thời gian, echoes, build_cluster, unique_routes, top_route_share, gold, soul_ash, exp, mastery_xp), floors.csv, routes.csv (số lần chạy, DPS factor thực — kiểm 03 §10 ±10%). Hiệu năng: ≥ 150× thời gian thực mỗi luồng trên PC dev (1,000 run B1–B100 ≈ 25 phút với 8 luồng). Hiệu chỉnh: balance_sim.py --calibrate Builds/autosim/*.csv (08) khớp hệ số sức mạnh anh hùng; cổng M2: sai số tầng chết trung vị ≤ 3 tầng. Autosim không cần scene, art, audio.

18Kiểm thử

18.1 Tự động

Bộ Loại Nội dung
DamageServiceTests EditMode Công thức 01 §4.1–4.3 (crit, DEF, vuln, guard, chặn, sàn 15%), trần 01 §3.1, StatSheet 4 lớp
DefenseRollDistributionTests EditMode Mỗi dòng TechDefenseCases.csv: 10,000 roll, mọi kết quả
DefenseTimingTests EditMode Mọi clip phòng thủ × hit tick × AtkSpeed 0.5–2.0: lỗi khung chạm = 0 tick; chuỗi tick ở x1 = x3 (hash)
OfferDeterminismTests EditMode 1,000 seed × 30 tầng × reroll 0–3: cùng lượt chọn; cấu trúc ô A/B/C; pity lượt 6; EVO chiếm ô A; Save gọi trước Show
EchoDslParseAllTests EditMode Mọi effect của HeroEchoes.csv + EchoesShared.csv parse, ký hiệu giải được, in → parse lại bằng nhau
CondParseTests, CsvSchemaTests, RngVectorTests, TickQuantizationTests EditMode cond lộ trình; header CSV = parser sinh; vector chuẩn SplitMix64/xorshift128+; bảng tech_ticks
SaveMigrationTests, SaveRecoveryTests EditMode v0→v1 với fixture; HMAC sai → bak1 → bak2 → mặc định
SnapshotRoundTrip, RestoreEquivalence, Determinism1000 EditMode (batch) §5.7
CombatSandboxTests PlayMode Kaito vs hình nộm theo kịch bản từng loại đòn: kết quả hiện đúng tick; không actor ngoài biên; token ≤ trần
GcAllocTests, PixelViewportTests PlayMode 0 B/600 frame; 1080×1920, 1080×2400, 720×1600, 1620×2160 → refResolution đúng, không tràn ngang
Autosim smoke Batch 50 seed × 8 anh hùng × B1–B20: 0 exception, 0 NaN, 0 kẹt khựng > 1.5 s

CI (máy Mac runner sẵn có + Windows dev): mỗi push lên nhánh làm việc chạy EditMode + autosim smoke; hằng đêm Determinism1000 + autosim 1,000 seed → so với lần trước.

Vector kiểm phòng thủ (trích TechDefenseCases.csv, đầy đủ trong CSV):

Case Hero Gạt/Chặn/Né gốc Loại đòn Trạng thái Quy tắc Gạt Chặn Né Trúng
SYN_CAP_AVOID — 50%/0%/40% light free — 38.89% 0.00% 31.11% 30.00%
SYN_CAP_TOTAL — 45%/45%/20% light free — 36.82% 36.82% 16.36% 10.00%
SYN_CAP_STAT — 75%/70%/65% heavy free — 34.36% 27.00% 28.64% 10.00%
SYN_ZERO — 0%/0%/0% thrust free — 0.00% 0.00% 0.00% 100.00%
SYN_MIKIRI — 20%/10%/10% thrust free mikiri 20.00% 0.00% 10.00% 70.00%
SYN_GRAB_BREAK — 10%/30%/10% grab free grab_break 0.00% 15.00% 12.00% 73.00%
SYN_SNAP_GUARD — 20%/20%/10% light attacking snap_guard 10.00% 10.00% 6.00% 74.00%
SYN_AIR_PARRY — 30%/10%/20% light air air_parry 30.00% 0.00% 16.00% 54.00%
SYN_ARMOR — 30%/30%/20% heavy armor — 0.00% 0.00% 0.00% 100.00%

18.2 Thủ công & thiết bị

Ma trận: Android Low (Helio G85, 3–4 GB), Android Mid (Helio G99), Android High (Pixel 7), iPhone 11, iPad 9 (3:4, pillarbox). QA agent /auto-test-device theo charter từng feature; bug tự lên BugHub.

19Cheat (skill feature-cheat, chỉ khi GameSystems.isCheat)

Cộng mọi MoneyTypes · đặt kỷ lục/first-kill · mở mọi anh hùng, Thông Thạo 20, mọi nhóm Hồi Âm · thêm/nâng Hồi Âm theo id trong run · ép kết quả phòng thủ N đòn kế (Gạt/Hoàn Hảo/Chặn/Né/Trúng/Va Kiếm) · bất tử · hạ toàn bộ địch · nhảy tầng · spawn địch/Thủ Vệ/Boss theo id · overlay hitbox + timeline phòng thủ (commit/contact/hit) · sim x10 · chạy N autosim trên máy · dump snapshot · reset FTUE · đặt tier · làm hỏng save (test phục hồi) · dịch giờ TimeManager. Bản release: chỉ máy được duyệt qua BugHub (TesterAccess).

20Build & phát hành

  • Android: AAB + PAD, IL2CPP ARM64, minSdk 26/target 36, hỗ trợ trang 16 KB (Unity 6), ký qua AndroidSigningFromEnv; iOS: Xcode từ Mac runner.
  • Phiên bản: VersionIncrementor (Core/Build) — bundleVersion 1.0.N, versionCode tăng mỗi build; SimVersion (hằng trong Sim) tăng khi đổi logic sim (khóa golden + snapshot).
  • Pipeline: skill /build (Android/Release → Android/AutoBuild → runner build + deploy). Symbol IL2CPP lên Crashlytics; BugLogger gắn commit sha.
  • Track: internal → closed (tester) → soft launch (VN, PH, CA) → production staged 10% → 50% → 100%. Cập nhật nội dung = cập nhật app ở v1.0.
  • Store: phân loại tuổi khai "In-game purchases (includes random items)"; trang công khai tỉ lệ Triệu Hồi/Rương; chính sách quyền riêng tư.

21Rủi ro kỹ thuật

Rủi ro dự án tổng hợp ở 14 §8 (R4, R9, R11, R14); riêng kỹ thuật:

Rủi ro Giảm thiểu
Khung chạm phòng thủ trôi khi hit-stop/slow chồng Đòn địch Committed + clip phòng thủ miễn hit-stop; assert 0 tick
PixelPerfectCamera URP + Upscale RT với zoom/camera stack Không stack; zoom bằng đổi refResolution nguyên; spike ở M0
Reflection save (TypeNameHandling.All) bị strip/đổi namespace link.xml sinh tự động, MovedTypeSerializationBinder, đóng băng namespace kiểu save
Snapshot ghi giữa trận gây giật Nhị phân 4–8 KB vào buffer pool; mã hóa + IO trên thread pool
/run-backlog worktree không compile được Task Sim/Pixel chạy chế độ current có Unity MCP; worktree chỉ cho task dữ liệu

22HANDOFF — giả định module khác phải tôn trọng

  • 02 Pixel pipeline: export đúng §9.1 (khung khóa vào tick hit/contact, startTick từng khung, root motion theo khung, cờ guard key); contact = 9 tick cho Gạt/Chặn; trần ô khung §16.3; VFX recolor lúc export; PPU 16.
  • 03/Heroes: mọi hit t là bội 1/60 s sau khi đổi tick (validator báo lệch > 1 ms); mỗi rule <CODE>_* đặc tả đủ để viết IHeroRule; ≤ 64 lộ trình/anh hùng.
  • 04 Địch/Boss: mọi đòn khai AttackKind + lấy đà tối thiểu; đạn tốc độ hằng (cam kết tất định); pattern Boss là dữ liệu, chỉ rule có tên mới cần code; khai bán kính + chiều cao thân.
  • 05 Run: snapshot chứa đủ trường §11.5; checkpoint wave = tick không còn địch sống; mọi ngẫu nhiên qua luồng §5.5 (thêm luồng → thêm vào constants.RNG_STREAMS + tech.py).
  • 06 Hồi Âm: effect theo ngữ pháp 06 §8.1, chỉ dùng op/sự kiện/rule đã liệt kê; số có đơn vị %/s; ICD ≥ 1 tick.
  • 07–10 Meta/Kinh tế/Kiếm tiền/LiveOps: dùng đúng id §13; mảnh trong ví; kết quả gacha ghi save trước animation; khóa RC ảnh hưởng sim khai cờ "Chụp"; event analytics thêm vào tech.py (validator Firebase); không điểm IAP trong run (INV-08).
  • 11 UI: canvas Overlay 1080×1920; World UI vẽ trong RT pixel; quyết định trong run đi qua IRunPrompt; nút tương tác trong run ở 45% dưới (INV-07).
  • 12 Art/Audio: SFX/nhạc theo id int (AudioSfx.csv/AudioMusic.csv), VFX theo ArtVfxMap.csv, đèn theo ArtStrata.csv và trần tier.