CatGunner CookbookBardCat 1.1.59 · L1 ANALYSIS · tài liệu học
Trang chủCookbook › 06 — Pet & nhân vật (đàn mèo leader/follower, bầy pet boids, Spine)
Nguồn: 06-pet-nhan-vat.md · Cập nhật lần cuối: 2026-09-06 23:06 +07 · sha256 nguồn bdc3f6250a6d

06 — Pet & nhân vật (đàn mèo leader/follower, bầy pet boids, Spine)

Nguồn framework: docs/framework/05-characters-pets-enemies.md §2.1, §2.2, §3.1–3.3, §4, §5; docs/framework/04-sound.md §3.5 (cờ is_Shoot_Mute); docs/framework/08-architecture-and-scale.md §2.4 (Spine, atlas). Cấp bằng chứng: L1 ANALYSIS — dựng lại clean-room từ disasm + metadata + quan sát runtime; không phải source gốc. Mỗi dòng bên dưới mang nhãn bằng chứng riêng.

Mục tiêu

Muốn màn hình idle đông và sống: một mèo người chơi, vài mèo AI bám theo như đoàn rắn, một bầy pet lượn quanh không giẫm lên nhau — mà không cần pathfinding hay NavMesh. CatGunner làm bằng hai class flocking chạy nhiều nhất toàn game (Pet_Ingame_manager 20 038 lời gọi / 40 s, Cat_manager 9 577) cộng một Cat ~100 field. Trang này gom đúng các hằng số và công thức đã đọc được, và nói rõ chỗ nào artist cần chuẩn bị asset Spine.

Game gốc làm gì

Mèo — leader + follower

