← Deepstair GDD🎞️ Chương 02 · Animation & Pixel

02 — Animation & Pixel Pipeline (pixelfx)

Kế thừa 00 (INV-04, INV-05, INV-14, INV-16), 01 (§2 đấu trường, §5 phòng thủ, §6 schema MoveDef/RouteDef, §11.3 báo hiệu, §12 phản ứng, §14 game feel), 03 (bộ kỹ năng chuẩn). Luật dự án: .claude/rules/art-style.md, hub .claude/docs/ArtStyle.md §1/§3/§4/§7, .claude/rules/image-generation.md. Bảng số sinh từ GDD/Tools/data/art.py (block art_*). Owner chốt: "phong cách pixel sẽ được generate bởi Tools/pixelfx" → 100% sprite nhân vật, địch, VFX sinh bằng code pixelfx (+ phần mở rộng ở §3). Không vẽ tay frame, không AI sinh sprite động.

15 mục 26 bảng ~48 phút đọc Nguồn: 02-Animation-Pixel-Pipeline.md DERIVED công thức/mô phỏng · ASSUMED khởi điểm · BENCHMARK thị trường

1Mục tiêu & thước đo chất lượng

Module rủi ro cao nhất: ~480 clip anh hùng + ~770 clip địch/Thủ Vệ/Boss + ~190 VFX, không có họa sĩ animation. Mục tiêu: đòn đọc được ở 1×, đúng nhịp từng tick, sản xuất bằng dữ liệu.

# Tiêu chí Ngưỡng đạt Đo bằng
Q1 Khớp hit-frame Khung "release/contact" của sprite bắt đầu đúng tick hit của MoveDef ±0 tick; sai lệch art ↔ data ≤ 1 tick lúc export ds-check.mjs (§11) + log hit_frame_error_ticks ở PixelLab
Q2 Khớp phòng thủ Khung contact của clip Gạt/Chặn/Né trùng hit-frame địch ±1 tick ở x1 và x3 (01 §19) log defense_contact_error_ticks
Q3 Không giật pose khi nối đòn Pose tại cancel của đòn A và khung đầu đòn B lệch ≤ 3 px mỗi tay, crouch ≤ 1 px (INV-16) ds-check.mjs
Q4 Chân không trượt Root track mượt theo tick; chân đứng yên khi root không đổi contact sheet + PixelLab
Q5 Silhouette Ở khung release, vũ khí/chi ra đòn tách khỏi thân ≥ 70% chiều dài; nhận ra anh hùng ở thumbnail 128 px (P5) G2 art-style-reviewer
Q6 Nhịp Lấy đà 0.06–0.20 s, hit ≤ 0.08 s sau release, hồi 0.10–0.30 s (01 §6.2) ds-check.mjs từ khóa pose
Q7 Pixel sạch 1 cỡ pixel, viền ink 1 px kín, 0 pixel đơn lẻ trong mảng màu, chỉ màu thuộc LUT costume ds-check.mjs + hub §1
Q8 Hiệu năng Toàn bộ animation + VFX ≤ 1.2 ms/frame (10 địch, 60 đạn, Mid tier); 0 GC trong trận Profiler
Q9 Bộ nhớ Texture trong run ≤ 56 / 44 / 32 MB (High/Mid/Low) §8
Q10 Cảm nhận "Đã mắt" ≥ 4/5 ở playtest 10 người (P1) khảo sát

2Khóa tỉ lệ (scale lock)

2.1 Hằng số

Hạng mục Giá trị Nguồn / ghi chú
Canvas thế giới native 270 × 480 (khóa ngang; cao 480–700) constants NATIVE_W / NATIVE_H_REF
PPU thế giới / VFX thế giới 16 PixelArtImportRules.WORLD_PIXELS_PER_UNIT
PPU UI 25 1 art px = 4 đơn vị canvas 1080×1920 (ref PPU 100)
Hệ số hiển thị chuẩn ×4 (1080 px rộng) k nguyên, §2.2
Thân anh hùng ≈ 24 × 38 px pixelfx actor:2, xương cố định
Cell idle/loco 48 × 56, pivot chân (24, 52) Sprite trim + pivot riêng mỗi khung
Bước khung (limb step) 12 fps = 5 tick ms mỗi khung thay đổi được (timeline)
Tick mô phỏng 60 Hz INV-14, CombatClock
Zoom camera 1× / 2× (nguyên) Tuyệt Sát / Tuyệt Kỹ
Rung / camera punch px nguyên x = sin(f·2.4)·k, y = cos(f·3.7)·0.7k
Bóng nhân vật elip 2 hàng, rộng 10 + ½|ffx − fbx| − min(8, ½|y|) giống pixelfx act.shadow
Lính / Thủ Vệ / Boss cao tối đa 48 / 68 / 96 px hub §4

Một pixel nghệ thuật (art px) = 1 px pixelfx = 1/16 unit Unity. Không bao giờ xoay sprite góc lẻ, scale lẻ, hay đặt vị trí lẻ khi hiển thị: mô phỏng dùng float, renderer làm tròn vị trí về art px nguyên (Mathf.Round, đối xứng khi lật) ngay trước khi vẽ. Lật trái/phải = flipX của SpriteRenderer với pivot đã bù nửa pixel (pivot X là số nguyên → lật quanh cạnh pixel, không lệch 1 px).

2.2 Hiển thị thế giới theo thiết bị

Camera thế giới = URP Pixel Perfect Camera (ref 270×480, PPU 16, upscaleRT = true, pixelSnapping = true, cropFrame = None). Quy tắc chọn hệ số:

k = min(⌊W / 270⌋, ⌊H / 480⌋)
nếu k ≥ 3 và W / k ≤ 384  → chế độ NGUYÊN: render target (⌊W/k⌋ × ⌊H/k⌋), phóng ×k
ngược lại                  → chế độ LẺ: kf = min(W/270, H/480), render target (W/kf × H/kf), phóng point-filter

Chiều cao dư (máy dài) → thêm vùng trên trần (vòm tối, thạch nhũ) và mặt sàn trước; bề rộng dư (iPad) → thấy thêm phòng, tối đa 384 px (phòng 352 + 16 px tường ngoài mỗi bên — tile tường ngoài phải tồn tại). Sàn neo ở 66% khung chiến đấu (01 §2; khung chiến đấu = giữa dải HUD trên 176 và panel dưới 496 đơn vị canvas, 11 §3).

Thiết bị Màn hình Chế độ k Thế giới thấy (art px) Canvas UI Sàn (canvas y) Trên sàn (art px)
9:16 chuẩn 1080×1920 nguyên 4 270×480 1080×1920 1000 206
19.5:9 Android 1080×2340 nguyên 4 270×585 1080×2340 1277 275
20:9 Android 1080×2400 nguyên 4 270×600 1080×2400 1316 285
21:9 1080×2520 nguyên 4 270×630 1080×2520 1396 305
iPhone 13/14 1170×2532 nguyên 4 292×633 1080×2337 1275 298
iPhone Pro Max 1290×2796 nguyên 4 322×699 1080×2341 1278 329
Android 720p 720×1600 lẻ (point) 2.67 270×600 1080×2400 1316 285
iPad 3:4 1536×2048 nguyên 4 384×512 1440×1920 1000 220
iPad Air 11" 1668×2388 lẻ (point) 4.97 335×480 1341×1920 1000 206
iPad Pro 12.9" 2048×2732 lẻ (point) 5.69 360×480 1439×1920 1000 206

Công thức đặt camera: camY = floorY + (floorFromTop_art − H_art/2) với floorFromTop_art = (floorCanvasY · s) / k. Camera bám x anh hùng (deadzone ±24 px), kẹp trong phòng; nâng y khi anh hùng y > 72 px: +0.5 × (y − 72), tối đa +40 px (combo trên không cao). Mọi giá trị camera làm tròn art px.

2.3 Cell, pivot, kích thước nhân vật

  • Sprite không dùng cell lưới cố định khi ship: mỗi khung được trim theo bounding box + 1 px đệm, pack MaxRects (§3.5), pivot = gót chân (feet origin) riêng từng khung. "Cell" dưới đây là khung tham chiếu khi dựng act (meta.size) và trần kích thước cho ds-check.
  • Thân anh hùng ≈ 24×38 (stock actor actor: 2); Brann scale 1.08 (≈ 41 px). Vũ khí dài cần canvas đòn rộng: giáo, lưỡi hái, liềm xích.
