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; VFXSprite-Unlitcộng; World UI unlit. - Hiệu ứng trạng thái đổi
sharedMaterialgiữa vài material dùng chung (Mat_Lit,Mat_LitFlashtrắng 1 khung,Mat_OutlineGoldDòng Chảy,Mat_OutlineWhiteVỡ 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 (
freezeTickscủ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ọiStep()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
- Vị trí/vận tốc
floatpx,dthằng1f/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. - 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.csvdo Python tính),DateTime,Guid,System.Random,string.GetHashCode(dùng FNV-1a 64), duyệtDictionary/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. - HP/sát thương
double(Vực Không Đáy vượt 1e15 — hiển thị1.23aa). - 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ôngatan2. - 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
SimChecksummỗ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ànhRunHash.- Golden:
Tests/Golden/determinism.json=RunHashcủa seed 1…1,000 × {H01, H02} × B1–B10, meta profile cố định, Auto-Pick "Cân Bằng". Khóa theoContentHash(hash mọi CSV Gameplay) +SimVersion. Đổi nội dung có chủ đích → chạy menuDeepstair/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 →
RunHashtừ đ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_*) mangcontact(tick). Chuẩn dữ liệu:contact = 9cho 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óck − 9tick đầu.k < 9: anh hùng giữ guard key9 − ktick 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 assertdefense_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;AreaAvoidquyế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ồngdefense).
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ố:
StatSheetgiữ 4 lớp (BaseAdd,BasePercent,TotalAdd,TotalPercent) cho 52RPGStatType, tính lại khi bẩn:Final = ((Base + ΣBaseAdd)·(1+ΣBasePercent) + ΣTotalAdd)·(1+ΣTotalPercent)→ kẹpCAPS. Meta (lớp 1–2) đến trongRunSetup, 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;TriggerTablelập chỉ mục theoEventKind→ 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,
chanceroll trên luồngcombat. HealCap: cửa sổ trượt 60 tick, tổng hồi từ trigger ≤ 2% Max HP/s; ICD mặc địnhburst/wave12 tick/mục tiêu,slowmo240,clone60,heal18.
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 hitHeroHits.csv;durlệ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ặncontact ≠ 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ànhEventManager.EmitEvent(nameof(EventId.X), data).EventIdthêm (giữ quy ước template: chỉ dùng quanameof):
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ằngSaveHub(4.CORE): module vẫn kế thừaDataPlayerBaseGeneric<T>(giữ APILoad/Save/SetupDefaultData), nhưngSave()chỉ đánh dấu bẩn;SaveHubghi một filepersistentDataPath/save/profile.dsvchứ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 quacom.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.bak2cậ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 debouncesave_debounce_s(ProgressThread timer);OnApplicationPause(true)/quit → flush đồng bộ. Không bao giờ trongUpdate(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 (RunSnapshotWritervàobyte[]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ảRunTuningchụ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ả,RunStatstelemetry, cờdetaillấy mẫu. - Điểm ghi: (1)
pendingOffervừ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 = EndedtrướcIRunResultSink.Commit. - Khôi phục sau cập nhật app:
snapVersioncòn hỗ trợ → nạp;contentHashkhá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:
GameplayCsvCodegensinh model + parser C# từ header (không reflection, AOT-safe) vàoSim/Data/Generated/; CSV nhúng dạngTextAssettrong group Addressablescore_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/ |
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/ |
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ý:
- 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.
- 17 đã là
LuckySpinCoin= "Xu Vòng Quay" của 00 → dùng lại, không thêm member. Energy = 3đang được 13 CSV template thưởng (DailyAds, DefaultResource 60, LuckySpin, Quest, SevenDayLogin, GemRefillPack, FirstPurchase, Vip30Day, AccountReward, GloryPass, ResourceExchange, DailyShop, MiniShopConversion) +GeneralConfig.csvcộ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ỏiHomeResourceView;sync_unity_config.pytừ chối dòngres_id 3.- 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ệ.
- Bundle id lệch: ProjectSettings + mọi
pack_idIAP template dùngcom.defaultcompany.deepstair.*→ đổi thànhcom.blackface.deepstair.*(SKU theoIapCatalog.csvcủa 09). - 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,
startTicktừng khung, root motion theo khung, cờ guard key);contact = 9tick cho Gạt/Chặn; trần ô khung §16.3; VFX recolor lúc export; PPU 16. - 03/Heroes: mọi hit
tlà bội 1/60 s sau khi đổi tick (validator báo lệch > 1 ms); mỗi rule<CODE>_*đặc tả đủ để viếtIHeroRule; ≤ 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:
effecttheo 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 theoArtVfxMap.csv, đèn theoArtStrata.csvvà trần tier.