Điều game làm Con số / hằng số Mức bằng chứng Nguồn
Cat_manager.Cat_list[0]mèo người chơi, còn lại là AI follower — ghi trong tooltip tiếng Hàn của chính source "0번 = 메인 플레이어 / 나머지 = AI 팔로워" metadata-proven doc 05 §2.1, dump.cs:593380
Cat_manager chỉ có 3 tham số đội hình FormationRadius @0x28, SeparationRadius @0x2C, SeparationStrength @0x30 metadata-proven doc 05 §2.1
Cat_manager 11 method Awake, Start, Update, ComputeSeparation, SetupFormation, AddFollower, RemoveFollower, Balance_Reload, Warp, Skin_Reload, .ctor metadata-proven doc 05 §2.1
Separation chạy mỗi frame Update 4 776× và ComputeSeparation 4 776× / ~40 s runtime-observed doc 05 §2.1
Cat_manager.is_Shoot_Mute static bool @0x0 — Mute_Panel ghi khi panel mở 19 Mute_Panel trong scene metadata-proven + scene-census doc 04 §2/§3.5
Cat (TypeDefIndex 8154) là class lớn nhất của gameplay ~100 field, 50+ method, máy trạng thái Cat.AIState riêng metadata-proven doc 05 §2.1, dump.cs:593002
Follower đi theo breadcrumb (vệt đường leader) static readonly List<Vector3> Breadcrumbs, RecordDist = 0.5, MaxCrumbs = 100, CrumbLookahead = 5 metadata-proven doc 05 §2.1
Follower có lịch sử vị trí có độ trễ Queue<Cat.PosRecord> _leaderHistory, HistoryDuration = 2 giây, GetLaggedLeaderPos() metadata-proven doc 05 §2.1
Chống rung trạng thái [Range(0.05,0.3)] HysteresisMargin, [Range(0.1,1)] MinStateDuration, _stateTimer, EvaluateDesiredState(leader, distToLeader) metadata-proven doc 05 §2.1
Làm mượt vận tốc [Range(2,20)] VelocitySmoothSpeed, _smoothVelocity, DecelerationRange, MinSpeedRatio metadata-proven doc 05 §2.1
Fidget khi đứng yên AIFidget(), _fidgetTimer, _fidgetInterval, _fidgetOffset metadata-proven doc 05 §2.1
Hệ spread (tản đạn) động SpreadAngleMin, SpreadAngleMax, [Range(0.1,0.6)] SpreadGrowthRate, SpreadDecayMultiplier, _spreadAngle; UpdateSpreadState RVA 0x2AE80B0; tooltip gốc: góc tản nhỏ nhất = tốc độ đánh chậm nhất metadata-proven doc 05 §2.1/§3.2
Field debug read-only ngay trong inspector [Header("Spread Debug (Read Only)")] Debug_SpreadAngle / Debug_SpreadMaxAngle metadata-proven doc 05 §3.2
Hằng số camera / hình học CameraYAngle = -45, CamPitchSin = 0.57735026 (= 1/√3, nghiêng 30°), HandBasicAngle = 130, BulletSpawnY = 2, MonsterMoveRadius = 14 metadata-proven doc 05 §2.1
Auto-move riêng cho từng nội dung HandleAutoMove, FindNearestTreeByGrid, FindNearestTreeSpeedRun, FindNearestOreMine, FindBossTarget, OverlapSphereNearestOre metadata-proven doc 05 §2.1
Ngắm bằng raycast FindNearestEnemyByRaycast (RayCount 8..72) → ResolveAttackTargetAttack metadata-proven doc 05 §3.1
Tốc độ đánh Cat.CalculateAtkCoolMax(baseAtkSpeed, upgradeValue_Disable, isMainStage, isRaid = false) RVA 0x2AE5CB8 metadata-proven doc 05 §2.1
Cat_manager.Balance_Reload chỉ chạy khi cân bằng lại (không mỗi frame) Start 9×, Balance_Reload runtime-observed doc 05 §2.1
Đợt 3 — 21/67 method của Cat đã dựng lại (ctor, cctor, Awake, Start, Skin_Reload, Balance_Reload, cooldown, Update, ngắm, hướng mặt); OnDrawGizmosSelected (editor-only) cố ý bỏ 42 160 B / 67 method, 5 batch disasm-proven doc 05 §2.1 (G352, G344)
Mặc định ctor của Cat (khởi tạo field bị công cụ decompile bỏ rơi) moveSpeed = 5, Attack_Length = 5, Raycast_Cool_Max = 0.1, RayCount = 36, AutoOreSearchRadius = 25, OreOverlapBufferSize = 64, AutoNearbyRadius = 8 (+ 27 khởi tạo khác) disasm-proven doc 05 §2.1 (G344, G352)
Hằng static của Cat (thiếu ⇒ ngắm/hướng mặt lệch 45°) BoxHalfExtents (0.3, 0.4, 0.3), LeftAngle (40, −45, 0), RightAngle (−40, 135, 0), CamForward = Euler(40, −45, 0)·forward = (−0.5417, −0.6428, 0.5417) (đính chính G400 — bản trước thiếu pitch 40°, và đó chính là lý do đạn không trúng, xem 04), CamCorrection = Euler(0, 45, 0), MonsterMapCenter (−100, 0, 0), ScreenSpeedK = 1.2 disasm-proven (re-derived) doc 05 §2.1 (G352, G400)
Singleton Cat.instance chỉ là mèo người chơi: Awake gán if (!is_AI) instance = this — follower AI không ghi đè disasm-proven doc 05 §2.1 (G352)
Góc tản tối đa là ánh xạ log theo tốc độ đánh SpreadMaxAngle = log(AtkCool_Max / 0.05) / log(40) (chuẩn hoá dải cool 0.05..2 s) disasm-proven doc 05 §2.1 (G352)
Pipeline cân bằng rẽ theo chế độ (Cat.Balance_Reload): Mine = damage phẳng 1, CriPer = 0; Boss / Hunt / Raid = damage súng thô (không nhân upgrade vĩnh viễn), Raid bỏ cả Rebirth; Main nhân đôi moveSpeed khi is_Clear_Stage 4 nhánh disasm-proven doc 05 §2.1 (G352)
Tốc độ đánh nhanh dần theo tiến độ: CalculateAtkCoolMax trừ 30 mỗi stage; IsOnScreen mở (true) khi không có camera; FindNearestTreeByGrid cắt tỉa theo dải Z của lưới, cây boss cạnh tranh ở Level 4 −30 / stage disasm-proven doc 05 §2.1 (G352)
Cat_manager ctor: warpOffsets = {(−6, 0, −6.4), (6, 0, −6.4)} (thiếu ⇒ NRE Warp), FormationRadius = 10 / SeparationRadius = 1.8 / SeparationStrength = 3 — nhưng scene serialize FormationRadius = 2.5scene thắng; Pet_manager.AutoMerge_Degree = 6, Pet._currentAnim = "" disasm-proven + scene-YAML doc 05 §2.1, doc 11 §1.1 (G306)
Follower đi theo VỆT leader, không đuổi theo điểm: lịch sử leader static dùng chung (2 s), mỗi follower đọc ở độ trễ riêng 0.15–0.6 s rút theo instance id ⇒ đoàn rắn tất định, không cần offset đội hình phức tạp 2 s / 0.15–0.6 s disasm-proven doc 05 §2.1/§3.1c (G364)
Hai lớp chống rung độc lập: nới băng trạng thái đang giữ (HysteresisMargin) MinStateDuration 2 disasm-proven doc 05 §3.1c (G364)
AIAssist xích ở 0.85 × FollowMaxDist; ở chế độ Hunt không có leader — mỗi mèo một trạm trên vành bán kính 14 0.85 / 14 disasm-proven doc 05 §2.1 (G364)
Save mới chỉ thấy MỘT mèo — đúng: Cat_manager.Balance_Reload bật một mèo cho mỗi súng đang trang bị; save mới trang bị 1 ⇒ 1 mèo; xem follower cần ≥ 2 súng trang bị (chơi thật) Selected_items = [0, −1, −1] disasm-proven + scene-YAML doc 05 §2.1 (G364)
Cat hoàn tất (65/67, chỉ gizmo editor-only bỏ): finder quặng mở rộng dần, buffer non-alloc 64, Warp dời rigidbody, damage kỳ vọng boss trộn crit 65/67 disasm-proven doc 05 §2.1 (G369)