Id Mã Anh hùng Vũ khí (pixelfx) Cell idle Canvas đòn tối đa Màu chữ ký Ramp vật liệu Hình dáng 1× Phụ kiện costume
H01 KAI Kaito katana + saya (mới) 48×56 112×72 #c8283c da: PAL.dust sáng · tóc dài: PAL.stone sáng (bạc) · áo bó, quần, găng hở ngón, ủng: PAL.night · áo choàng dài rách, khăn cổ, vạt hông, dây quấn tay/chân: PAL.blood · giáp vai gai, giáp tay, gối, mũi ủng: PAL.metal tối · thắt lưng: PAL.wood + khóa PAL.metal · lưỡi katana cong: PAL.stone tối + ánh PAL.blood · chuôi quấn PAL.blood · tsuba PAL.metal Mảnh, tóc bạc dài ngang vai bay ngược, giáp vai gai lớn một bên, áo choàng đỏ thẫm dài rách vạt bay sau lưng, khăn đỏ quấn cổ, katana lưỡi cong đỏ-đen thủ thấp một tay tóc bạc dài · áo choàng rách 12 px (2 khung trễ) · khăn cổ · giáp vai gai
H02 ALD Aldric sword + kiteshield (mới) 48×56 96×64 #f0c050 giáp tấm: PAL.metal + viền PAL.gold · tabard, áo choàng dài, khăn cổ: PAL.electric tối (lam đậm) · găng, lớp lót giáp: PAL.night · khiên diều: mặt PAL.metal, vành PAL.gold, lõi PAL.electric tối, huy hiệu mặt trời PAL.holy · ngọc ngực & chuôi kiếm: PAL.electric · da: PAL.dust sáng · tóc vàng: PAL.gold · VFX: PAL.holy Vai rộng giáp tấm bạc viền vàng, tóc vàng hất ngược (không mũ trụ), khiên diều cao quá nửa thân che nửa trái thân, kiếm thẳng lưỡi rãnh lam cầm thấp, áo choàng lam dài bay sau lưng tóc vàng ngắn · khiên diều 14×22 · giáp vai · khăn cổ · áo choàng lam 8 px
H03 VEX Vex dagger ×2 (có sẵn, lưỡi dài + dây cáp) 48×56 80×64 #3ad0b0 da: PAL.dust sáng · tóc đuôi ngựa cao: PAL.gold + dây buộc PAL.electric tối · khăn cổ, thân áo, quần đùi, tất cao: PAL.electric tối (lam) · ngực áo: PAL.bone sáng (trắng) · giáp vai nhỏ: PAL.metal + viền PAL.gold · găng hở ngón, giáp tay, thắt lưng, ủng cao: PAL.wood (da thuộc) + viền PAL.gold · hộp máy hai bên hông + dây cáp: PAL.metal + đèn PAL.electric · dao lưỡi dài: PAL.metal + hộ thủ PAL.electric · VFX chữ ký: PAL.arcane (dư ảnh, khói chớp) + lõi vệt chém #3ad0b0 Nhỏ, gọn, khom thấp như lò xo nén; tóc vàng buộc đuôi ngựa cao bung sau gáy, khăn cổ lam, hai dao lưỡi dài nối dây cáp chĩa xuống hai bên, hộp máy cơ khí hai bên hông đuôi ngựa cao 8 px (2 khung trễ) · khăn cổ · hộp máy hông + dây cáp
H04 BRN Brann greatsword (có sẵn, ramp riêng) 56×64 128×80 #e0702a da: PAL.dust sáng · tóc vàng tro vuốt ngược: PAL.bone · áo khoác dài quý tộc: PAL.metal sáng (trắng) · lót áo, cổ đứng, gilê: PAL.electric tối (lam) · viền áo, khuy, nẹp giáp: PAL.gold · giáp vai, giáp tay, ống chân: PAL.metal · áo trong, quần, găng, ủng: PAL.night · thắt lưng: PAL.wood · lưỡi: PAL.metal + rãnh PAL.night + vệt nung PAL.ember · chắn tay: PAL.metal + ngọc PAL.electric · VFX: PAL.ember/PAL.lava Vai rộng giáp vai bạc, tóc vàng tro vuốt ngược, áo khoác dài trắng cổ đứng lót lam vạt bay sau lưng, đại kiếm lưỡi thẳng bản rộng chúc mũi xuống trước tóc vuốt ngược · vạt áo khoác dài · giáp vai bạc · scale 1.08
H05 LYR Lyra spear lưỡi kích đỏ (có sẵn + biến thể) 48×64 144×88 #58b8f0 da: PAL.smoke sáng (tím xám hắc tiên) + vân PAL.neon tối trên ngực · tóc trắng: PAL.stone sáng · mắt: PAL.blood sáng · giáp gai (vai, tay, gối, ủng): PAL.night + cạnh PAL.metal + khe PAL.blood · áo hở ngực, quần: PAL.night · vạt vải rách ở hông: PAL.blood · cán giáo: PAL.night · lưỡi kích: PAL.blood + vành gai PAL.metal · mũi đuôi cán: PAL.metal · viền tích sét: PAL.electric · VFX: PAL.electric Mảnh, tai nhọn, tóc trắng bù xù, da tím xám; giáp gai đen-đỏ thẫm hở ngực, vạt vải đỏ rách ở hông; ngọn giáo cán tối lưỡi kích đỏ thẫm ~65 px gần gấp đôi thân cầm chéo, lưỡi chúc xuống tai nhọn · tóc trắng bù xù · giáp vai gai · vạt vải rách 10 px
H06 ROK Rook gauntlet lửa ×1 tay phải (mới) 48×56 80×64 #e8a030 da: PAL.earth sáng (rám nắng, thân trần) · mũ trùm: PAL.night + viền & huy hiệu trán PAL.gold · khăn che mặt, áo choàng rách vai trái, vạt hông: PAL.blood · giáp vai gai, giáp tay trái, đai, ủng: PAL.night + viền PAL.gold · ngọc đai: PAL.blood · quần, găng hở ngón tay trái: PAL.night · găng lửa tay phải: PAL.lava + vảy lửa PAL.fire + vành & mắt ngọc PAL.gold · Chân Khí: PAL.gold/PAL.volt · đá: PAL.stone/PAL.earth Vai rộng thân trần cơ bắp, mũ trùm đen viền vàng, khăn đỏ che nửa mặt; một găng lửa đỏ-cam khổng lồ bọc cả cẳng tay phải (nắm đấm to gần bằng đầu), giáp vai gai đen-vàng bên trái kèm vạt áo choàng đỏ rách bay, thế thủ hai nắm đấm trước cằm mũ trùm + khăn che mặt · vạt áo choàng rách · găng lửa 6×6
H07 MOR Mora scythe (có sẵn, ramp riêng) 56×64 144×96 #9a6ae0 mũ trùm chỏm nhọn & áo choàng rách: PAL.blood (đỏ thẫm) · tóc bạc ngắn: PAL.stone sáng · mắt: PAL.blood sáng · da: PAL.dust sáng · váy corset, găng hở ngón, ủng: PAL.night + dây buộc & viền PAL.blood · thắt lưng chéo: PAL.wood + khóa PAL.metal · trâm tim & huy hiệu mũ: PAL.metal + ngọc PAL.blood · cán hái: PAL.night + đai PAL.metal · lưỡi: PAL.stone tối + vân PAL.blood, rìa PAL.metal · hồn + VFX chữ ký: PAL.cyber (lục ma) viền PAL.bone · vệt gặt: bóng #9a6ae0 Mảnh, mũ trùm đỏ thẫm chỏm nhọn, tóc bạc ngắn ngang cằm, áo choàng đỏ rách thành dải bay ngược; lưỡi hái đen-đỏ to hơn người cầm ngang thân, lưỡi cong như trăng khuyết vươn cao phía trước — đọc rõ ở 1× mũ trùm chỏm nhọn · vạt áo choàng rách 10 px · tóc bạc ngắn
H08 SAB Sable kusarigama (mới, tạ lưỡi phi) + xích runtime 48×56 112×72 #5ad06a da: PAL.dust sáng · tóc bù xù: PAL.blood tối (đỏ rượu) · mắt: PAL.blood sáng · áo khoác dài cổ đứng, áo trong, quần, găng hở ngón, ủng: PAL.night · lót áo, cổ tay áo, vạt trong: PAL.blood · viền áo, khóa, trâm, nẹp ủng: PAL.metal · thắt lưng: PAL.blood tối · liềm, tạ lưỡi phi & xích: PAL.metal tối + ngọc PAL.blood · vân độc trên lưỡi & VFX độc: PAL.poison Gọn, tóc đỏ rượu bù xù dựng ngược, áo khoác dài đen lót đỏ thẫm vạt xẻ tới bắp chân, xích bạc vắt ngang hông; liềm ở tay gần, xích chùng thõng tới quả tạ lưỡi phi gai — vòng xích chùng là dấu nhận diện áo khoác dài vạt xẻ · tóc bù xù · vòng xích chùng ≤ 12 px
H09 ZER Zerran Song đại đao ma (xem chương H09 §12) 56×64 144×80 #e8384f da: PAL.earth sáng (rám) · tóc: PAL.metal sáng gần trắng + bóng PAL.shadow tím nhạt · giáp & áo: PAL.night đen + viền PAL.gold · vải tạp dề & lót: PAL.blood · ngọc thắt lưng/ngực: PAL.fire đỏ · lưỡi: PAL.shadow đen + sống răng PAL.gold + lưỡi bén PAL.blood/#e8384f phát sáng · mắt: tím Tóc trắng dựng gai rất to, vai giáp gai vàng cong như sừng, ngực trần, vạt tạp dề đỏ giữa hai chân, hai đại đao cong lưỡi đỏ dài gần bằng thân rủ chếch ra hai bên theo chương anh hùng §12
H10 FEN Feng Tay không quấn băng (xem chương H10 §12) 48×56 112×72 #f8e020 da: PAL.dust sáng · áo cộc tay: PAL.gold + hoa văn mây lửa PAL.night · quần: PAL.night + hoa văn PAL.gold · đai: PAL.blood · băng quấn tay/cổ chân: PAL.bone · bao cổ tay & dép: PAL.night viền PAL.gold · tóc: ink · vệt cước: #f8e020 Thiếu niên gọn người, tóc đen dựng chéo ra trước, áo vàng cộc tay cổ đứng cài khuy đen, đai đỏ thắt nút hai vạt bay bên hông, quần đen ống túm, băng trắng quấn nắm tay và cổ chân, dép đen; thế nghiêng một nắm đấm trước ngực, nắm kia ở hông theo chương anh hùng §12
H11 MOM Momo Kim côn co giãn (xem chương H11 §12) 48×64 160×80 #b8762a lông & tóc: PAL.gold (vàng nâu) · mặt & tai: PAL.dust · giáp: PAL.gold + PAL.blood (đỏ son) · khăn: PAL.blood · áo lót & quần: PAL.night · bao tay: PAL.night + viền PAL.gold · ủng: PAL.gold viền PAL.blood · côn: cán PAL.blood, hai đầu bịt PAL.gold Khỉ nhỏ tóc vàng dựng ngược như ngọn lửa đội vòng trán vàng chạm mây, tai khỉ, khăn đỏ, giáp vàng-đỏ có giáp vai và khóa đai hình mặt thú, đuôi khỉ dài cong; tay phải kéo cây côn cán đỏ hai đầu bịt vàng chúc mũi xuống sàn theo chương anh hùng §12
H12 ISO Isolde Tinh thể Hàn Tinh (triệu hồi binh khí băng) (xem chương H12 §12) 48×64 144×88 #a8e8ff da: PAL.dust sáng · tóc: PAL.ice nhạt (bạc-lam) · váy corset: PAL.night (lam hoàng gia) · tay áo/vạt trước: PAL.bone trắng + viền PAL.metal bạc · vương miện/tinh thể/binh khí: PAL.ice · ủng: PAL.night + PAL.metal Thon cao, vương miện băng nhiều mũi nhọn, tóc bạc dài quá hông, tay áo trắng loe rộng, váy dài xẻ tà, tay trước ngửa nâng tinh thể lơ lửng theo chương anh hùng §12
H13 AUR Aurelle Quyền trượng thánh (ngọc lam) + đôi cánh (xem chương H13 §12) 56×64 144×96 #fff2c0 da: PAL.dust sáng · tóc: PAL.gold (vàng óng) · váy/tay áo: PAL.bone trắng + viền PAL.gold · lót áo choàng/băng đô: PAL.night (lam) · cánh: PAL.bone + bóng PAL.holy · quyền trượng: cán PAL.night, đầu PAL.gold, ngọc PAL.ice đậm (lam) · VFX: PAL.holy + lõi lam Lơ lửng cao hơn mọi anh hùng 32 px với bóng tròn dưới sàn, tóc vàng dài gợn sóng, đôi cánh trắng nhỏ sau lưng + cánh nhỏ trên tóc, quyền trượng cao hơn người đầu càng trăng và ngọc lam theo chương anh hùng §12
H14 SOR Sorin Song vuốt dài gắn giáp tay (xem chương H14 §12) 48×56 112×72 #8a1020 da: PAL.dust sáng · tóc ngắn: PAL.stone sáng (bạc) · mắt: PAL.blood sáng · mũ trùm, áo bó, quần, găng, tất đùi, ủng: PAL.night (đen) · viền mũ, khăn cổ, vạt tabard, lót giáp tay: PAL.blood · giáp vai, ngực, đai, gối, mũi ủng, huy hiệu nanh trên mũ: PAL.metal tối · vuốt: PAL.metal + rãnh PAL.blood · VFX: vệt vuốt PAL.blood + lõi trắng nóng, Dấu Mồi #8a1020 Nhỏ, khom thấp như thú rình; mũ trùm đen viền đỏ có huy hiệu nanh bạc, tóc bạc ngắn lòi ra, khăn đỏ; mỗi cẳng tay mọc ba lưỡi vuốt thép cong dài chúc xuống tới gối — đọc rõ ở 1× theo chương anh hùng §12
H15 TSU Tsubaki Ô giấy đỏ + phù chú (xem chương H15 §12) 48×64 128×80 #ff4a6a da: PAL.dust sáng · tóc: ink (PAL.night tối nhất) · áo trắng tay rộng: PAL.bone sáng · viền áo, hakama, băng đô, nơ: PAL.blood · hoa anh đào trên tay áo: PAL.candy nhạt · obi: PAL.shadow · chuông & khóa ô: PAL.gold · cán ô: PAL.wood tối · tán ô: PAL.blood + PAL.bone · phù: PAL.bone + mực PAL.blood · VFX ấn: PAL.holy viền #ff4a6a Nhỏ, tóc đen thẳng dài tới hông, băng đô đỏ có nơ đỏ-trắng và tua bên trái, tay áo trắng rộng thõng, hakama đỏ ngắn, guốc đen; ô giấy đỏ-trắng mở tròn như chiếc đĩa gác vai (hoặc khép thành ngọn thương có chuông + tua đỏ) — đĩa tán tròn là dấu nhận diện ở 1× theo chương anh hùng §12
H16 JUN Juno Đại pháo cầm tay (pháo ray) (xem chương H16 §12) 56×64 176×80 #b04aff tóc: PAL.ember (đỏ cam dựng) · kính bảo hộ: khung PAL.gold, tròng PAL.arcane · da: PAL.dust · áo crop, găng, quần: PAL.shadow + mảng PAL.arcane tối · khăn quàng: PAL.arcane · đai, bao tay, khóa giày: PAL.wood + PAL.gold · vạch sơn mặt: PAL.arcane · pháo: thân PAL.arcane, khung PAL.metal, nòng PAL.shadow, khe sáng PAL.arcane sáng · VFX đạn: xuyên PAL.arcane+trắng, chùm PAL.ember, nổ PAL.fire Thấp, chân dang rộng, tóc đỏ dựng tua tủa, kính bảo hộ gác trán, khăn tím; khẩu pháo tím to gần bằng người ôm ngang hông chĩa về trước — khối pháo nằm ngang với khung báng góc cạnh phía sau là dấu nhận diện ở 1× theo chương anh hùng §12
H17 IGN Ignis Kiếm liễu (rapier) (xem chương H17 §12) 48×56 112×72 #b0b4c0 da: PAL.dust sáng · tóc dài gợn sóng: PAL.gold · mắt: PAL.water · áo khoác dài (frock coat): PAL.bone (trắng ngà) + viền PAL.gold · giáp vai vỏ sò & gilê: PAL.gold · áo choàng: ngoài PAL.blood đậm (đỏ thẫm, owner chốt 2026-11-02) + viền PAL.gold, lót PAL.bone · ngọc cài cổ & bao cổ tay: PAL.water + PAL.gold · găng & quần: PAL.night · ủng: PAL.bone + mũi PAL.gold · lưỡi kiếm: PAL.metal + giỏ chuôi PAL.gold + rãnh lưỡi PAL.fire khi Nhịp ≥ 5 Thanh mảnh, khối tóc vàng dài gợn sóng chạm lưng (đặc điểm nhận diện số 1), đứng nghiêng thế đấu kiếm, kiếm liễu mảnh duỗi chéo xuống trước mặt, áo khoác trắng dài với giáp vai vàng hình vỏ sò, áo choàng đỏ thẫm viền vàng phồng sau lưng theo chương anh hùng §12
H18 ELO Elowen Cung trăng khuyết (xem chương H18 §12) 48×64 176×80 #3a5ad8 da: PAL.dust sáng · tóc: PAL.ice nhạt pha tím (bạc-lam tím) · giáp ngực/váy/ủng: PAL.night (lam) + viền PAL.gold · giáp vai & phần trắng: PAL.metal sáng · lông vũ & ngọc cung: PAL.ice/PAL.electric lam · cung: PAL.night thẫm + viền & lưỡi trăng PAL.gold · tên ánh trăng & VFX: PAL.ice/PAL.holy lam Thon nhỏ, đuôi ngựa bạc-tím rất dài quét tới gót, tai nhọn, lông vũ xanh cài đỉnh đầu, cây cung trăng khuyết dài gần bằng người với hai lưỡi trăng vàng ở hai đầu, ủng cao đùi theo chương anh hùng §12
H19 KAZ Kazane Song quạt chiến (xem chương H19 §12) 48×56 128×72 #1f8f7a da: PAL.dust sáng · tóc dài (búi cao + hai lọn dài + đuôi): PAL.night lục lam (pha #1f8f7a) · mắt: PAL.blood sáng · kimono đen trễ vai, tay áo rộng, tất đùi: PAL.night · cổ áo trong: PAL.bone sáng · obi, vạt váy, hoa văn lá phong, dây tua: PAL.blood · viền áo, trâm cài, khóa obi: PAL.gold · đế guốc: PAL.wood + quai PAL.blood · quạt: nan PAL.night + lụa PAL.blood + lá phong PAL.blood sáng + viền PAL.gold · VFX gió: lõi trắng + vệt #1f8f7a + lá phong PAL.blood Mảnh, búi tóc lục lam cao cài trâm vàng hình vòng cung và hoa lá phong đỏ tua trắng, hai lọn tóc dài; kimono đen trễ vai tay áo rộng; hai quạt chiến đỏ-đen viền vàng mở như hai cánh hình thang hai bên hông — đọc rõ ở 1× theo chương anh hùng §12
H20 TAU Taurok Đại chùy chiến (xem chương H20 §12) 64×72 160×96 #7a4a2a da/lông thân: PAL.earth đỏ nâu · bờm/râu/đuôi: PAL.shadow nâu đen · sừng: PAL.dust kem + chóp PAL.blood + đai PAL.gold · giáp vai/đai/khố: PAL.blood đỏ + viền PAL.gold · đai da chéo & ống tay: PAL.wood · khoen mũi: PAL.gold · chùy: thân trụ PAL.shadow sắt đen + đai & gai PAL.gold + ký hiệu PAL.blood · VFX: bụi PAL.earth/PAL.dust, Thức Tỉnh PAL.fire/PAL.gold Lớn nhất roster, lưng gù, đầu bò tót bờm xù với hai sừng cong lớn, khoen vàng ở mũi, giáp vai đỏ viền vàng, móng guốc chẻ, đuôi có chùm lông; đại chùy trụ kéo lê sau lưng theo chương anh hùng §12
H21 VUL Vulkar Song liêm trăng khuyết nối xích (dung nham) (xem chương H21 §12) 64×72 160×96 #ff3d00 thân đá bazan: PAL.shadow + PAL.stone · mặt nạ sọ, sừng, giáp vai gai: PAL.bone tối · vết nứt & lõi ngực & mắt: PAL.lava (lõi PAL.fire, light buffer) · xích & khoen: PAL.metal tối · liêm: rìa PAL.stone + lòng PAL.lava vân tổ ong · đá đai lưng: PAL.ember · VFX: PAL.fire/PAL.lava To bè nhất roster (cell 64×72, scale 1.15), khom rộng, sừng cong lớn và mặt nạ sọ, vai gai đá, thân đá đen nứt dung nham; hai liêm trăng khuyết lớn treo trên xích dưới hai nắm đấm — hai vầng trăng cam hai bên hông là dấu nhận diện theo chương anh hùng §12

