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 chods-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)
- 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).
- Viền
ink #0b0a101 px (nhân vật, đạo cụ, icon); VFX không viền (lines + light). - 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. - 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.
- 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).
- 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
deepstaircủaTools/pixelfx(submodule), commit gốc ghim trong.gitmodules; không push upstreammainkhi 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.mjstoàn thư viện vẫn sạch). - Chỉ sheet đã export vào
Assets/;records/gitignored. Contact sheet duyệt copy sangDesign/ArtPrompts/Anim/<hero>/. - Credit giữ nguyên: Hugo Duprez (MIT), Hollow Crown (MIT), Patch — vào màn Credits (11 §8). Font
gallery/fonts/PatchPixelkhô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 đọccw/cltừ pose.body-sheet.mjstự điền:cw = clamp(−vx_root/120 − Δchest_x/6, −1, 1)với vận tốc root/ngực lấy trễlagkhung (áo choàng Kaito trễ 2 khung),cl = clamp(vy_root/200, −1, 1); khi đứng yêncwtắ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:
- 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ịpposeAtcủa pixelfx: tay chân giữ bước 12 fps); khóasnap→ giữ pose trước tới đúng khóa. - 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"). - 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). - Điền
cw/cl(§3.2),ghost→ cờ khung. - 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 ô. - 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.
- Xuất
<code>_body_p<N>.pngR8 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)
- Đòn mặt đất: khóa đầu và khóa cuối bằng đúng
GUARD(chỉxkhác).ds-checkso sâu mọi trường. - Pose tại
cancel(từ MoveDef) phải ≈GUARD(Q3): khóaGUARDcuố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. - Đòn
AIR: đầu/cuốiAIR_READY; đòn nhảy lên (move.dy > 0) đầuGUARDcuốiAIR_READY; đòn đập xuống đầuAIR_READYcuốiGUARDsau tiếp đất. - Cờ
linktrong 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.jsLINKS = [["VEX_A3", "VEX_A4"]]và pose cuối A ≡ pose đầu B. - Xoay: kết thúc ở
yaw = k·2π; quay người (TURN) dùngflipXở khung đầu, không xoay 180° bằng yaw. - 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ửahero_*.pytrong cùng task (không để hai nguồn lệch).AtkSpeedchia 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
stoptrong 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-flamerecolor ở 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ânPAL.shadow, mắtPAL.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ảngSKINScủ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
icetoà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-checksạch cho mọi owner;check.mjstoàn thư viện sạch sau patch core. - [ ] Mọi MoveClip: hit trùng
HeroHits.csv±1 tick;contactphòng thủ = 9 tick; pose tạicancel≈ 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.