Pet — bầy boids

Điều game làm Con số / hằng số Mức bằng chứng Nguồn
Pet_Ingame_manager là class chạy nhiều thứ hai toàn game 20 038 lời gọi / 40 s, 11 method runtime-observed doc 05 §2.2
Vòng lặp mỗi bước vật lý FixedUpdate 4 006× → GetMainCat 4 007×, ComputePetSeparation 4 006×, ComputeCatAvoidance 4 006×, MovePets 4 006× runtime-observed doc 05 §2.2
GetMainCat() = Cat_list[0] nếu có disasm-proven doc 05 §2.2, _evidence/Pet_Ingame_manager/README.md
Tốc độ mèo được làm mượt hàm mũ _smoothedCatSpeed += (inst - _smoothedCatSpeed) * 0.12f disasm-proven doc 05 §2.2/§3.3
Tham số bầy (offset đã xác minh) FormationRadius @0x28, FormationSpreadDeg @0x2C, StartMoveDistance @0x30, StopMoveDistance @0x34, MinSpeed @0x38, MaxSpeed @0x3C, FullSpeedDistance @0x40, CatSpeedMultiplier @0x44, PetSeparationRadius @0x48, PetSeparationStrength @0x4C, CatAvoidRadius @0x50, CatAvoidStrength @0x54 metadata-proven doc 05 §2.2
Pet giữ 3 vector lực [HideInInspector] SeparationForce @0x40, CatAvoidanceForce @0x4C, FormationOffset @0x58, IsMoving @0x64, _currentAnim @0x68 metadata-proven doc 05 §2.2, dump.cs:597811
Pet._animSkeletonAnimation (Spine), không phải Animator 5 method: Reload(int SkinNum), Warp(Vector3), SetAnimation(string), SetFacing(Vector3), .ctor metadata-proven doc 05 §2.2
Spine runtime 4.1.24, định dạng JSON 13 skeleton + 13 atlas, đường ảnh gốc ./Pet/ asset-census doc 05 §2.2, asset_lane.md §0
13 pet Acorn, Bee, BlackCat, Chicken, Dove, Frog, Ghost, Griffin, Pigeon, PillowGhost, PinkRabbit, Slime, Slime2 — tất cả "spine": "4.1.24" asset-census doc 05 §2.2
Tên dự án nội bộ đọc từ header skeleton BardCat asset-census doc 05 §2.2
Không có NavMeshAgent trong Cat / Pet / Monster / Boss — mọi di chuyển thủ công qua Rigidbody + lực package com.unity.ai.navigation có trong manifest nhưng không dùng ở các class này metadata-proven (negative) doc 05 §5
Sprite mèo nằm trong atlas riêng atlas Cat 125 sprite, 4096×4096 ASTC 6x6 asset-census doc 08 §2.4