2.4 Luật pixel bắt buộc (hub §1, §3, §4, §7)

  1. Một cỡ pixel trên mọi màn hình; zoom chỉ 1× hoặc 2× nguyên (Tuyệt Sát/Tuyệt Kỹ crop quanh điểm va).
  2. Viền ink #0b0a10 1 px (nhân vật, đạo cụ, icon); VFX không viền (lines + light).
  3. Ramp vật liệu 4 tông từ PAL (sáng → tối); ánh sáng trên-trái; trắng chỉ cho lõi nóng / lóe 1–2 khung.
  4. Không anti-alias, không blur, không gradient mượt; dither Bayer 4×4 chỉ cho mờ dần và mảng lớn.
  5. Hiệu ứng không vẽ vào sprite thân (polish §3): vệt chém, tia lửa, khói là VFX riêng; trạng thái là shader + emblem (§6.5).
  6. Hình dáng chính, ramp chính, vũ khí của anh hùng cố định (INV-04).

3Mở rộng pixelfx (module M1 — nhánh deepstair của submodule)

3.0 Nguyên tắc

  • Làm trên nhánh deepstair của Tools/pixelfx (submodule), commit gốc ghim trong .gitmodules; không push upstream main khi chưa có dev. Rebase upstream mỗi quý; mọi patch core giữ tương thích ngược (gọi cũ không truyền costume → hình y như cũ, node tools/check.mjs toàn thư viện vẫn sạch).
  • Chỉ sheet đã export vào Assets/; records/ gitignored. Contact sheet duyệt copy sang Design/ArtPrompts/Anim/<hero>/.
  • Credit giữ nguyên: Hugo Duprez (MIT), Hollow Crown (MIT), Patch — vào màn Credits (11 §8). Font gallery/fonts/PatchPixel không bao giờ ship.

