# Catatan mesin (untuk yang mengerjakan fitur) Game fighting 2D canvas, tanpa dependensi dan tanpa langkah build. Semua teks untuk pemain dalam **bahasa Indonesia**. Dibangun dari AETHER CLASH (Bang Tutorial). ## Peta berkas - `index.html` memuat semua script berurutan: manifest tiap karakter → `announcer.js` → `match.js` → `feel.js` → `game.js` → `touch.js` → `menu.js` → `meta.js`. Gaya: `style.css` (arena/HUD), `feel.css` (peringkat kombo), `menu.css` (menu), `meta.css` (toko, karakter, koleksi, toast, tombol sentuh tambahan). - Urutan muat terkini dan titik temu fitur Tahap 3 (hasil integrasi `plan/t3`). Skrip: `brand.js` → `content/story.js` → `story.js` (blok awal, sebelum `precache.js`) ... `feel.js` → `content/passives.js` → `passives.js` → `content/quotes.js` → `content/dialogs.js` → `vs.js` → `game.js` → `touch.js` → `content/mystery.js` → `mystery.js` → `menu.js` → `meta.js`; gaya: `style.css`, `menu.css`, `meta.css`, `feel.css`, `vs.css`, `story.css`, `mystery.css`. Titik temu: (1) `startMatch` memanggil `refreshPassives(matchStyles(...))` lalu `setShadowFoe(...)`, dan info layar VS membawa `styles` (dipakai `Passives.decorateVS`) sekaligus `shadow` (dipakai `Story.decorateVs`). (2) `VS.show` menyusun dialog atau kutipan, lalu `Passives.decorateVS` menambah baris `.vs-perk` di AKHIR `.vs-info` (sesudah balon, supaya ekor balon tetap menunjuk papan nama); untuk Bayangan `Story.decorateVs` hanya mengganti teks `.vs-who`, `.vs-name`, `.vs-tag`, `.vs-quote` sisi kanan sehingga baris pasif tetap ada. (3) Bayangan memakai id yang sama dengan pemain, jadi `VS.dialog` mengembalikan null dan kutipan umum dipakai; CPU Bayangan mendapat gaya acak seperti CPU lain (`styleRand`), bukan gaya pemain. (4) Urutan pengali damage: `passiveDamage` dulu, lalu `foeDamage` (+10% Bayangan) di pembungkus `__game.receiveHit` dan `dotDamage`. (5) `Meta.toast` menahan toast selama `VS.active` atau `Story.active`, jadi toast `Meta.pairEgg` (dipicu dari `VS.show`) muncul sesudah layar VS. (6) `mystery.js` menghitung `eggs` tanpa kunci berawalan `code:` dan `dialog:`, dan `pairs` dari `dialog:*`; pencapaian `reuni` dan `penasaran_berat` sama-sama tersembunyi di Koleksi. - `game.js` (~2400 baris, satu IIFE): seluruh mesin. Pemain = `hero`, lawan = `dummy` (CPU atau P2). Fungsi penting: - `step(dt)` loop logika; `draw()` menggambar (`drawBackground`, `drawHero`, `drawDummy`, `drawEffects`, `drawCfBack/Front`, dst); `updateHud()`/`updateMatchHud()` mengisi HUD DOM. - Serangan: `startAttack`, `cast`/`castMove`, `meleeHit`, `fireSkill`, `hitDummy` (pemain memukul lawan), `window.__game.receiveHit` (lawan memukul pemain), `startCommand`/`specialHit`/`startThrow`/`useShopMove`/`heroGuard` (gerakan perintah, lempar, blok/parry, jurus toko). - Karakter data-driven: `window.FIGHTERS[id]` (dari `assets//manifest.js`) → `cfKit(d)` (gerakan), `cfStrike(actor, def, move)` (efek pukul: jenis projectile/wave/homing/dash/area/pillar/lob; ultimate volley/stampede/rain/pillars via `updateCfUltimate`). Bro Wo ditulis tangan (`browo.js` + `updateBrowo`, `browoStrike`). - Proyektil: array `projectiles`, diperbarui di `updateProjectiles(dt)`; efek visual: array `effects` (tipe `slash`, `impact`, `cf-fx`, `cf-pillar`, ...) digambar di `drawEffects()`; partikel `spawnParticles`; angka damage `damageNumbers`. - Efek jurus (blok "Efek jurus (FX)" di game.js, dipakai pemain dan CPU lewat `cfStrike`): proyektil CF punya umur (`p.age`) dan gaya gambar `p.fx` (rings, shout, chain, roll, spray, flame, bull, drive, wave, bolt, ant); `fxPart` membuat partikel kosmetik (pengacak terpisah `fxr()`, bukan `random()`); jurus area punya aba-aba cincin 0,25 s (`fxTeleP`), gelombang kejut dan retakan; dash meninggalkan bayangan sprite (`cfx.ghosts`). Flag data di manifest per skill (juga `mode.skills`): `fx`, `lift`, `trail`, `split`, `dot`, `bind`, `convoy`, `aftershock`, `pull`, `rebound`, `swarm`+`ramp`, `fire`; artinya ada di komentar "Mekanik jurus" di game.js. - Rasa kena (`feel.js`, `window.Feel`): tabel penyetelan `Feel.table` per jenis serangan (ringan/sedang/berat/ultimate/guard/parry/counter: hit-stop, getaran, sentakan layar, kilat putih, ukuran angka, riak), `Feel.classify(damage, {counter, shake})`, K.O. slow-motion (`Feel.ko`, skala hanya untuk dt simulasi visual; jam ronde tetap waktu nyata), angka damage pop (maks 8) dan peringkat kombo `#combo-rank`. Titik pakai di game.js: `feelImpact` (satu pintu efek), `feelHeroHit` (CPU memukul pemain), `hitDummy` (pemain memukul lawan), `startKoFx`. - CPU: `updateFenrAI`, `cpuProfile` (tingkat easy/medium/hard/excellent dari `match.js`). - Aturan ronde: `match.js` (`MatchRules`), dipakai `advanceMatch` di game.js. - `meta.js`: `window.Meta` — koin, toko, skin (filter warna), jurus toko (tombol K), varian skill, menu Karakter/Koleksi, pencapaian, easter egg, Arcade/Survival (`startRun`), kolom kode cheat di Pengaturan. Simpanan: `localStorage['konoha.save.v1']`. - Pasif, kelemahan, dan gaya bertarung (RENCANA 3.2/3.3): data di `content/passives.js` (`window.PASSIVES[id] = {name, desc, flaw, mods}`, `window.STYLES` agresif/seimbang/bertahan), helper dan teks angka otomatis di `passives.js` (`window.Passives`: `merge`, `describe`, `lockerHtml`, `decorateVS`). Mesin: `refreshPassives(styles)` di `startMatch` (juga `selectCharacter`, `setP2`) menggabungkan perk + kelemahan + gaya per sisi ke `sideMods`; semua pembacaan lewat satu titik kait `passiveMod(actor, key, base)` (hero = sisi pemain; selain itu = lawan, CPU atau P2, jadi simetris). Kunci: `dmgOut`, `dmgBasic`, `dmgSkill`, `dmgIn`, `walk`, `cd`, `cdSkill`, `cdUlt`, `parry`, `modeDur` (pengali) dan `regen` (HP/dtk, penambah). Damage dikali di `hitDummy`/`receiveHit`/`dotDamage` lewat `passiveDamage` (dibulatkan dengan sisa yang dibawa, tanpa pengacak); cooldown di loop cooldown `step`/`updateDummy`; jalan di `step`/`updateP2`/`updateFenrAI`; parry di `press('l')` dan peluang blok CPU di `hitDummy`. Gaya pemain disimpan di `Meta.save.style[id]` (`Meta.styleOf/setStyle`, simpanan lama tanpa kunci = Seimbang); `startMatch({styles:{player,enemy}})` menimpa, dan CPU di pertandingan ber-VS memakai gaya acak (pengacak terpisah `styleRand`). Teks tampil di menu Karakter (`renderLocker`), layar VS (`.vs-perk`), dan Daftar gerakan (`passiveRows`). API uji: `__game.passives()`, `__game.passiveMod(actor,key,base)`, `__game.setStyles({player,enemy})`. Catatan: P2 manusia tidak punya tombol blok, jadi kunci `parry` tidak berefek bagi P2. - `menu.js`: menu utama (home, pilih karakter, arena, hasil, about) di `#front-menu`; `window.FrontEnd`. - Slot misteri yang hidup (Tahap 3.7): lima petak `locked-N` di layar pilih karakter. Data `content/mystery.js` (`window.MYSTERY`, satu entri per petak menurut urutan; skema dan pedoman isi ada di komentar berkas itu), logika `mystery.js` (`window.Mystery`), gaya `mystery.css`; dimuat di `index.html` tepat sebelum `menu.js`. Tiap slot punya 3–4 petunjuk berurutan dengan syarat `need {type, n}` yang dibaca dari `Meta.save` (wins, matches, perfects, combo, survival, arcade, ach, eggs, pairs = `save.eggs['dialog:*']`, shop, coins; daftar `GOALS` di mystery.js; tahap `pairs` dilewati bila `window.DIALOGS.rahasia` tidak ada). Petak: siluet SVG sebaris (tubuh disalin dari `assets/menu/locked.svg`, benda khas muncul samar di tahap kedua terakhir dan jelas di tahap akhir) makin tajam lewat `--ms-p`, lencana `n/total`, tanda tanya berubah (`?` `??` `?!` `!?` `!`), titik BARU. Panel pratinjau (yang menampilkan sorotan: kiri saat memilih P1, kanan saat memilih lawan) memuat petunjuk terbuka, syarat petunjuk berikutnya dengan bilah progres, dan "Slot misteri n / total". Hook di menu.js: `Mystery.panel` (di `fighterPanel`), `decorate` (akhir `render()`), `preview` (di `updatePreview`: teks `#fighter-moves` dan `#roster-notice`), `notice` (di `confirm()` saat slot ditekan). Semua fungsi publik aman: bila gagal, tampilan lama slot terkunci dipakai. Petak tetap `data-fighter="locked-N"` karena easter egg `penasaran` (meta.js) membaca ketukan di atasnya. Semua petunjuk terbuka = pencapaian tersembunyi `penasaran_berat` (PENASARAN BERAT, +50 koin; dicek tiap layar pilih karakter dirender). Ingatan "sudah dilihat" untuk titik BARU dan toast ada di `localStorage['konoha.mystery.seen']` (bukan di `Meta.save`). Uji manual cepat: buka `?mystery=2` (atau `all`) untuk mempratinjau tahap 2 pada semua slot tanpa menyimpan atau membuka pencapaian; `Mystery.states()` di konsol meringkas kemajuan. Saat petarung sungguhan menggantikan satu slot, hapus entri slotnya dari `window.MYSTERY` supaya urutan tetap sama dengan petak `locked-N` yang tersisa. - `touch.js`: kontrol sentuh; tombol memanggil `__game.input(key, down)`. - Rekam ulang K.O. dan tangkapan layar (RENCANA 7.3 dan 7.4): `record.js` (`window.Record`) + `record.css`; tag di baris sendiri sebelum `` di `index.html`, **game.js tidak diubah**. Kait lain hanya di `vs.js`: `ResultView.view` menambahkan `window.Record?.resultHtml(r)` di dalam `.rs-main` (toolbar `.rs-rec` yang melayang di pojok kanan atas kolom tengah, jadi tinggi layar hasil tidak bertambah) dan `ResultView.mount` memanggil `Record.mount(root)`. Keadaan permainan dibaca dari DOM tiap 150 ms (`poll`): `body.in-menu`, `#arena[data-mode|data-match-phase|data-character|data-opponent]` (diisi `updateHud` tiap frame), `dialog[open]`, `VS.active`/`Story.active`, `document.hidden`; `__game.startMatch` dibungkus hanya untuk memulai sesi baru seketika. **Perekam**: kanvas salinan <= 854 px lebar (640 di layar sentuh) diisi dari `#arena` ~30 kali/dtk oleh rAF sendiri (letterbox bila rasio jendela berubah), `captureStream()` -> `MediaRecorder` webm (vp8; vp9/webm cadangan), tanpa audio, 1,5 Mbps (0,9 di layar sentuh), `timeslice` 1 dtk. Potongan WebM tidak bisa dipangkas di depan (hanya potongan pertama memuat header dan keyframe), jadi dipakai **dua perekam bergantian**: perekam baru dimulai tiap 6 dtk waktu rekam, tiap perekam berhenti dan dibuang sesudah 12 dtk, klip = perekam tertua (6 sampai 12 dtk, satu berkas WebM utuh tanpa menyambung kontainer). Jeda/Pengaturan/layar VS/kartu cerita/tab tersembunyi = `pause()` (jam rekam ikut berhenti); menu atau pertandingan dibatalkan = dibuang; latihan tidak direkam. Saat `matchPhase` menjadi `complete` (atau `resultHtml` lebih dulu, karena layar hasil dipasang pada tick yang sama dengan event itu) perekam tertua dihentikan jadi klip dan bingkai terakhir arena dibekukan untuk tombol FOTO di layar hasil; klip dan bingkai dibuang saat pertandingan berikutnya mulai. Layar hasil: SIMPAN KLIP (`konoha-fighting--vs-.webm`), TONTON ULANG (`` modal, Esc dan tombol B gamepad menutup), FOTO, BAGIKAN (`navigator.share` dengan berkas, selain itu unduh; PNG dibuat sinkron lewat `toDataURL` agar masih dalam gestur klik). HUD: tombol kamera di `.match-actions` (disembunyikan di lebar <= 620 px) dan penanda REC di bawah barisnya; menu jeda: baris TANGKAP LAYAR dan BAGIKAN; pintasan **F9** (arena, jeda, layar hasil). Pengaturan: dua baris disisipkan setelah slider Suara (Rekam ulang K.O. = saklar utama; Indikator REC = penanda saja). Simpanan `localStorage['konoha.record.v1'] = {on, badge}`. Bila `MediaRecorder`/webm/`captureStream` tidak ada atau terjadi galat, klip dan baris Pengaturan hilang tanpa suara (tangkapan layar tetap jalan). Konsol: `Record.state()`, `Record.set(false)`. Belum ada skenario otomatis (mode ringan, diuji manual); nilai di bagian atas record.js (`PERIOD`, `FPS`, bitrate di `start`) bisa disetel. - Gamepad dan ubah tombol (Tahap 4.5): `input.js` + `input.css` (`window.Input`), dimuat tepat setelah `touch.js`; satu-satunya kait ke game.js adalah `__game.input` (plus pembungkus `__game.startMatch/setP2` untuk mengetahui mode dua pemain; bila kelak ada `__game.p2Active()`, itu yang dipakai). **Ubah tombol** (Pengaturan > "Ubah tombol…", dialog `#keymap-dialog`): 11 aksi Pemain 1 disimpan di `localStorage['konoha.keymap.v1']` sebagai `{aksi: {c: e.code, l: label}}`. Hanya bila pemetaan berbeda dari bawaan, keydown/keyup ditangkap di fase capture pada `window`, diterjemahkan ke kunci kanonik (a d w s spasi i o p k l j) lewat `__game.input`, dan event aslinya dihentikan; tombol bawaan yang sudah dipindah ditelan (termasuk keyup-nya, supaya game.js tidak melepas kunci kanonik yang ditahan lewat tombol baru). Tidak aktif di menu depan (`body.in-menu`), dialog terbuka, layar VS/kartu cerita, atau kolom teks; Spasi/Enter pada `