Công thức / pseudocode

// ---- L1 ANALYSIS (có RVA / evidence) ----

// Pet_Ingame_manager.FixedUpdate — disasm-proven, chạy 4 006x/40s
Cat mainCat = GetMainCat();            // Cat_manager.instance.Cat_list[0]
if (mainCat == null) return;
Vector3 pos = mainCat.transform.position;
float inst = Vector3.Distance(pos, _prevMainCatPos) / Time.fixedDeltaTime;
_smoothedCatSpeed += (inst - _smoothedCatSpeed) * 0.12f;      // lọc thông thấp bậc 1 (~8 bước vật lý)
_prevMainCatPos = pos;
ComputePetSeparation();
ComputeCatAvoidance();
MovePets(mainCat);

// Ba lực — thuật toán đã giải mã, CHƯA xác minh byte-exact (G131, xem UNKNOWN)
// 1. tách nhau giữa pet (từng cặp i<j, chỉ pet đang active)
d = Pets[i].pos.xy - Pets[j].pos.xy;  dist = |d|
if (0.001 <= dist <= PetSeparationRadius) {
    f = normalize(d) * ((1 - dist / PetSeparationRadius)^2 * PetSeparationStrength)
    Pets[i].SeparationForce += f;  Pets[j].SeparationForce -= f
}
// 2. né mèo (mỗi pet, với mỗi mèo đang active)
d = pet.pos.xy - cat.pos.xy;  dist = |d|
if (0.001 <= dist <= CatAvoidRadius)
    pet.CatAvoidanceForce += normalize(d) * ((1 - dist / CatAvoidRadius)^2 * CatAvoidStrength)
// 3. MovePets
    FormationOffset + SeparationForce + CatAvoidanceForce
    + hysteresis Start/StopMoveDistance
    + kẹp [MinSpeed, MaxSpeed], FullSpeedDistance
    + CatSpeedMultiplier * _smoothedCatSpeed
    -> rb; điều khiển Spine (IsMoving / _currentAnim)
// ---- Auto-move của mèo (batch C, disasm-proven, dự đoán khớp thiết bị — G356/G358) ----
HandleAutoMove (mode-aware):
    Main     : cây gần nhất theo lưới; đi tới, DỪNG CÁCH 8 đơn vị; ~14.4 u/s (tới cây cách 27 u trong ~1.4 s)
    SpeedRun : khoá mục tiêu bắn      Raid : mục tiêu = boss
    Monster  : đứng trên VÀNH đấu trường bán kính 14; lùi khi vào trong MinEnemyDist tới +0.5
ScreenSpeedMultiplier: stick.x / ScreenSpeedK ; stick.z * ScreenSpeedK   // triệt tiêu foreshortening iso (CamPitchSin 0.577) — BẤT ĐỐI XỨNG
Breadcrumbs (static, dùng CHUNG cả đàn): leader thả 1 vệt / 0.5 u, giữ 100; follower nhắm vệt thứ 5 phía trước