3.1 Patch src/core/rig.js (hook, không viết lại)

# Thay đổi Chi tiết
R1 posed(pose, costume = null), skeleton(pose, costume) keyOf thêm costume.id; cache theo cặp (pose, costume)
R2 Scale solve(pose, s): mọi số đo M.* × s (= costume.scale, mặc định 1); bán kính sphere/sweep × s; viền vẫn 1 px. Lưới nháp BW × BH = ceil(300·s) × ceil(240·s), OX/OY theo đó (boss s = 2.5 không bị cắt)
R3 Hook phụ kiện Sau drawBody(S): costume.extras?.(S, api) với api = { sphere, sweep, ellipsoid, plot, frames: S.B, PART, mat } — vẽ tóc/mũ/áo choàng vào cùng depth buffer, PART id mới 25–40
R4 Tô vật liệu Trong finish(): Col[i] = costume ? costume.paint(Part[i], Mat[i], tone, LX[i], LY[i], Fr[i]) : (SHELL/CORE)[tone]; seam = bậc tối hơn của vật liệu đó thay cho SEAM
R5 Mắt costume.eyes = { c, px: [[dx, dy], …] } đặt theo khung đầu (như MARK của ngực) chỉ khi mặt hướng ra (R.x.z > 0.15); không vẽ khi đầu quay đi
R6 Thép riêng WEAPONS[name].draw(T, q, off, steel) — steel = map tông kim loại của costume (thay STEEL), mặc định STEEL cũ
R7 Pose fields mới cw (gió vải −1…1), cl (nâng vải −1…1), chain (độ chùng xích 0…12 px), shield (0 = cầm bên, 1 = giơ trước ngực). Thêm vào POSE_KEYS của check.mjs; lerpPose nội suy được
R8 Index output posed() trả thêm idx (Uint8Array, chỉ số màu trong LUT costume) song song color — dùng cho export R8 (§3.5)

3.2 src/core/costume.js

Một costume = dữ liệu thuần (không random, tất định). Mỗi anh hùng/địch có src/fx/<owner>/_costume.js export COSTUME.

import { costume, hair, hood, helmet, cape, scarf, pauldron, PAL } from "../../core/costume.js";
export const COSTUME = costume({                // H01 theo concept art Heroes/H01.png
  id: "kai", scale: 1,
  materials: {                       // 4 tông: [sáng, mặt, bóng, tối] — chỉ số trong ramp PAL
    skin:  ["dust", 6, 5, 4, 3],  suit:  ["night", 5, 4, 3, 2],  cape:  ["blood", 5, 4, 3, 2],
    hair:  ["stone", 7, 6, 5, 4], armor: ["metal", 5, 4, 3, 2],  belt:  ["wood", 4, 3, 2, 1],
    steel: ["stone", 4, 3, 2, 1], grip:  ["blood", 3, 2, 1, 0],  // lưỡi thép đen; ánh đỏ 1 px dọc cạnh lấy tông sáng của cape
  },
  parts: { head: "hair", face: "skin", neck: "cape", chest: "suit", waist: "suit", pelvis: "suit",
           upperN: "suit", foreN: "armor", handN: "suit", upperF: "suit", foreF: "armor", handF: "suit",
           thighN: "suit", shinN: "armor", footN: "suit", thighF: "suit", shinF: "armor", footF: "suit" },
  zones: [{ part: "waist", band: [-1, 1], mat: "belt" }],       // thắt lưng da theo tọa độ local LY
  extras: [hair.long({ len: 8 }), pauldron({ side: "N", spikes: 3 }), cape({ len: 14, seg: 7, torn: true }),
           scarf({ len: 4, seg: 2, from: "neck", lag: 2 })],
  eyes: { c: "#a4a4b2", px: [[1, 0]] },
  weapon: { name: "katana", steel: "steel", gold: "armor", grip: "grip" }, offWeapon: { name: "saya" },
  ghost: ["blood", 5],               // màu dư ảnh (§6.6)
});
  • Ánh xạ tông: rig cho tông 0–3 (sáng → tối, chi phía xa +1); paint đổi tông → ramp vật liệu. Mỗi vật liệu đúng 4 màu → costume ≤ 12 vật liệu × 4 + ink + seam + mắt ≤ 52 màu (giới hạn LUT 64 màu/anh hùng, 255/LUT Địa Tầng).
  • Phụ kiện hình học (đều qua depth buffer → che khuất đúng khi xoay yaw):
Hàm Hình Tham số chính
hair.topknot / ponytail / short / long / bald ellipsoid + chuỗi sweep bám khung đầu h, len, seg
hood({ peak }) ellipsoid lớn hơn đầu 0.6 px, mép trước mở chỏm nhọn (Mora), trùm sâu (Rook)
helmet({ visor, plume, fins }) ellipsoid kín + tấm che + chỏm/vây Thủ Vệ/địch (H01–H08 không đội mũ trụ)
cape({ len, seg, width, torn }) chuỗi seg đoạn từ 2 vai, rơi theo trọng lực + cw/cl Kaito rách, Aldric 8, Rook rách 1 vai, Mora 10 rách; Brann/Sable vạt áo khoác (gắn eo)
scarf({ len, seg, from, lag }) chuỗi mảnh 1–2 px từ cổ/trán Kaito, Aldric, Vex (khăn cổ)
pauldron, belly, beard, mask ellipsoid gắn khung pauldron: Kaito, Aldric, Brann, Lyra, Rook · belly: Gorrak · mask: Rook (khăn che nửa mặt)
  • Động lực vải tất định: posed() không có trạng thái, nên vải đọc cw/cl từ pose. body-sheet.mjs tự điền: cw = clamp(−vx_root/120 − Δchest_x/6, −1, 1) với vận tốc root/ngực lấy trễ lag khung (áo choàng Kaito trễ 2 khung), cl = clamp(vy_root/200, −1, 1); khi đứng yên cw tắt dần ×0.5/khung. Cùng clip → cùng kết quả.
  • Scale: địch nhỏ 0.85, to 1.25, Thủ Vệ 1.5–1.8, Boss 2.0–2.5 (§5). Viền 1 px giữ nguyên → mật độ pixel không đổi.

3.3 Vũ khí mới trong WEAPONS

Key Dạng Spec (px, từ nắm tay i = 0) Dùng
katana two: -5 chuôi −6.5…2.2 quấn ito (xen grip/gripD mỗi 1 px), tsuba đĩa r 2.2 armor (bạc), lưỡi 3.2…28.2 rộng 1.6 thép đen ánh đỏ, cong k = 0.012·(i−3)² (sori), cạnh sắc dùng tông hi, sống d H01
saya offWeapon (tay xa/hông) bao 27 px lacquer suit (đen) + 2 khâu armor; wl cho phép thu ngắn khi nhìn chéo H01 iai
towershield offWeapon, across tấm 14×22 dày 1.5 (2 mặt + cạnh, như búa), viền steel, mặt cloth + huy hiệu mặt trời 5×5 holy; shield: 1 → giơ trước ngực, mặt quay ra 30° WD10
kiteshield offWeapon, across khiên diều 14×22 dày 1.5: vai khiên trên phẳng, thân thon dần tới mũi nhọn ở đáy; viền gold, mặt steel bạc, lõi lam hình giọt + huy hiệu mặt trời 5×5 holy; shield: 1 → giơ trước ngực, mặt quay ra 30° H02
gauntlet weapon (chỉ tay gần — tay phải) khối 4×4×3 bọc nắm tay + ống găng phình tới khuỷu, ramp lava + vảy fire, mắt ngọc gold ở mu tay, 3 đốt khớp 1 px sáng; không kéo dài tầm; tay xa không vẽ găng H06
kusarigama weapon (liềm) + offWeapon chainweight liềm: cán 7 px bọc đen (night) đai đỏ, lưỡi cong 9 px vuông góc thép đen viền bạc; tạ: lưỡi phi gai (kunai) thép đen nạm ngọc đỏ, thay quả cầu r 1.6 (hitbox giữ); xích trong sprite chỉ là vòng chùng giữa 2 tay chain ≤ 12 px (mắt xích 2×1 xen kẽ); xích bay xa vẽ runtime (§6.9) H08
spear biến thể glaive thêm tùy chọn head lưỡi kích đỏ thẫm 11 px + vành gai bạc 3 px; đuôi cán mũi nhọn nhỏ H05
shovel, crossbow, anchor, tongs, staff, club đợt 2 (địch) dùng lại haft/blade + đầu riêng Thủ Vệ, Boss (12 §4)

Thép theo anh hùng (concept art): Kaito thép đen ánh đỏ (stone tối + blood), Aldric bạc + gold, Brann thép sáng (metal) + rãnh tối, Mora/Lyra/Sable thép đen + blood, Rook găng lava, Thủ Vệ pha lê (arcane/ice).

3.4 Trang Phục (skin) — tôn trọng INV-04

Loại skin Cách làm Chi phí
Palette (vd "Tro Tàn" Thông Thạo 5, vệt chém vàng TT 18) thêm 1 hàng LUT (đổi ramp phụ + màu VFX); sprite dùng lại ~0 (1 hàng 256 px)
Costume (đổi phụ kiện: mũ, khăn, áo choàng) costume biến thể extends costume gốc, chỉ đổi extras + vật liệu phụ; render lại body-sheet trang thân riêng 1–2 MB, chỉ nạp skin đang mặc

Không skin nào đổi hình dáng thân, vũ khí, ramp chính (palette.signature + vật liệu chính khóa trong costume gốc; ds-check so sánh).

3.5 tools/body-sheet.mjs — xuất thân nhân vật

node tools/body-sheet.mjs hero-kai [--clips kai-l1,kai-l2] [--out ../../Assets/ArtAsset/Characters/Heroes/H01_Kaito]

Vào: src/fx/hero-kai/_costume.js, _poses.js, _loco.js (export CLIPS = { idle: { poses, loop:true }, … }), mọi file đòn kai-*.js (export meta, act, create).

