fighting-konoha/docs/ENGINE_NOTES.md

17 KiB
Raw Blame History

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).
  • 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/<id>/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).
  • brand.js (window.Brand) + content/loading.js (window.LOADING_QUOTES) + content/changelog.js (window.CHANGELOG): dimuat di index.html tepat setelah kartu #boot, sebelum precache.js. Kutipan acak (tidak pernah sama dengan yang terakhir, kunci konoha.loadquote; ?quote=N memaksa) tampil di #boot .boot-quote dan #load-state .load-quote. "Yang baru": Brand.decorate(root, screen) dipanggil menu.js di akhir render(); layar about mendapat panel .whatsnew-panel dan tautan kredit, layar home memicu toast .brand-toast satu kali bila versi teratas CHANGELOG berbeda dari localStorage['konoha.changelog.seen'] (pemain tanpa simpanan konoha.save.v1 tidak diberi toast). Menambah pembaruan: sisipkan objek baru di AWAL content/changelog.js dengan version lebih besar. Berkas content/*.js ikut tools/build_precache.py.
  • Latar menu utama dan About: menu.js collageHtml() menyusun tujuh potret assets/menu/<id>-select.webp (lima di layar sentuh) menjadi kolase CSS (.hc-* di bagian "Merek" paling bawah menu.css). Gerak berhenti lewat kelas .bg-still pada #front-menu (FrontEnd.syncBackground(): Pengaturan terbuka, tab tersembunyi, saklar "Animasi latar menu", prefers-reduced-motion). Video dan gambar bergaya game asal (home-factions.webp, home-factions-loop.mp4, home-art.webp) tidak dipakai lagi tetapi berkasnya masih ada (dan ikut daftar precache).
  • vs.js (window.VS, window.ResultView) + vs.css + content/quotes.js (window.QUOTES: intro/win/lose per karakter): layar VS dan layar hasil. startMatch({..., vs: true | {label, p2}}) menampilkan layar VS 2,2 dtk (hanya mode versus; latihan tidak); selama itu vsHold di game.js menahan advanceMatch dan cue 'Round 1' keluar tepat sekali saat VS selesai atau dilewati (klik/Spasi/Enter/Esc setelah 0,38 dtk). startMatch tanpa vs berperilaku seperti dulu. menu.js screen result memakai ResultView.view/mount (layar lama jadi cadangan); Meta.award menyimpan rincian di Meta.lastAward; Meta.nextFight/finishRun memakai VS dan layar hasil yang sama. Pemilih kutipan VS.quote(id, 'intro'|'win'|'lose') memakai pengacak kosmetik terpisah. API uji: __game.vsActive(), __game.vsSkip(). Menu depan yang tersembunyi dijamin hilang oleh #front-menu[hidden]{display:none!important} (menu.css); aturan display per data-screen tidak boleh menimpanya. Layar hasil memfokuskan wadah .rs (tabindex=-1), bukan tombol, agar Spasi/Enter dari pertarungan tidak langsung memicu MAIN LAGI (Tab tetap menjangkau tombol). Meta.toast menahan toast selama VS.active dan memunculkannya sesudah VS selesai.
  • Dialog pasangan dan easter egg berpasangan (Tahap 3.1 dan 3.4): content/dialogs.js (dimuat sebelum vs.js) mengisi window.DIALOGS['<idA>|<idB>'] = {a, b} untuk 21 pasangan (kunci urut alfabet; a = ucapan idA) dan window.DIALOGS.rahasia (empat pasangan tersembunyi, bentuk sama, bukan bagian dari 21 kunci). VS.dialog(idPemain, idLawan) -> {key, secret, l, r, reply} memetakan ucapan ke sisi kiri (pemain) dan kanan (lawan), atau null bila pasangan tidak punya dialog (karakter sama) sehingga kutipan umum QUOTES jadi cadangan. Pasangan ber-dialog menampilkan dua balon .vs-quote.vs-bubble (pembuka = ucapan a muncul dulu, pembalas = ucapan b menyusul lewat kelas .re, apa pun sisi pemainnya) dan layar VS berlangsung VS.durationDialog (3,3 dtk; VS.duration 2,2 dtk dipakai tanpa dialog); gaya di bagian "Dialog pasangan" vs.css. Dialog rahasia menggantikan dialog biasa setiap pasangan itu bertemu (balon keemasan .secret, label .vs-secret DIALOG RAHASIA) dan memanggil Meta.pairEgg(kunci): +75 koin dan satu toast hanya sekali per pasangan (Meta.save.eggs['dialog:<kunci>']), pencapaian reuni (tampil ??? sampai terbuka, deskripsi umum tanpa menyebut pasangan) setelah semua kunci DIALOGS.rahasia ditemukan. Toast ikut tertahan selama VS (aturan Meta.toast). Berlaku di versus, 2 pemain, Arcade, Survival; latihan tidak punya layar VS. Pedoman isi sama dengan quotes.js; Belum ada skenario otomatis khusus dialog (diuji manual; cek cepat: __game.startMatch({player:'soloman',enemy:'sonofsolo',stage:'harbor',level:'easy',mode:'versus',vs:true}), lalu Meta.save.eggs dan VS.dialog(idPemain, idLawan)). Pengujian yang menunggu layar VS selesai harus menunggu !__game.vsActive() (bukan tidur 2,2 dtk) karena waktunya kini bergantung pada ada-tidaknya dialog.
  • HUD (game.js): meter ultimate #ult-player/#ult-enemy (updateUltMeters; mengisi/SIAP/waktu mode dari modeOf, bwx.rage, bentuk FENR; DOM hanya ditulis saat tampilan berubah). Menu jeda #pause-panel dua panel; buildMoveList() membangun ulang Daftar gerakan tiap setPaused(true), pilihan lipat diingat di localStorage['konoha.moveList'].
  • Mode cerita Arcade dan bos Bayangan (Tahap 3.5 dan 3.6): content/story.js (window.STORY: intro[id] 2-3 kalimat, between[A][B] 1-2 kalimat (atau fungsi), ending[id] 3 kalimat, boss = pembuka, ucapan VS/kalah/menang, kalimat akhir, gelar; generic cadangan dengan penampung {name} {rival} {n}) dan story.js + story.css (window.Story). Story.show({kind: intro|between|boss|ending, player, enemy, step, total, stage, boss}, onDone) menampilkan kartu layar penuh (#story-screen, z-index 45, di atas layar VS 40) dan memanggil onDone tepat satu kali: klik/ketuk/Spasi/Enter/Esc setelah 0,5 dtk, atau otomatis (kartu antar-lawan sekitar 4 dtk, kartu panjang lebih lama); selama tampil semua tombol ditahan dan Meta menahan toast (Story.active). Mengembalikan false tanpa kartu bila tidak ada teks, jadi alur tetap jalan tanpa cerita. meta.js Arcade: startRun (kartu pembuka) -> nextFight -> menang -> betweenFights (kartu antar-lawan, panggung lawan berikutnya jadi latar kartu) ... lawan terakhir kalah -> arcadeCleared (hadiah tamat 500 + pencapaian langsung dibayar seperti dulu) -> kartu bos -> startBoss -> finishRun(true, 'win'|'lose') -> kartu akhir -> layar hasil. Survival tidak memakai cerita. run.boss = null | 'pending' | 'fight'. Menang atas Bayangan: +1000 koin, pencapaian BAYANGAN (+50), gelar PENAKLUK BAYANGAN (Meta.save.title, tampil di kartu akhir dan plat pemenang layar hasil); kalah: Arcade tetap tamat, tanpa bonus bos. Uji manual: ?arcade=N memendekkan Arcade menjadi N lawan. Bayangan = startMatch({player: X, enemy: X, level: 'excellent', shadow: true}) (id sama; state hero dan dummy tetap terpisah): setShadowFoe mengatur BALANCE.dummyMax (+40%), foeDamage menambah 10% pada damage ke pemain (pembungkus __game.receiveHit di akhir game.js dan dotDamage), dummyHpShown memberi skala HP dasar untuk bar dan penentu waktu habis (MatchRules.tick), drawDummy memakai ctx.filter gelap + lapisan ungu drawShadowTint + aura, percikan ungu lewat fxr() di updateVisuals. HUD: nama BAYANGAN dengan label nama asli, kelas .shadow-foe (story.css). VS: game.js memanggil Story.decorateVs(info) tepat setelah VS.show (nama 'BAYANGAN ', ucapan bos); layar hasil: ResultView.view membaca run.boss (Bayangan di sisi kalah; kalah dari Bayangan tetap tampil TAMAT). __game.shadowFoe() melaporkan keadaan.
  • assets/<id>/ atlas sprite (15 state), manifest.js (data + metrik), ui/; work/ (di repo induk, luar folder ini) berisi pipeline pembuat karakter.
  • precache.js/files.json: dibuat otomatis oleh tools/build_precache.py — jangan diedit atau di-commit oleh cabang fitur; perintahkan integrator membangun ulang.

API uji: window.__game

startMatch({player,enemy,stage,level,mode}) (mode versus|training), advance(detik) (memajukan simulasi tanpa menunggu waktu nyata), press(key)/release(key) (a d w s spasi i o p k l j; P2: arrowleft arrowright arrowup n m , .), setAI(bool), setP2(bool), snapshot(), hero, dummy, projectiles, effects, draw(), receiveHit(dmg), command(id). Layar tidak diperbarui saat advance; panggil draw() sebelum screenshot.

__game.hitDummy, __game.feel dan __game.feelState() membuka tabel rasa kena, status K.O./angka untuk tests/t1-feel.mjs. __game.fx.snapshot() melaporkan keadaan efek (bayangan, genangan api, DoT, aba-aba, proyektil beserta gaya/umur) untuk skenario uji. Untuk uji deterministik set __game.manual = true dulu; kalau tidak, loop waktu nyata ikut memajukan simulasi saat screenshot.

Alat uji headless

node tests/harness.mjs smoke — 7 karakter × easy/hard melawan CPU, gagal bila ada galat halaman. node tests/harness.mjs tests/foo.mjs — menjalankan skenario: export default async ({ page, game, errors, characters }) => hasil (game(fn, arg) = page.evaluate). Screenshot: await page.screenshot({path}) lalu Read gambar. tests/t1-fx.mjs melepas semua jurus enam karakter berbasis data dari kedua sisi (env FX_SHOTS = folder screenshot, FX_ONLY = regex nama jurus). Node dan Playwright sudah terpasang; tidak perlu npm install. tests/t2-hud.mjs (env HUD_ONLY=meter|enemy|cpu|pause|layout) menguji meter ultimate dan menu jeda; tests/t2-vsresult.mjs (env VSR_SHOTS, VSR_ONLY=quotes|vs|skip|abort|result|run|ui|focus|phone) menguji layar VS, layar hasil, dan kutipan. Skenario tests/t2-vsresult.mjs menunggu !__game.vsActive() (bukan tidur 2,2 dtk) karena layar VS ber-dialog 3,3 dtk. tests/t2-brand.mjs (env BRAND_SHOTS, BRAND_ONLY=text|quote|log|menu|flow|docs) menguji sisa nama game asal di seluruh layar, kutipan loading, Yang baru dan toast, serta latar kolase.

Aturan kerja

  • Jangan memakai Math.random/Date.now untuk logika yang memengaruhi hasil pertandingan bila ada pengacak random() bawaan (benih tetap).
  • Tampilan harus tetap muat di jendela setinggi 520 px; jangan memotong menu.
  • Tidak ada pustaka eksternal baru. Tidak ada gambar baru (tidak boleh memanggil generator gambar).
  • Commit atas nama rizkyaz0 <rizkyadizaelani@gmail.com>, tanpa baris Co-Authored-By atau atribusi apa pun. Jangan pernah git push dan jangan mengubah remote.
  • Teks HUD di index.html (nama, kelas, jurus) hanya placeholder netral; game.js menimpanya lewat fighterInfo. Cabang lama di fighterInfo dan manifest assets/mecha, assets/fenr adalah sisa game asal yang tidak terjangkau dari roster; jangan menampilkannya ke pemain.
  • Jaga gaya kode yang ada (ringkas, satu baris per pernyataan kecil, komentar hanya bila perlu).