// ---- AI follower (batch D, disasm-proven — G364) ----
RecordLeaderHistory: leader đẩy (pos, t) vào lịch sử STATIC dùng chung, cắt còn 2 s
GetLaggedLeaderPos : follower đọc vị trí leader tại t - _lagSeconds, _lagSeconds ∈ [0.15, 0.6] s rút 1 lần/instance (tất định theo instance id)
EvaluateDesiredState: nới băng trạng thái ĐANG GIỮ thêm HysteresisMargin      // chống rung lớp 1
HandleAI            : giữ trạng thái ít nhất MinStateDuration                    // chống rung lớp 2
AIAssist            : xích ở 0.85 × FollowMaxDist
HandleAIMonster     : không leader — mỗi mèo một TRẠM trên vành đấu trường bán kính 14

// ---- Finder & tiện ích (batch E, disasm-proven — G369) ----
FindBossTarget      : không tìm — một mục tiêu duy nhất
FindNearestOreMine  : bán kính -> bán kính × hệ số fallback -> quét toàn Ore_list (mở rộng dần)
OverlapSphereNearestOre: non-alloc vào buffer 64 ô cấp sẵn trong ctor
Warp                : dời cả rigidbody, không chỉ transform
ExpectedDamage_Boss_return: bỏ Power_return; trộn thường/chí mạng theo tỉ lệ crit = kỳ vọng mỗi phát

// ---- Cấu trúc đàn mèo (tóm tắt từ metadata) ----
Cat_manager (tầng ĐÀN)     : Update -> ComputeSeparation (mỗi frame); SetupFormation; Add/RemoveFollower; Warp; Skin_Reload
Cat (tầng CÁ THỂ)          : AIState Follow / Chase / AttackStay / Reposition / Assist / Fidget  (suy từ tên method, enum chưa trích)
    breadcrumb             : leader thả vệt mỗi RecordDist = 0.5; giữ MaxCrumbs = 100; follower nhắm điểm thứ CrumbLookahead = 5
    lịch sử trễ            : _leaderHistory, HistoryDuration = 2 s, GetLaggedLeaderPos()
    chống rung             : đổi trạng thái chỉ khi vượt HysteresisMargin và đã ở trạng thái >= MinStateDuration
    spread                 : bắn -> _spreadAngle tăng theo SpreadGrowthRate; ngừng -> giảm theo SpreadDecayMultiplier
// ---- Gợi ý framework ----
IFlockManager  : đội hình + lực tách nhau + thêm/bớt thành viên (chạy mỗi frame / bước vật lý)
IFlockMember   : máy trạng thái riêng, nhận lực, tự di chuyển
HysteresisThreshold { margin; minStateDuration }        // dùng cho MỌI ngưỡng chuyển trạng thái
LowPass(k = 0.12) cho mọi tín hiệu tốc độ đầu vào       // hằng số cảm-giác đã ship
Falloff(d, r) = (1 - d/r)^2, tính trong 2D               // mềm ở rìa, đẩy mạnh khi sát

"Bể" thực thể trang trí — pool cố định, rải chỗ ngẫu nhiên, sắp lớp hai trục (doc 05 §4 mục 11–13)

Đây là một hệ khác với đàn boids ở trên: không có lực, không có vật lý, thực thể chỉ bơi lượn trong một khung UI và là hình ảnh của kho đồ. Bể cá 20 ô của game gốc dùng đúng ba mẫu sau, và cả ba đều rẻ tới mức đáng chép nguyên.

1) Pool cố định bằng đúng trần kho — không Instantiate/Destroy lúc chạy.

Awake:   for each slot in pool: slot.Init(this); slot.Deactivate()      # pool dựng sẵn trong scene
Thêm:    it = GetInactiveItem();  if (it == null) return                # HẾT CHỖ = trần kho tự thực thi
         it.Reload(grade, invIndex); it.SetPosition(FindEmptyPosition(it)); SortByGrade()