Thuật toán:

  1. Mỗi clip lấy act.poses; mốc 0 của clip = khóa đầu (t0, thường âm). Lấy mẫu: tại mỗi khóa và mỗi bước 1/12 s sau khóa đó tới khóa kế (đúng nhịp poseAt của pixelfx: tay chân giữ bước 12 fps); khóa snap → giữ pose trước tới đúng khóa.
  2. Thời lượng khung = round(t_next·60) − round(t·60) tick (cộng dồn, không trôi); tối thiểu 2 tick (khung "release" ngắn hơn 5 tick là hợp lệ — đó là cách đòn nhanh "giật").
  3. Render posed(q, costume) tại chỗ (bỏ x, y): vị trí chân thuộc về root track, không nằm trong ảnh. Trên không giữ cờ air (rig không cắt chân dưới sàn).
  4. Điền cw/cl (§3.2), ghost → cờ khung.
  5. Gộp khung liền kề giống hệt (hash idx + kích thước) → cộng tick. Dedupe toàn anh hùng: khung trùng giữa các clip (GUARD đầu/cuối mọi đòn — INV-16) dùng chung một ô.
  6. Trim + 1 px đệm trong suốt, pack MaxRects (không xoay, đệm 2 px giữa ô) vào trang 1024×1024 (tối đa 2048×1024). Quá 2 trang → lỗi, giảm khung.
  7. Xuất <code>_body_p<N>.png R8 index (0 = trong suốt, 1 = ink, 2… = LUT), <code>_palette.png (RGBA32 256×N, hàng 0 = gốc, hàng skin palette kế tiếp), <code>.pxbody (JSON).

Socket mỗi khung (cho VFX, xích, túm, dư ảnh): handN, handF, weaponTip (cuối trục vũ khí), head, chest — từ skeleton(q), đổi về tọa độ feet-local, làm tròn px.

{ "owner": "H01", "costume": "kai", "ppu": 16, "pages": ["kai_body_p0.png", "kai_body_p1.png"], "palette": "kai_palette.png",
  "cells": [ { "id": 0, "page": 0, "x": 2, "y": 2, "w": 21, "h": 39, "ox": 9, "oy": 38 } ],
  "clips": { "KAI_L1": { "loop": false, "ticks": 22,
      "frames": [ { "cell": 0, "ticks": 5 }, { "cell": 7, "ticks": 3, "ghost": true }, { "cell": 8, "ticks": 4 } ],
      "root": [ [0, 0, 0, "smooth"], [8, 6, 0, "snap"], [22, 6, 0, "smooth"] ],
      "sockets": [ { "handN": [12, -21], "weaponTip": [30, -36], "head": [1, -36] } ] } } }

root = các khóa [tick, x, y, how] (y dương = lên trong Unity; exporter đổi dấu từ pixelfx); runtime nội suy cùng hàm ease của poseAt theo từng tick (chân mượt, tay chân nhảy bước 12 fps — đúng cách pixelfx diễn).

3.6 tools/move-export.mjs — xuất MoveClip (art) cho mỗi đòn

node tools/move-export.mjs hero-kai [--check-csv ../../GDD/Config/HeroHits.csv]

Mỗi file đòn → <move-id>.pxmove:

{ "id": "KAI_L1", "body": "KAI_L1", "ticks": 22, "windup": 7, "contact": null,
  "lock": [[4, 10]],
  "hits": [ { "t": 7, "freeze": 2, "shake": 0, "amp": 0, "push": 2.8, "skin": null } ],
  "cues": [ { "fx": "kai_slash_l", "t": 7, "until": null, "x": 6, "y": -22, "palette": "metal", "layer": "front",
              "flip": false, "angle": 0, "follow": "none" } ],
  "projectiles": [], "tethers": [], "sfx": [ { "id": "kai_slash_l", "t": 6 } ], "dust": [ { "t": 7, "x": 12, "n": 6 } ] }