Xoá:     it.Deactivate()                                                # chỉ tắt object, không huỷ

Không GC spike khi người chơi rút liên tục, và trần kho không cần một if riêng — kích thước mảng chính là trần. Đổi lại: pool và hằng trần phải cùng đọc một const.

Kèm theo: chỉ số kho là dữ liệu, tham chiếu item là tạm bợ. Sau mỗi lần xoá, gán lại chỉ số cho mọi item đang sống bằng cách bắt cặp theo hạng với một mảng used (mỗi ô kho dùng đúng một lần); item không khớp được thì để -1. Đừng nhớ item bằng tham chiếu qua nhiều frame.

2) Rải chỗ trống: "thử N lần, giữ chỗ thoáng nhất" — không lưới, không giải va chạm.

best = randomPoint(); bestDist = -1                       # SEED bằng điểm ngẫu nhiên, KHÔNG bằng (0,0)
for i in range(maxAttempts):                              # 30
    p = randomPoint()
    d = distanceToNearestActive(p, exclude = self)
    if d >= minDistance: return p                         # 100 px — đủ thoáng, NHẬN NGAY
    if d > bestDist: bestDist, best = d, p
return best                                               # không có nhánh thất bại

Ba tính chất khiến nó đáng chép: luôn trả về một điểm hợp lệ kể cả khi bể chật (chất lượng suy giảm mượt thay vì hỏng); chi phí có trần cứng O(maxAttempts × N); hai tham số là núm chỉnh cảm giác, không phải hằng số kỹ thuật. Kèm khuôn kẹp khung: if (min > max) { min = max = 0 } — thực thể to hơn khung thì ghim về tâm, đừng để Random.Range ngược biên.

3) Sắp lớp bằng MỘT thang order mang HAI trục + một dải "luôn trên cùng" nằm ngoài Clamp.

order = Clamp(round(yOrigin - y) + step * (maxGrade - grade), 0, 2000)   # step = 10
if isDragging:               order = 9999      # vật thể người chơi đang cầm
elif isAnimatingToTarget:    order = 9998      # vật thể đang trong hoạt ảnh

Trục hạng chiếm bước 10, trục Y chiếm bước 1 ⇒ hạng vẫn thắng, nhưng trong cùng hạng ai ở thấp thì nổi lên trên (phối cảnh nhìn ngang). Hai giá trị đặc biệt nằm ngoài Clamp nên không bao giờ bị trục nội dung đuổi kịp.

Và một quyết định thẩm mỹ nghe rất ngược: sắp hierarchy theo hạng giảm dần ⇒ thực thể bậc CAO (sprite to) bị bậc THẤP vẽ đè. Lý do: một con to không được nuốt mất mặt tiền của cả đàn nhỏ — người chơi vẫn phải đếm được tài sản của mình. Thực thể đang tắt thì đẩy xuống cuối hierarchy để không chiếm chỗ sibling.

4) Bơi trang trí, rẻ mà có nhịp: tốc độ Random(15, 35) px/s, đổi hướng mỗi Random(2, 5) s, hướng mới x = ±1 (50/50) và y ∈ [-0.3, 0.3] rồi chuẩn hoá ⇒ luôn bơi gần như ngang, chỉ chếch nhẹ. Chạm mép ngang thì đảo x và lật hình; chạm mép dọc thì đảo ykhông lật (không con cá nào lộn ngược khi chạm đáy). Cập nhật order mỗi frame theo Y.

Bẫy đã gặp

  • Toán NEON của 3 method flocking chưa xác minh byte-exact (G131). Thuật toán đã giải mã nhưng ánh xạ lane vector → thành phần và cách cộng dồn lực chưa được oracle xác nhận. Flock-oracle đã chạy nhưng pet không active trong phiên early-game nên mọi lực = 0 — bằng chứng phủ định hợp lệ, không phải xác nhận. Không ship ở mức 1:1 khi chưa xác minh (doc 05 §5).
  • IDA đặt sai biên hàm cho Cat_manager.ComputeSeparation / SetupFormation (decompile ra thân Update/Awake) — phải del_func + add_func tại đúng RVA trước khi đọc (G130).
  • Dùng tốc độ tức thời thay vì tốc độ đã làm mượt ⇒ pet giật mỗi khi người chơi nhả joystick. Hệ số 0.12 là hằng số cảm-giác quan trọng nhất của hệ pet (doc 05 §3.3).
  • Follower đi thẳng tới leader thay vì đi theo vệt ⇒ đi xuyên tường và trông như đám xúm lại thay vì đoàn rắn; CrumbLookahead = 5 là thứ cho phép cắt góc nhẹ (doc 05 §3.1).
  • Không có hysteresis ⇒ follower đứng đúng ranh giới khoảng cách nhấp nháy giữa hai trạng thái mỗi frame; tên field tiếng Hàn 버벅거림 방지 ("chống giật") cho thấy đây là bản vá cho bug thật (doc 05 §3.1).
  • is_Shoot_Mute là bool toàn cục, không phải counter — hai panel Mute_Panel cùng mở, một cái đóng ⇒ cờ về false dù panel kia còn mở. An toàn trong SOT vì UI một-panel-một-lúc; framework nên dùng counter (doc 04 §3.5).
  • Đổi tên field Spine / lệch version runtime — SOT dùng spine-unity 4.1 và SkeletonData export bởi Spine 4.1.24; runtime phải khớp version editor đã export (quy tắc dự án (nội bộ) R2).
  • Khởi tạo field bị công cụ decompile bỏ rơi. warpOffsets null ⇒ NRE ngay Warp; các hằng static của Cat thiếu ⇒ hướng mặt lệch 45° mà không ném gì. Đọc .ctor/.cctor trước khi dựng method (G306, G352; doc 08 §3.6).
  • Ctor default ≠ giá trị ship với field serialize: FormationRadius ctor = 10, scene = 2.5. Với field public/serialize, scene thắng; ctor chỉ áp khi AddComponent lúc chạy (G306, G364; doc 11 §1.1). SeparationRadius 1.8 / Strength 3 thì hai nguồn trùng nhau. Bẫy này lặp lại và tốn hơn ở một chỗ khác: bảng tỉ lệ gacha của hệ Cá — ctor {40,30,20,10}, scene {60,30,10,0} — đã bị chép từ ctor vào tài liệu và publish trước khi bị bắt (G1682). Luật đọc: public/[SerializeField] có trong scene ⇒ scene thắng; private không [SerializeField]ctor là giá trị thật, scene không cứu được; const ⇒ ctor thắng. Kiểm loại field trước khi tin con số.
  • Setter khởi động coroutine thừa hưởng stub của coroutine. Bounce_Position.Bounce_Start (giật lùi súng) được coi là cosmetic, nhưng Cat.Attack gọi nó mỗi phát và coroutine bên trong là return null ⇒ ship setter một mình là giết đường bắn (G369).