Trường Nguồn Ý nghĩa
windup tick của hit đầu dùng cho báo hiệu địch (ds-check đối chiếu 01 §11.3)
lock tự sinh: [hit − 3, hit + 3] tick cửa sổ không được hủy để phòng thủ (01 §5.2) — runtime đọc thẳng, không tự tính
hits[].t act.hits[].t − t0 (tick) phải trùng HitDef.t trong HeroHits.csv ±1 tick, --check-csv báo lỗi
freeze/shake/amp/push/skin act.hits[] hit-stop & phản ứng; trống → mặc định lớp hit (01 §12.1)
cues create = chain([...]) effect, thời điểm, vị trí local (từ chân), palette, lớp `back
projectiles cue có x là hàm thời gian { id: "PJ_KAI_WAVE", t, socket, vx, vy, g, life, fx } — mô phỏng va chạm do runtime
tethers Sable, Mora móc `{ t0, t1, from: "handF", to: "target"

Render cue: mỗi cặp (fx, palette, options) render đúng một lần bằng renderFrames (dedupe toàn dự án), xuất hai sheet: <name>@<pal>.png (buffer color, RGBA32 alpha thật) và <name>@<pal>_light.png (buffer light, RGB24 cộng) — thay cho sheet gộp của render.mjs --sheet (gộp chỉ đúng trên nền tối). Trim + pack như §3.5, JSON giữ meta.pixelfx (fps, loop, pivot, frames) + rect từng khung.

3.7 Công cụ phụ

Tool Việc Đầu ra
tools/ds-check.mjs <owner> Cổng G0 (§11): INV-16, pop ở cancel, contact = 9 tick, hit ↔ CSV, ngân sách khung/canvas (§9), màu ⊂ LUT, pixel đơn lẻ, viền kín báo cáo, exit ≠ 0 khi lỗi
tools/hero-sheet.mjs <owner> [move] Contact sheet mọi khung ×4, nhãn tick, vạch đỏ hit, vạch xanh contact, vệt root records/<owner>/<move>.contact.png
tools/echo-icon.mjs <owner> <route> Icon Hồi Âm động 64×64, 8 khung (06 §9): chọn 8 khung đều từ chuỗi đòn của lộ trình, anh hùng + 1 cue chính, crop quanh tâm thân, nền trong suốt icon_<echo-id>.png (8 ô) + JSON
tools/portrait.mjs <owner> Chân dung 32×32 / 64×64 từ pose PORTRAIT (đầu + vai, quay 3/4) dùng ở 11, 12 §6
tools/creature-sheet.mjs <owner> Như body-sheet cho module sinh vật (§5.2) cùng schema .pxbody

4Quy ước biên đạo theo anh hùng

4.1 Thư mục

Tools/pixelfx/src/fx/hero-kai/
  _costume.js   COSTUME (§3.2)
  _poses.js     thư viện pose (§4.2)
  _loco.js      CLIPS: idle, run, hurt1, hurt2, knockdown, getup, death, victory, descent, intro + 6 clip phụ (§10)
  kai-l1.js     một file / MoveDef  (id KAI_L1 → kebab thường)
  kai-r01.js    (không có) — lộ trình là dữ liệu 01 §6.3, không có file art
  fx-slash-l.js VFX chữ ký: fx id "kai_slash_l" ↔ module "hero-kai/fx-slash-l"

Quy ước fx: trong hero_*.py, fx="melee/sword-slash" = effect catalog (recolor theo palette anh hùng), fx="kai_slash_l" = effect chữ ký trong thư mục anh hùng. sfx="kai_slash_l" → file sfx_kai_slash_l[_n].ogg (12 §9).

4.2 Thư viện pose (_poses.js)

Mỗi anh hùng ≥ 30 pose có tên; mọi đòn ghép từ thư viện (spread + ghi đè vài trường). Pose bắt buộc:

Pose Vai trò
GUARD Khung thủ — đầu/cuối mọi đòn mặt đất (INV-16). Kaito: thủ thấp, tay phải trên chuôi, katana trong bao
AIR_READY Khung chờ trên không — đầu/cuối đòn AIR
WIND_HIGH, WIND_LOW, WIND_BACK, WIND_THRUST Lấy đà ngược hướng đòn (AUTHORING "what sells it")
CUT_HIGH, CUT_MID, CUT_LOW, CUT_RISE, CUT_DOWN Khung release (đổi silhouette lớn nhất, ghost: 1 nếu nhanh)
THRUST, THRUST_LONG Đâm (tay gần duỗi, bước x)
FOLLOW, FOLLOW_HEAVY Theo quán tính quá đà
SPIN_A…D 4 góc yaw (0, π/2, π, 3π/2) cho xoay
DEF_PARRY, DEF_BLOCK, DEF_DUCK, DEF_JUMP, DEF_ROLL_A/B Phòng thủ (khung contact)
HURT_HI, HURT_LO, DOWN, GETUP_A/B, KNEEL Phản ứng
RUN_A…F, IDLE_A/B, STEP_DOWN_A…D Loco + đi xuống cầu thang
VICTORY, CAMP_SIT, PORTRAIT Thắng, Home, chân dung

4.3 Luật nối đòn (INV-16)

  1. Đòn mặt đất: khóa đầu và khóa cuối bằng đúng GUARD (chỉ x khác). ds-check so sâu mọi trường.
  2. Pose tại cancel (từ MoveDef) phải ≈ GUARD (Q3): khóa GUARD cuối đặt ≤ cancel; đoạn [cancel, dur] chỉ là "lắng" trong GUARD. Vì đòn kế bắt đầu ở cancel, nếu bỏ luật này sẽ thấy pose giật.
  3. Đòn AIR: đầu/cuối AIR_READY; đòn nhảy lên (move.dy > 0) đầu GUARD cuối AIR_READY; đòn đập xuống đầu AIR_READY cuối GUARD sau tiếp đất.
  4. Cờ link trong MoveDef (đòn nối có chủ đích): miễn luật 1, nhưng cặp đòn được phép nối phải liệt kê trong _poses.js LINKS = [["VEX_A3", "VEX_A4"]] và pose cuối A ≡ pose đầu B.
  5. Xoay: kết thúc ở yaw = k·2π; quay người (TURN) dùng flipX ở khung đầu, không xoay 180° bằng yaw.
  6. Phản Đòn: khung đầu = khung contact của clip phòng thủ trước (cùng pose) → Phản Đòn "bật" ngay khung kế (01 §5.5).

4.4 Clip phòng thủ & contact

Clip dur contact Khung contact là Ghi chú
DEFLECT 0.30 s 0.15 s (9 tick) lưỡi chạm điểm va, ghost 0 tia lửa impacts/parry đặt ở socket weaponTip
BLOCK 0.30 s 0.15 s vũ khí/khiên chắn trước ngực trượt lùi 4 px trong root
EVADE_BACK 0.40 s 0.15 s thân lùi xa nhất, i-frame phủ ghost 2 khung
EVADE_JUMP 0.55 s 0.15 s đỉnh nhảy qua đòn quét root y +28 px
EVADE_ROLL 0.50 s 0.15 s giữa lăn (sau lưng địch) root x +40 px, flipX cuối

contact = DEFENSE_LEAD (0.15 s) cho mọi clip phòng thủ → bắt đầu clip ở t_commit thì khung chạm rơi đúng hit-frame địch (01 §5.1). Body-sheet đặt một khóa đúng tại 0.15 s nên khung contact bắt đầu đúng tick 9. Clip phòng thủ có thể bắt đầu từ bất kỳ pose nào (là phản xạ) — khung đầu là DEF_* đã giữ tư thế, không cần GUARD.

Clip địch dùng cùng cơ chế ngược lại: hits[0].t của đòn địch = hit-frame mà DefenseResolver nhắm; windup ≥ mức tối thiểu theo AttackKind (light 21 tick, heavy 36, thrust/sweep/grab 42, projectile 30, area 48 — 01 §11.3). ds-check từ chối nếu thiếu.

4.5 Thời gian: act → tick → MoveDef

  • Act viết bằng giây (pixelfx); export đổi sang tick 60 Hz nguyên (round(t·60)). Mọi dữ liệu runtime là tick.
  • HitDef.t (giây, hero_*.py) là thiết kế; act đặt khóa release sao cho hit rơi đúng. Lệch > 1 tick khi export → sửa act, hoặc sửa hero_*.py trong cùng task (không để hai nguồn lệch).
  • AtkSpeed chia thời lượng mọi tick của clip (cả hit, root, cue) — runtime dùng bộ tích lũy phân số (§6.2), không làm tròn từng khung.
  • Hit-stop không bake thành khung (pixelfx stop trong act chỉ để xem trước); runtime đóng băng đồng hồ actor (§6.3).

4.6 Ví dụ file đòn

// hero-kai/kai-l1.js — KAI_L1 Chém Ngang (L, dur 0.36, cancel 0.26, hit 0.12)
import { chain } from "../../core/index.js";
import { GUARD, WIND_LOW, CUT_MID, FOLLOW } from "./_poses.js";
import * as slash from "./fx-slash-l.js";
export const meta = { name: "KAI_L1", size: [112, 72], origin: [40, 64], duration: 0.36, fps: 30, actor: 2, costume: "kai" };
export const act = {
  target: { x: 30 },
  poses: [[-0.12, GUARD], [-0.04, WIND_LOW], [0, CUT_MID, "snap"], [0.06, FOLLOW, "snap"], [0.14, { ...GUARD, x: 6 }]],
  hits: [{ t: 0, stop: 0.033, shake: 0, amp: 0, push: 2 }],
  dust: [{ t: 0, x: 8, n: 4, dir: 1 }],
};
export const create = chain([{ fx: slash, t: -0.01, x: 6, y: -22, palette: "metal" }]);

Ở đây t0 = −0.12 → hit ở tick round(0.12·60) = 7 ↔ HitDef.t = 0.12 ✔; GUARD cuối ở 0.26 s tính từ t0 = cancel ✔.

5Địch, Thủ Vệ, Boss

5.1 Địch hình người — rig + costume

Lớp HUM-S/M/L (12 §4). Mỗi loại: src/fx/enemy-<id>/_costume.js (scale 0.85/1.0/1.25, vũ khí, ramp không trùng ramp chính anh hùng — INV-04), _poses.js riêng 15–20 pose, 3 đòn (atk-*.js), 4 loco (idle, walk, run, taunt), và thư viện phản ứng chung src/fx/_react/ (16 clip viết một lần trên pose tham số, render cho mọi costume):

flinch_hi, flinch_lo, stagger, launch_up, air_hit, air_fall, land_bounce, knockdown, getup, wallsplat, crumple, spin, broken (quỳ một gối, loop), offbalance, grabbed (paired), pulled (bị kéo — liềm xích/lưỡi hái), + 3 biến thể death (đứng, trên không, quỳ). Mọi clip phản ứng bắt đầu/kết thúc ở pose chung R_STAND để nối tự do; biến thể chọn tất định theo hash(actorId, hitIndex), không lặp 2 lần liền (chỉ hình ảnh).

5.2 Sinh vật — module dựa trên critters/*

Lớp CRT-S/M/L (dơi, chuột, slime, nấm, nhện, đốm hồn, sói xương…): module thuần draw(p, t, o) theo khuôn critters/bat, critters/slime, thêm tùy chọn o.state ∈ {idle, move, windup, attack, hurt, launch, die} và o.phase (0–1). creature-sheet.mjs render mỗi state ở 12 fps → cùng schema .pxbody (root = 0, chuyển động do runtime). Phản ứng rút gọn: hurt (nháy + lùi 1 px), launch (lộn 2 khung), die. Cánh/đuôi lặp seamless (cycle, loopNoise).

5.3 Thủ Vệ & Boss

  • Rig phóng to (scale 1.5–2.5) + costume + vũ khí địch (§3.3 đợt 2): đa số Thủ Vệ, Gorrak, Valcor (costume không đầu + fire/spirit-flame recolor ở cổ), Nereth, Brakka, Morvane, Vua Rỗng (2 costume = 2 pha).
  • Module riêng: Mẹ Bào Tử (thân nấm + 4 xúc tu spline), Hàn Long Skarn (đầu + cổ spline 16 đốt kiểu critters/snake + cánh 2 lớp), Song Tử Lăng Kính (2 rig ×1.6, costume pha lê + khúc xạ = VFX).
  • Bóng Gương (BS09): dùng body-sheet + MoveClip của anh hùng đang chơi, LUT hàng mirror (thân PAL.shadow, mắt PAL.neon), dư ảnh tím — 0 sprite mới.
  • Canvas đòn Boss ≤ 192×144, cao ≤ 96 px (hub §4). Đòn Boss có phase (1/2) và cờ armor (siêu giáp) hiển thị bằng viền shader trắng 1 px nhấp nháy 2 khung khi bắt đầu armor.

5.4 Báo hiệu đòn trong sprite

Mỗi đòn địch có ≥ 2 khung lấy đà giữ (đọc được), silhouette lấy đà khác silhouette đứng. Nháy đỏ đòn heavy (2 khung/0.1 s #ff2040) và viền đỏ thẫm đòn không chặn được là shader (skin red, chế độ viền) do runtime bật theo AttackKind — không bake. Glyph "!" / mũi tên / sóng / bàn tay là sprite UI kit (11 §3.3), neo trên đầu theo socket head.

5.5 Đòn ghép đôi (túm, kéo)

Đòn có tag grab (Rook, Sable, Boss túm anh hùng): MoveClip có pair = { t0, t1, socket: "handN", victimClip: "grabbed", offset: [x, y], release: { vx, vy } }. Từ t0 nạn nhân bám socket của kẻ túm theo từng khung (vị trí làm tròn px), phát grabbed; t1 → thả với vận tốc → phản ứng launch/knockback. Chỉ mục tiêu tgt:grab (01 §6.4).

6Unity runtime

6.1 Asset dữ liệu

ScriptableObject Từ Nội dung
BodyClipSet .pxbody sprite các ô (sub-asset, pivot riêng), LUT, BodyClip[] {frames: (spriteIdx, ticks, flags), root keys, sockets, loop}
MoveClip .pxmove tham chiếu BodyClip + hits, lock, contact, cues, projectiles, tethers, sfx, dust, pair
VfxSheet <name>@<pal>.json sprite màu + sprite sáng, fps, loop, pivot, khung
MoveDef (gameplay) HeroMoves.csv + HeroHits.csv 01 §6.2; nối với MoveClip theo id

Gameplay không đọc sprite; mô phỏng headless (INV-14) chỉ cần MoveDef + root/lock/hits của MoveClip (dữ liệu thuần, không Unity Object nặng) → MoveClipData struct tách khỏi phần sprite để autosim chạy không cần texture.

6.2 CombatClock & PixelClipPlayer

CombatClock (1 / trận):  acc += unscaledDt × gameSpeed(1|1.5|2|3) × globalSlowmo
                          while acc ≥ 1/60: acc −= 1/60; Tick()      // tối đa 8 tick/frame, phần dư dồn
Tick(): với mỗi actor:   if freezeTicks > 0 → freezeTicks−−; continue     // hit-stop cục bộ
                          clip.Advance(rate = AtkSpeed (đòn tấn công) | 1 (loco, phản ứng))
PixelClipPlayer.Advance:  pos += rate × 1000   (đơn vị 1/1000 tick, số nguyên)
                          while pos ≥ frameEnd[i]: i++ ; bắn sự kiện có tick ∈ (prev, now] theo thứ tự
                          root = EvalRoot(pos)  ; sprite đổi chỉ khi i đổi
  • Sự kiện (hit, cue, sfx, projectile, tether, pair, lock in/out) bắn theo thứ tự tick, kể cả khi một frame Unity chứa nhiều tick (x3) → không mất hit (01 §19).
  • Vị trí hiển thị = vị trí mô phỏng ở tick cuối, làm tròn art px; không nội suy giữa tick (pixel art cần dừng gọn).
  • Pin tiết kiệm (11 §8): render 30 fps, mô phỏng vẫn 60 Hz (2 tick/frame).

6.3 Hit-stop, slow-mo, tốc độ

Cơ chế Cách làm
Hit-stop cục bộ freezeTicks = max(hiện có, round(stop·60)) cho attacker + nạn nhân (01 §14.1); trần ngân sách 300 ms/1 s cho anh hùng
VFX của chính hit đó spawn ở hit, giữ khung 0 trong suốt freeze rồi mới chạy (khung va đọc rõ), cờ freezeWithOwner
Slow-mo toàn cục globalSlowmo 0.25×/0.3×/0.4× theo thời gian thực (01 §14.1); UI unscaled
Tốc độ x1–x3 nhân vào CombatClock, không vào Time.timeScale (INV-13)

6.4 Camera

Rung = offset nguyên art px theo công thức pixelfx (act.shake) với amp của hit (hoặc lớp hit 01 §12.1), cộng camera punch 2 px theo hướng đòn trong 2 tick ở hit H/F/K; Giảm nhấp nháy → biên độ ×0.5 (làm tròn xuống), cài đặt cường độ rung 0–100% (11 §8). Zoom 2×: Pixel Perfect Camera đổi refResolution sang 135×240 quanh điểm va (tâm kẹp trong phòng), vào/ra trong 1 tick (cắt cảnh, không zoom mượt — pixel không zoom lẻ).

6.5 Shader & mã hóa per-instance

Shader Dùng cho Ghi chú
Deepstair/PixelIndexed thân anh hùng/địch/Boss đọc R8 → _PaletteTex (point, không sRGB) hàng _Row; idx 0 → clip
Deepstair/PixelSprite VFX màu, môi trường, đạo cụ alpha blend thường
Deepstair/PixelAdditive trang sáng VFX Blend One One, tắt ở Low tier
Deepstair/PixelGhost dư ảnh cộng màu phẳng × bậc alpha

Để giữ batching (không dùng MaterialPropertyBlock), tham số mỗi actor đi qua SpriteRenderer.color (vertex color 8-bit):

Kênh Nghĩa
R nháy trắng: 0 / 1 (hit flash 1–2 khung, 01 §12.3)
G id skin (3 bit, 8 skin của pixelfx SKINS: flash, char, ice, shock, holy, shadow, red, poison) + bit chế độ (toàn thân / chỉ viền ink)
B lượng skin 0–1 (bước 1/8)
A độ hiện (dither Bayer 4×4 khi < 1: spawn/tan biến)
  • Skin trúng đòn (2–6 khung, toàn thân): char (hỏa), shock (lôi), ice (băng), holy, shadow, poison — đúng bảng SKINS của pixelfx.
  • Trạng thái kéo dài (Bỏng, Độc, Ám Ấn…): chỉ đổi màu viền + emblem nhỏ gắn đầu/chân (polish §4: không tô cả thân). Ngoại lệ có nghĩa: Đóng Băng = skin ice toàn thân + khối băng VFX.
  • Viền Tinh Anh theo affix và viền Vỡ Thế (trắng nhấp nháy) dùng chế độ "chỉ viền".

6.6 Dư ảnh

Pool 3 SpriteRenderer/actor (Low tier: tắt). Khi khung có cờ ghost (hoặc Dòng Chảy, Thức Tỉnh), mỗi 2 tick chụp sprite + vị trí hiện tại vào bản cũ nhất; sống 0.16 s (pixelfx GHOST_LIFE), độ sáng bậc 0.5 → 0.33 → 0.17; màu = costume.ghost; lớp FxBack (sau thân — anh hùng luôn sắc nét trước dư ảnh của mình).

6.7 Lớp & thứ tự vẽ

Sorting layer Nội dung
BG_Far / BG_Mid parallax ×0.2 / ×0.5 (12 §4.2)
Room room plate ×1.0, decal tường, vết nứt dội tường
Floor sàn, decal sàn (tro, máu tối, cháy xém), vùng báo hiệu area, đường ngắm
Shadow bóng elip của actor
FxBack nửa sau vòng VFX (drawBack), dư ảnh
Actors địch, anh hùng, đạn; order = 1000 − lane·10 (+5 anh hùng), lane = lệch dọc 0–6 px (01 §2)
FxFront VFX trước (tia lửa, vệt chém)
Light trang sáng cộng
WorldText số sát thương, chữ chiến đấu, glyph báo hiệu, thanh Thế/HP Tinh Anh
Foreground cột/xích tiền cảnh ×1.25, viền tối
ScreenFx impact frame, nền Tuyệt Kỹ, chuyển cảnh (quad bám camera)

UI canvas (Screen Space – Camera) nằm trên tất cả.

6.8 VFX player & pool

PixelVfx = 2 SpriteRenderer (màu + sáng) + chỉ số khung, chạy theo CombatClock (hoặc unscaled cho UI). Pool theo VfxSheet (PoolingManager), prewarm ở FloorIntro: số instance = tần suất cue tối đa trong 1 s của Cây Combo hiện tại (tính khi nhận Hồi Âm) + 2. Trần đồng thời: 48 VFX High / 32 Mid / 20 Low; vượt → bỏ cue cấp thấp nhất (bụi < tia lửa < vệt chém < hit chính, không bao giờ bỏ VFX phòng thủ).

6.9 Xích & đạn

ChainLine (Sable, móc lưỡi hái Mora): vẽ mắt xích sprite 3×2 (px_chain_link, xen ngang/dọc) theo đường Bresenham từ socket tay tới đầu liềm/tạ, võng bậc 2 theo slack (giảm dần khi căng), tối đa 112 px, mỗi tick tính lại từ dữ liệu tethers — không physics. Đạn: sprite VFX loop + hitbox tròn từ projectiles, bay tuyến tính/parabol (g = 950 px/s²), pool theo id.

7Import pipeline

Bước Công cụ Chi tiết
1 body-sheet.mjs / move-export.mjs (--out thẳng vào Assets/ArtAsset/...) ghi PNG + .pxbody/.pxmove/JSON
2 PixelArtImportRules (mở rộng) thêm gốc Assets/ArtAsset/Characters/ (thế giới, PPU 16, R8, sRGBTexture = false, Read/Write off, cắt theo rect của .pxbody thay vì lưới) và Assets/ArtAsset/Environment/ (PPU 16, RGBA32); _palette.png → RGBA32, point, không sRGB; _light.png cắt bằng cùng JSON (bỏ hậu tố _light), định dạng RGB24
3 PxBodyImporter (ScriptedImporter .pxbody) tạo BodyClipSet + sprite sub-asset (pivot = ox, oy), id sprite ổn định (MD5 như StableSpriteId)
4 PxMoveImporter (.pxmove) tạo MoveClip; lỗi schema = import error đỏ
5 Tools/Deepstair/Validate Moves (CI Editor) nối MoveDef ↔ MoveClip: hit ±1 tick, contact 9 tick, cancel ≈ GUARD, tổng root = move (±1 px), mọi fx/sfx id tồn tại

Thư mục: Characters/Heroes/H01_Kaito/{kai_body_p0.png, kai_palette.png, kai.pxbody, Moves/*.pxmove} · Characters/Enemies/<S0x>/<EN..>/ · Characters/Bosses/<BS..|WD..>/ · Vfx/Pixel/<category>/<name>@<pal>[_light].png · Vfx/Pixel/Hero/<code>/ · Environment/<S0x>/ · UIPixel/Combat/ (chữ chiến đấu, số, glyph).

8Atlas, bộ nhớ, Addressables

  • Thân: body-sheet đã pack sẵn trang R8 → không đưa vào SpriteAtlas Unity (atlas sẽ ép RGBA32). 1 trang = 1 MB (1024²).
  • VFX / môi trường / UI: SpriteAtlas V2, ≤ 2048², padding 2, không xoay, không tight packing, Full Rect; atlas màu RGBA32 và atlas sáng RGB24 tách riêng.
  • Compression None là luật hub (pixel không chịu nén); tiết kiệm bằng R8 index, trim, dedupe, nạp theo Địa Tầng.
Nhóm Định dạng Ngân sách Ghi chú
Thân anh hùng (1 anh hùng/run) R8 index, 1–2 trang 1024² (tối đa 2048×1024) ≤ 2 MB LUT palette RGBA32 256×8 = 8 KB
VFX chữ ký anh hùng màu RGBA32 + sáng RGB24, trim + pack ≤ 10 MB Low: bỏ trang sáng → ≤ 6 MB
VFX chiến đấu chung (lớp hit, phòng thủ, trạng thái, chết) RGBA32 + RGB24 ≤ 10 MB thường trú cả run
Địa Tầng: nền 3 lớp + room plate + tileset/props RGBA32 ≤ 8 MB 270×600 + 384×600 + atlas 1024²
Địa Tầng: địch thường (≤ 6 loại) R8, 1–2 trang 1024² ≤ 2 MB LUT chung Địa Tầng ≤ 255 màu
Thủ Vệ + Boss của Địa Tầng R8, 2048×1024 ≤ 2 MB nạp ở Cổng Boss x9, x4
VFX môi trường + đạn địch RGBA32 + RGB24 ≤ 6 MB
UI trong run (kit, HUD, chữ chiến đấu, số) RGBA32 ≤ 6 MB
Icon Hồi Âm động (LRU 16) RGBA32 sheet 8×64² ≤ 2 MB nạp theo lượt chọn
Tổng texture trong run — High / Mid / Low ≤ 56 / 44 / 32 MB Low: tắt light buffer + dư ảnh (01 §11.4)
Âm thanh trong run Vorbis; SFX nạp sẵn, nhạc stream ≤ 12 MB RAM SFX mono 44.1 kHz q0.5
Nhóm Addressables Nội dung Nạp / dỡ
combat_shared VFX chung, chữ chiến đấu, số, glyph, SFX chiến đấu khi vào Run, giữ tới Kết Quả
hero_H0x body, LUT, MoveClip, VFX chữ ký, SFX chữ ký anh hùng chọn ở RunPrep
stratum_S0x nền, tileset, địch thường, VFX môi trường, ambience S(n) khi vào tầng x1; nạp trước S(n+1) ở Cổng Boss x9; dỡ S(n) sau Boss
boss_S0x Thủ Vệ (nạp ở x4) + Boss (nạp ở x9) dỡ sau khi hạ
music_S0x nhạc stream như stratum
echo_icons mỗi icon một label khi lượt chọn sinh (INV-11), LRU 16
ui_meta_<feature> art màn meta khi mở màn

9Ngân sách khung & kích thước theo loại đòn

Khung = khung duy nhất sau gộp. Canvas = trần bounding box một khung (trim thật thường nhỏ hơn 40–60%).

Loại đòn Thời lượng (s) Khung duy nhất Canvas tối đa Dư ảnh
LIGHT 0.28–0.45 4–6 96×64 —
HEAVY 0.50–0.90 6–9 128×80 tùy
THRUST 0.30–0.55 4–6 144×64 —
SPIN 0.40–0.70 6–8 128×80 tùy
GRAB 0.60–1.20 6–10 128×96 —
THROW 0.50–1.00 6–9 128×96 —
TAUNT 0.60–1.20 5–8 64×64 —
LAUNCH 0.35–0.60 5–7 112×88 —
AIR 0.25–0.45 4–6 112×96 tùy
SLAM 0.40–0.70 5–8 128×96 —
DASH 0.20–0.40 3–5 160×64 có
COUNTER 0.25–0.50 4–6 128×72 có
SKILL 0.60–1.40 10–16 192×128 tùy
ULT 1.50–3.00 16–28 270×160 có
EXEC 1.20–2.50 14–24 192×128 có
DEFLECT 0.30 (contact 0.15) 3–4 64×64 —
BLOCK 0.30 (contact 0.15) 3 64×64 —
EVADE 0.35–0.55 (contact 0.15) 4–6 96×80 lùi/lăn
LOCO loop / 0.25–1.2 2–8 48×56 – 64×64 —
LOCO · idle loop 0.67 s 4 48×56 —
LOCO · run loop 0.50 s 6 48×56 —
LOCO · hurt1 / hurt2 0.25 s 2 48×56 —
LOCO · knockdown 0.80 s 5 48×56 —
LOCO · getup 0.50 s 4 48×56 —
LOCO · death 1.00 s 7 48×56 —
LOCO · victory 1.20 s 8 48×56 —
LOCO · descent (xuống thang) loop 0.67 s 8 48×56 —
LOCO · intro 1.00 s 6 48×56 —
PHỤ · camp_idle Trạm Đầu Thang, ngồi cạnh lửa 6 64×64 —
PHỤ · parried bị địch gạt — khựng 0.3 s 3 64×64 —
PHỤ · guard_break Vỡ Thủ 0.9 s 4 64×64 —
PHỤ · grabbed bị Boss túm (paired) 4 64×64 —
PHỤ · downed quỳ, đèn hồn chập chờn 4 64×64 —
PHỤ · revive bật dậy, vòng lửa đèn 5 64×64 —

Quy tắc thêm: khung release 2–3 tick (đòn L/M), giữ lấy đà ≥ 2 bước 12 fps cho H/F; ULT/EXEC chia "shot" — mỗi shot ≤ 8 khung, giữa shot cắt bằng VFX màn hình hoặc zoom 2× (không cần pose trung gian).

10Số lượng asset theo anh hùng

Đếm từ hero_*.py (cột Nguồn = data) hoặc kế hoạch 03 §2 (plan) khi chương anh hùng chưa có dữ liệu. Clip phụ (6/anh hùng, không phải MoveDef): camp_idle, parried (bị địch gạt, 01 §5.9), guard_break, grabbed, downed, revive + pose PORTRAIT.

Id Anh hùng Nguồn Đánh đất Hất/Không/Đập Lướt Phản Đòn Kỹ/Tuyệt Kỹ Tuyệt Sát Phòng thủ Loco MoveDef Clip phụ Tổng clip Khung duy nhất Trang R8 1024² File act Icon Hồi Âm động
H01 Kaito data 22 9 6 7 5 2 5 10 66 6 72 378 2 59 39
H02 Aldric data 27 9 3 9 5 2 5 10 70 6 76 407 2 63 40
H03 Vex data 29 10 6 8 5 2 5 10 75 6 81 428 2 68 40
H04 Brann data 25 12 2 7 5 2 5 10 68 6 74 405 2 61 40
H05 Lyra data 22 14 5 6 5 2 5 10 69 6 75 393 2 62 40
H06 Rook data 40 12 2 7 5 2 5 10 83 6 89 489 2 76 40
H07 Mora data 30 8 3 8 5 2 5 10 71 6 77 419 2 64 40
H08 Sable data 31 8 3 6 5 2 5 10 70 6 76 419 2 63 40
H09 Zerran data 34 10 4 6 5 2 5 10 76 6 82 438 2 69 39
H10 Feng data 39 12 3 6 5 2 5 10 82 6 88 456 2 75 39
H11 Momo data 41 10 1 7 5 2 5 10 81 6 87 461 2 74 39
H12 Isolde data 27 8 3 8 5 2 5 10 68 6 74 402 2 61 40
H13 Aurelle data 21 14 2 9 5 2 5 10 68 6 74 392 2 61 40
H14 Sorin data 20 13 9 7 5 2 6 10 72 6 78 396 2 65 40
H15 Tsubaki data 30 9 2 7 5 2 5 10 70 6 76 408 2 63 40
H16 Juno data 32 10 3 7 5 2 5 10 74 6 80 448 2 67 40
H17 Ignis data 33 7 3 9 5 2 5 10 74 6 80 406 2 67 39
H18 Elowen data 29 8 7 8 5 2 5 11 75 6 81 423 2 67 40
H19 Kazane data 28 11 1 7 5 2 5 10 69 6 75 416 2 62 40
H20 Taurok data 18 14 8 7 5 2 5 10 69 6 75 390 2 62 40
H21 Vulkar data 25 12 3 7 5 2 5 10 69 6 75 409 2 62 40
Σ 8 anh hùng 603 220 79 153 105 42 106 211 1519 126 1645 8,783 42 1371 835

File act = MoveDef không phải LOCO + _poses.js, _loco.js, _costume.js. Icon Hồi Âm động = Hồi Âm riêng COMBO/TECH/SKILL/ART/EVO (06 §9) → echo-icon.mjs, 0 chi phí AI.

11Cổng duyệt

Cổng Kiểm Ai / công cụ Đạt khi
G0 node tools/check.mjs <id> + node tools/ds-check.mjs <owner> (Q1, Q3, Q6, Q7, ngân sách §9, LUT) tự động 0 lỗi
G1 Contact sheet hero-sheet.mjs, tự duyệt theo bảng "you see → fix" của skill pixelfx; tối đa 3 vòng/clip agent tác giả không còn lỗi đọc được
G2 Subagent art-style-reviewer trên contact sheet (bản NEAREST ×4) — 3 đòn đầu mỗi anh hùng, mọi SKILL/ULT/EXEC, mọi đòn Boss, 20% còn lại lấy mẫu độc lập pass
G3 Scene PixelLab.unity: anh hùng vs hình nộm + địch thử, x1/x3, hit-stop bật; xem 0.25× dev Q1, Q2, Q4 đạt; log lỗi tick = 0
G4 Playtest cảm nhận mỗi milestone 10 người Q10 ≥ 4/5

Mỗi commit thêm clip kèm đường dẫn contact sheet trong Design/ArtPrompts/Anim/<owner>/ (thiếu → không merge).

12Đặt tên

Thứ Mẫu Ví dụ
Thư mục owner pixelfx hero-<code> / enemy-<EN..> / warden-<WD..> / boss-<BS..> (thường) hero-kai, boss-bs05
File đòn <move-id kebab>.js kai-l1.js, ald-sb1.js
VFX chữ ký id <code>_<name> ↔ hero-<code>/fx-<name>.js kai_slash_l
VFX chung mới ds-fx/, ds-ui/, ds-screen/ (270×480) ds-screen/impact-frame-portrait
Sheet VFX <name>@<palette>[_light].png parry@gold.png
Trang thân <code>_body_p<N>.png, <code>_palette.png, <code>.pxbody kai_body_p0.png
Sprite khung <code>_c<NNNN> (ô duy nhất) kai_c0042
MoveClip <MOVE_ID>.pxmove KAI_L1.pxmove

13Năng suất & lịch sản xuất

Hạng mục Số (≈) Năng suất (agent + review) Ngày-agent
Phần mở rộng M1 (rig hook, costume, 4 vũ khí, body-sheet, move-export, ds-check, hero-sheet, echo-icon) 9 công cụ — 12
Costume + thư viện pose mỗi anh hùng 8 1 ngày/anh hùng 8
Clip anh hùng ~480 10–12 clip/ngày 44
Địch người (dùng _react chung) 12 loại × 7 clip riêng 15 clip/ngày 6
Sinh vật 8 module × 10 state 3 module/ngày 3
Thủ Vệ + Boss ~420 clip 6 clip/ngày 70
VFX chung mới/re-author + chữ ký ~130 4/ngày 33
Tổng ≈ 176 ngày-agent

Chạy 3 agent song song → ~12 tuần lịch; vertical slice (Kaito, Aldric, 6 địch, Gorrak, Valcor ≈ 300 clip + 40 VFX) ≈ 4 tuần sau M1. Thứ tự: M1 công cụ → Kaito trọn bộ (khóa chất lượng, G2 + G4) → Aldric → địch S01–S02 → BS01/BS02 → còn lại theo Địa Tầng.

14Rủi ro

Rủi ro Giảm thiểu
Nhân vật trông như "hình nộm" chung costume + phụ kiện chữ ký + vũ khí riêng; G2 kiểm silhouette ở 128 px; mỗi anh hùng 1 chi tiết động (khăn, áo choàng, đuôi ngựa)
12 fps thấy giật với anh hùng nhanh (Vex) khóa snap + khung release 2–3 tick, vệt chém VFX 30 fps, dư ảnh, hit-stop
Rig không diễn được iai/khiên/xích vũ khí mới + pose fields shield, chain; xích dài vẽ runtime
Bộ nhớ (không nén) R8 index + LUT, dedupe GUARD, trim, nạp theo Địa Tầng, Low tier bỏ trang sáng
Lệch thời gian art ↔ gameplay tick nguyên, --check-csv, importer chặn, một nguồn thiết kế (hero_*.py)
Upstream pixelfx đổi API nhánh deepstair ghim commit, patch dạng hook, chạy check.mjs toàn thư viện sau rebase
Boss vượt lưới nháp rig BW/BH theo scale (R2)
VFX lớn nặng (chest 12 MB) trim + pack, render theo feature, không đưa vào kit
Bản quyền chỉ code MIT + effect tự viết; credit Hugo Duprez, Hollow Crown, Patch; không tái tạo sprite game có sẵn (bar 9)

15QA checklist

  • [ ] ds-check sạch cho mọi owner; check.mjs toàn thư viện sạch sau patch core.
  • [ ] Mọi MoveClip: hit trùng HeroHits.csv ±1 tick; contact phòng thủ = 9 tick; pose tại cancel ≈ GUARD.
  • [ ] PixelLab x3: số hit hiển thị = số hit log (INV-05); defense_contact_error_ticks = 0.
  • [ ] Không sprite nào có màu ngoài LUT; viền ink kín; 0 pixel đơn lẻ.
  • [ ] Đổi skin palette không đổi sprite; skin costume không đổi thân/vũ khí/ramp chính (INV-04).
  • [ ] Texture trong run ≤ ngân sách §8 ở từng tier (Memory Profiler, B1/B50/B100).
  • [ ] Lật trái/phải không lệch 1 px; zoom 2× không có pixel lẻ.
  • [ ] Hit-stop: chỉ attacker + nạn nhân đứng; VFX của hit giữ khung 0 suốt freeze.
  • [ ] Dư ảnh luôn sau thân anh hùng; Low tier không tạo dư ảnh/trang sáng.
  • [ ] Bóng Gương dùng đúng sprite anh hùng đang chơi với LUT mirror.