Checklist khi tự làm

  • [ ] Tách manager (quan hệ giữa cá thể) khỏi cá thể (hành vi riêng); manager ~10 method là đủ.
  • [ ] list[0] = người chơi, phần còn lại = AI — một quy ước duy nhất, ghi thành tooltip.
  • [ ] Follower đi theo breadcrumb (RecordDist, MaxCrumbs, CrumbLookahead) + lịch sử leader có độ trễ.
  • [ ] Mọi ngưỡng chuyển trạng thái có HysteresisMargin + MinStateDuration; mọi ngưỡng khoảng cách có cặp Start/Stop.
  • [ ] Tín hiệu tốc độ đầu vào đi qua lọc thông thấp smoothed += (inst - smoothed) * 0.12.
  • [ ] Lực boids suy giảm bình phương (1 - d/r)^2, tính trong 2D cho game nhìn từ trên.
  • [ ] Fidget khi đứng yên để đàn không thành tượng.
  • [ ] Spread tăng theo nhịp bắn, giảm khi ngừng; spread max phụ thuộc tốc độ đánh (cân bằng tự nhiên).
  • [ ] Field debug read-only trong inspector cho mọi hệ cảm-giác ([Header("... Debug (Read Only)")]).
  • [ ] Pet animate bằng Spine SkeletonAnimation (không Animator), 13 skeleton JSON + 13 atlas, cùng một version runtime.
  • [ ] Artist: mỗi pet = 1 skeleton JSON + 1 atlas, tên project trong header; sprite mèo vào atlas Cat.
  • [ ] Không dùng NavMesh cho đàn/bầy — Rigidbody + lực là đủ và rẻ hơn.
  • [ ] "Bể" thực thể trang trí: pool cố định bằng đúng trần kho, GetInactiveItem() trả null = hết chỗ; không Instantiate lúc chạy.
  • [ ] Chỉ số kho gán lại sau mỗi lần xoá (bắt cặp theo hạng + mảng used); đừng nhớ item bằng tham chiếu qua nhiều frame.
  • [ ] Rải chỗ bằng "thử N lần, giữ chỗ thoáng nhất", seed best bằng điểm ngẫu nhiên; không có nhánh thất bại.
  • [ ] Một thang sortingOrder mang hai trục (hạng × bước 10, Y × bước 1) + dải "luôn trên cùng" ngoài Clamp.
  • [ ] Đọc .ctor để lấy hằng khởi tạo của cả pool/Item trước khi dựng method — field private không có [SerializeField] thì scene không cứu.

Điều chưa biết (UNKNOWN)

  • Toán NEON của ComputePetSeparation / ComputeCatAvoidance / MovePetsCHƯA XÁC MINH BYTE-EXACT (G131). Cần phiên có nhiều pet ĐANG hoạt động và tụ lại (mở khoá Pet ở Stage_Value ≥ 110, triệu hồi nhiều pet).
  • Cat_manager.ComputeSeparationCHƯA dựng lại, cần sửa biên hàm IDA (G130) rồi disasm RVA 0x2AEF830.
  • Cathoàn tất 65/67 (A/B/C/D/E; G352–G369). ~~Thân mèo không vẽ trên save đã nạp~~ — ĐÃ ĐÓNG (G374): Skin_Bone_return / Skin_Color_return / is_Unlock (skin 0 luôn mở) là stub ⇒ sprite thân null + tay trong suốt; đã dựng, chờ build kế. Hành vi follower chưa quan sát được trên thiết bị vì cần save có ≥ 2 súng trang bị.
  • Tham số đội hình Cat_managerĐÃ ĐỌC (scene: FormationRadius 2.5, SeparationRadius 1.8, SeparationStrength 3; doc 11 §1.1). Tham số boids Pet_Ingame_manager (PetSeparationRadius, StartMoveDistance…) — vẫn UNKNOWN, chưa trích từ scene.
  • Ánh xạ 13 skeleton Spine → pet nào trong game (Pet.Skin_list / _skinNum) — UNKNOWN; cần trích Pet_manager từ scene.
  • Cat.AIState có bao nhiêu trạng thái và giá trị — UNKNOWN một phần; suy được từ tên method, enum chưa trích.
  • Nơi Cat_manager.is_Shoot_Mute được đọc (Cat.Attack RVA 0x2AE9624 / Cat_manager.Update) — UNKNOWN, chưa dựng lại (doc 04 §5).
  • Pet_manager (43 method) — logic mở khoá / khoá slot pet (IsPetLocked, LockOwned, UnlockGroup là khoá do người chơi tự đặt, UNLOCK_GATES §3) — thân chưa khảo sát.
  • Hai GUID script đông nhất trong scene (7 991 và 4 957 instance) không ánh xạ được sang Assembly-CSharp; ứng viên gồm spine-unity — Không được đoán (doc 03 §5, doc 08 §5).

Đọc thêm