49 KiB
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.htmlmemuat 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, sebelumprecache.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)startMatchmemanggilrefreshPassives(matchStyles(...))lalusetShadowFoe(...), dan info layar VS membawastyles(dipakaiPassives.decorateVS) sekaligusshadow(dipakaiStory.decorateVs). (2)VS.showmenyusun dialog atau kutipan, laluPassives.decorateVSmenambah baris.vs-perkdi AKHIR.vs-info(sesudah balon, supaya ekor balon tetap menunjuk papan nama); untuk BayanganStory.decorateVshanya mengganti teks.vs-who,.vs-name,.vs-tag,.vs-quotesisi kanan sehingga baris pasif tetap ada. (3) Bayangan memakai id yang sama dengan pemain, jadiVS.dialogmengembalikan null dan kutipan umum dipakai; CPU Bayangan mendapat gaya acak seperti CPU lain (styleRand), bukan gaya pemain. (4) Urutan pengali damage:passiveDamagedulu, lalufoeDamage(+10% Bayangan) di pembungkus__game.receiveHitdandotDamage. (5)Meta.toastmenahan toast selamaVS.activeatauStory.active, jadi toastMeta.pairEgg(dipicu dariVS.show) muncul sesudah layar VS. (6)mystery.jsmenghitungeggstanpa kunci berawalancode:dandialog:, danpairsdaridialog:*; pencapaianreunidanpenasaran_beratsama-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](dariassets/<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 viaupdateCfUltimate). Bro Wo ditulis tangan (browo.js+updateBrowo,browoStrike). - Proyektil: array
projectiles, diperbarui diupdateProjectiles(dt); efek visual: arrayeffects(tipeslash,impact,cf-fx,cf-pillar, ...) digambar didrawEffects(); partikelspawnParticles; angka damagedamageNumbers. - Efek jurus (blok "Efek jurus (FX)" di game.js, dipakai pemain dan CPU lewat
cfStrike): proyektil CF punya umur (p.age) dan gaya gambarp.fx(rings, shout, chain, roll, spray, flame, bull, drive, wave, bolt, ant);fxPartmembuat partikel kosmetik (pengacak terpisahfxr(), bukanrandom()); 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 (jugamode.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 penyetelanFeel.tableper 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 darimatch.js). - Aturan ronde:
match.js(MatchRules), dipakaiadvanceMatchdi 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.STYLESagresif/seimbang/bertahan), helper dan teks angka otomatis dipassives.js(window.Passives:merge,describe,lockerHtml,decorateVS). Mesin:refreshPassives(styles)distartMatch(jugaselectCharacter,setP2) menggabungkan perk + kelemahan + gaya per sisi kesideMods; semua pembacaan lewat satu titik kaitpassiveMod(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) danregen(HP/dtk, penambah). Damage dikali dihitDummy/receiveHit/dotDamagelewatpassiveDamage(dibulatkan dengan sisa yang dibawa, tanpa pengacak); cooldown di loop cooldownstep/updateDummy; jalan distep/updateP2/updateFenrAI; parry dipress('l')dan peluang blok CPU dihitDummy. Gaya pemain disimpan diMeta.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 terpisahstyleRand). 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 kunciparrytidak 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-Ndi layar pilih karakter. Datacontent/mystery.js(window.MYSTERY, satu entri per petak menurut urutan; skema dan pedoman isi ada di komentar berkas itu), logikamystery.js(window.Mystery), gayamystery.css; dimuat diindex.htmltepat sebelummenu.js. Tiap slot punya 3–4 petunjuk berurutan dengan syaratneed {type, n}yang dibaca dariMeta.save(wins, matches, perfects, combo, survival, arcade, ach, eggs, pairs =save.eggs['dialog:*'], shop, coins; daftarGOALSdi mystery.js; tahappairsdilewati bilawindow.DIALOGS.rahasiatidak ada). Petak: siluet SVG sebaris (tubuh disalin dariassets/menu/locked.svg, benda khas muncul samar di tahap kedua terakhir dan jelas di tahap akhir) makin tajam lewat--ms-p, lencanan/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(difighterPanel),decorate(akhirrender()),preview(diupdatePreview: teks#fighter-movesdan#roster-notice),notice(diconfirm()saat slot ditekan). Semua fungsi publik aman: bila gagal, tampilan lama slot terkunci dipakai. Petak tetapdata-fighter="locked-N"karena easter eggpenasaran(meta.js) membaca ketukan di atasnya. Semua petunjuk terbuka = pencapaian tersembunyipenasaran_berat(PENASARAN BERAT, +50 koin; dicek tiap layar pilih karakter dirender). Ingatan "sudah dilihat" untuk titik BARU dan toast ada dilocalStorage['konoha.mystery.seen'](bukan diMeta.save). Uji manual cepat: buka?mystery=2(atauall) 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 dariwindow.MYSTERYsupaya urutan tetap sama dengan petaklocked-Nyang 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</main>diindex.html, game.js tidak diubah. Kait lain hanya divs.js:ResultView.viewmenambahkanwindow.Record?.resultHtml(r)di dalam.rs-main(toolbar.rs-recyang melayang di pojok kanan atas kolom tengah, jadi tinggi layar hasil tidak bertambah) danResultView.mountmemanggilRecord.mount(root). Keadaan permainan dibaca dari DOM tiap 150 ms (poll):body.in-menu,#arena[data-mode|data-match-phase|data-character|data-opponent](diisiupdateHudtiap frame),dialog[open],VS.active/Story.active,document.hidden;__game.startMatchdibungkus 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()->MediaRecorderwebm (vp8; vp9/webm cadangan), tanpa audio, 1,5 Mbps (0,9 di layar sentuh),timeslice1 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. SaatmatchPhasemenjadicomplete(atauresultHtmllebih 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-<karakter>-vs-<lawan>.webm), TONTON ULANG (<dialog id=rec-replay>modal, Esc dan tombol B gamepad menutup), FOTO, BAGIKAN (navigator.sharedengan berkas, selain itu unduh; PNG dibuat sinkron lewattoDataURLagar 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). SimpananlocalStorage['konoha.record.v1'] = {on, badge}. BilaMediaRecorder/webm/captureStreamtidak 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 distart) bisa disetel. - Gamepad dan ubah tombol (Tahap 4.5):
input.js+input.css(window.Input), dimuat tepat setelahtouch.js; satu-satunya kait ke game.js adalah__game.input(plus pembungkus__game.startMatch/setP2untuk 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 dilocalStorage['konoha.keymap.v1']sebagai{aksi: {c: e.code, l: label}}. Hanya bila pemetaan berbeda dari bawaan, keydown/keyup ditangkap di fase capture padawindow, 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<button>dibiarkan seperti di game.js. Tidak bisa dipetakan: panah, N, M, koma, titik (Pemain 2), Esc, R, Enter, Tab, Ctrl/Alt/Cmd, tombol F; bentrok antaraksi = kedua tombol ditukar. Bantuan kontrol di Pengaturan ditulis ulang dari teks aslinya (5 baris pertama), labelkbddi HUD (kartu skill, meter ultimate; meter ditulis ulang game.js tiap keadaannya berubah jadi diamati) dan Daftar gerakan menu jeda (hanya bagian Kontrol dasar, Serangan dan skill, Gerakan perintah, Mode) mengikuti pemetaan lewat MutationObserver tanpa mengubah game.js. Daftar gerakan di menu Karakter (meta.js) dan teks "tombol K/I/O" di toko belum mengikuti. Gamepad: pollingrequestAnimationFramehanya selama ada gamepad (tata letak standar; pad pertama = P1, pad kedua = P2 bila mode dua pemain). Di arena: stik/D-pad = a d w s, A/Cross = w, X/Square = spasi, Y/Triangle, B/Circle, R1 = i o p, L1/L2/R2 = k l j, Start = Esc; pad P2 memakai kunci P2 (panah, n m , .). Di menu depan: keydown sintetis ArrowKeys/Enter/Escape yang ditangani menu.js (bila tidak ditangani, fokus dipindah dan tombol diklik); di dialog (pengaturan, jeda, ubah tombol), hamparan Meta, layar VS dan kartu cerita: fokus dipindah langsung, A = klik/Enter, B/Start = tutup (Esc lewat__game.input('escape')). Kontrol yang sedang ditekan saat konteks berganti diabaikan sampai dilepas. Getaran lewatvibrationActuatorsaathero.hpturun (dimatikan lewat Pengaturan,localStorage['konoha.pad.v1']). Gamepad API hanya jalan dilocalhost/HTTPS. Belum ada skenario otomatis (diuji manual). - Tata letak sentuh yang bisa diatur (RENCANA 4.6):
touchlayout.js+touchlayout.css, dimuatdeferdi<head>index.html (satu baris tag baru bersama manifest danpwa.js); hanya aktif dihtml.touch,touch.jstidak diubah. Pengaturan mendapat baris#tl-row(disisipkan lewat JS sebelum.dialog-buttons) yang membuka editor#tl-editor:<dialog>modal layar penuh yang menumpuk di atas Pengaturan (Pengaturan disembunyikan lewathtml.tl-editing, tetap terbuka sehinggasettingsOpenmenahan game; Esc ditangkap fase capture agar game tidak menutup Pengaturan). Di dalamnya ada salinan (cloneNode, tanpa pendengar daninert).dpad,.action-pad,.extra-padplus kotak seret.tl-boxdengan pegangan sudut; panah/+/- juga bekerja di papan ketik. Hasil ke kontrol asli hanya kelas.tl-xf+ variabel--tl-x/--tl-y/--tl-s(transform: translate() scale(), titik tumpu sudut dekat tepi); elemen bawaan tidak disentuh sama sekali. SimpananlocalStorage['konoha.touchlayout.v1']={v:1, g:{dpad|action|extra:{x,y,s}}}(x,y = pecahan lebar/tinggi layar, s = skala 0,6-1,8). Penjepitan ke layar memakaigetBoundingClientRectnyata (area = layar - tepi aman - 4 px - bilah editor 46 px di atas), dijalankanResizeObserverpada#touch-controlssaat kontrol tampil/ukuran berubah; nilai simpanan tidak ditimpa penjepitan.window.TouchLayout={open(), editing, layout}(konsol:TouchLayout.open()). Belum ada skenario otomatis dan belum pernah dijalankan di peramban oleh penulisnya (hanyanode --checkdan smoke desktop): uji manual di 844x390 dengan?touch=1. - PWA (RENCANA 4.7):
manifest.webmanifest(idkonoha-fighting,display: fullscreen+display_override [fullscreen, standalone], landscape, warna#101923, ikon PNG any dan maskable 192/512) ditautkan di index.html dan mobile.html bersamarel=icondanapple-touch-icon. Ikon diassets/pwa/dibuattools/make_pwa_icons.py(hanya Pillow) dariassets/menu/browo-select.webp; jalankan ulang bila ingin mengganti gambar.pwa.js(window.KonohaPWA = {available(), install(), onChange(fn)}) dimuat sinkron di<head>kedua halaman karenabeforeinstallpromptdatang ke dokumen paling atas: di HP itumobile.html, dan iframe game memakaiwindow.parent.KonohaPWA(panggilan langsung supaya gestur ketuk tetap berlaku untukprompt()). Baris#pwa-row("Pasang aplikasi") di Pengaturan hanya tampil bila event tersedia dan belum terpasang; iOS tidak punya event itu, jadi di sana tombol tidak tampil.sw.js: perilaku lama dipertahankan (gambar/font cache dulu, HTML/CSS/JS jaringan dulu); navigasi yang gagal dan tidak ada di cache mendapatoffline.htmldari cache kecilkonoha-offline-v1(terpisah dariaether-assetskarenapreload.jsmenghapus entri yang tidak ada diprecache.js).tools/build_precache.pykini memuatmanifest.webmanifestdanassets/pwa/*(offline.html,pwa.js,touchlayout.*ikut otomatis);precache.js/files.jsonperlu dibangun ulang oleh integrator. mp3/mp4 tetap dilayani jaringan, jadi tanpa internet game jalan tanpa musik. Server perlu MIMEapplication/manifest+jsonuntuk.webmanifest(sudah di.htaccess). - Bantuan dan aksesibilitas (Tahap 4.2 dan 4.3):
access.js(window.Access) +access.css, dimuat tepat sesudahfeel.js. Menyisipkan kotak lipat "Bantuan & aksesibilitas" ke#settings-panel(tombol biasa, bukan<details>, karena penangan tombol game mencegat Spasi pada<summary>), setiap pilihan punya tombol Atur ulang (muncul bila nilainya bukan bawaan) plus "Atur ulang semua". SimpananlocalStorage['konoha.access.v1']hanya berisi nilai non-bawaan;konoha.access.openmengingat kotak terbuka. Id pilihan:combo,block,nocd,regen(true/false),text(0 Normal, 1 Besar, 2 Sangat besar),cb(off|pro|deu|tri),calm(automengikuti prefers-reduced-motion |on),hc(true/false). Penerapan lewat kelas pada<html>:acc-t+acc-text-1|2(variabel--acc-zteks kecil dan--acc-zmteks sedang, dipakai lewatzoompada elemen teks saja),acc-cb+acc-cb-pro|deu|tri(filter SVG#acc-cb-*pada#game-shell, dibuat access.js; bar HP lawan bergaris dan lapisan cadangan bertitik),acc-hc,acc-calm(cermin aturan prefers-reduced-motion yang sudah ada di berkas CSS lain). Mesin membacawindow.Accesssecara malas dan tanpa modul ini semua kait menjadi no-op. Kait di game.js, semuanya hanya sisi pemain (hero):startAttack(Kombo otomatis: pukulan 1 dibuat denganqueued: 2),heroGuard+step(Blok otomatis: serangan dari depan saat di tanah, tidak beraksi, guard belum pecah, masuk jalur blok biasa laluhero.autoGuardmenahan sikap blok 0,3 dtk; guard tetap bisa pecah),step(Access.capCooldowns: skill 1 dan 2 selalu 0, ultimate paling lama 5 dtk),ultMaxdan pie cooldown (Access.cdMaxagar bilah ikut batas 5 dtk),useShopMove(shopCd0),passiveRegen(+2 HP/dtk, aturan 3 dtk tanpa terkena tetap berlaku),draw/drawFlash/updateHud(Access.motion: 0 = tanpa getaran layar, sentakan, zoom kamera Bro Wo, kilat sprite, potongan ultimate meluncur).feel.jsmemakai pengali yang sama lewatmotion()/calm()(zoom K.O. dimatikan, kilat K.O. dimatikan, vignette tetap, animasi peringkat kombo berhenti) danvs.jsmenghentikan hitung naik koin. Saklar "Animasi latar menu" dipadamkan lewat saklar aslinya (menu.js tidak diubah). Hadiah koin dan pencapaian tidak dibedakan saat bantuan aktif. Uji manual cepat di konsol:Access.set('nocd', true),Access.set('cb', 'deu'),Access.set('text', 2),Access.state(),Access.reset().precache.js/files.jsonperlu dibangun ulang oleh integrator agaraccess.jsdanaccess.cssikut tersimpan offline. - Tutorial singkat (Tahap 4.1):
tutorial.js(window.Tutorial) +tutorial.css, dimuat diindex.htmltepat sebelummenu.js. Tombol TUTORIAL kecil di pojok kiri atas layar utama (dipasangTutorial.decorate(root, screen), dipanggil di akhirrender()menu.js; tidak menambah baris tombol, jadi menu tetap muat di 520 px) memulaistartMatch({mode:'training', level:'easy', stage:'bellora'})dengan karakter terakhir yang dimainkan (atau Mega Chan) melawan Soloman (atau Mega Chan) sebagai dummy diam. Delapan langkah (STEPSdi tutorial.js: gerak, lompat, kombo, gerakan perintah, skill, blok dan parry, lempar, ultimate) dideteksi per frame dari__game.hero(state,jumps,action.type/special,blocking,cooldowns,spCd,throwCd) dan dua kait baru di game.js:__game.stats()(comboHits,bestCombo,totalDamage) serta__game.dummyAttack(slow)(serangan dasar pertama dummy, hanya di latihan; durasi dikalislowsupaya pembuka parry terbaca;indexdisetel 3 supayacpuFollowUptidak merangkai serangan kedua). Hasil serangan dummy (parry, blok, kena) dibaca dari pembungkus__game.receiveHityang dipasang tutorial (hanya mengamati, tidak mengubah hasil). Selesai = +50 koin satu kali (Meta.addCoins); tandanyaMeta.save.eggs['code:tutorial'](awalancode:supaya tidak dihitung sebagai kejutan olehmystery.js). Panel#tut-panelada di#game-shell, tengah atas di bawah header HUD (letaknya mengikuti tiga ukuran header di style.css);#announcementdigeser ke bawah panel lewatbody.tut-ondan--tut-bottom. Tutorial berhenti sendiri bila menu utama terbuka (body.in-menu). Dihtml.touchteks dan sasaran memakai tombol layar. Belum ada skenario uji otomatis; cek cepat di konsol:Tutorial.state(),Tutorial.start({player:'browo'}). brand.js(window.Brand) +content/loading.js(window.LOADING_QUOTES) +content/changelog.js(window.CHANGELOG): dimuat diindex.htmltepat setelah kartu#boot, sebelumprecache.js. Kutipan acak (tidak pernah sama dengan yang terakhir, kuncikonoha.loadquote;?quote=Nmemaksa) tampil di#boot .boot-quotedan#load-state .load-quote. "Yang baru":Brand.decorate(root, screen)dipanggilmenu.jsdi akhirrender(); layaraboutmendapat panel.whatsnew-paneldan tautan kredit, layarhomememicu toast.brand-toastsatu kali bila versi teratas CHANGELOG berbeda darilocalStorage['konoha.changelog.seen'](pemain tanpa simpanankonoha.save.v1tidak diberi toast). Menambah pembaruan: sisipkan objek baru di AWALcontent/changelog.jsdengan version lebih besar. Berkascontent/*.jsikuttools/build_precache.py.- Latar menu utama dan About:
menu.jscollageHtml()menyusun tujuh potretassets/menu/<id>-select.webp(lima di layar sentuh) menjadi kolase CSS (.hc-*di bagian "Merek" paling bawahmenu.css). Gerak berhenti lewat kelas.bg-stillpada#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 ituvsHolddi game.js menahanadvanceMatchdan cue 'Round 1' keluar tepat sekali saat VS selesai atau dilewati (klik/Spasi/Enter/Esc setelah 0,38 dtk).startMatchtanpavsberperilaku seperti dulu.menu.jsscreenresultmemakaiResultView.view/mount(layar lama jadi cadangan);Meta.awardmenyimpan rincian diMeta.lastAward;Meta.nextFight/finishRunmemakai VS dan layar hasil yang sama. Pemilih kutipanVS.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); aturandisplayperdata-screentidak 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.toastmenahan toast selamaVS.activedan memunculkannya sesudah VS selesai.- Dialog pasangan dan easter egg berpasangan (Tahap 3.1 dan 3.4):
content/dialogs.js(dimuat sebelumvs.js) mengisiwindow.DIALOGS['<idA>|<idB>'] = {a, b}untuk 21 pasangan (kunci urut alfabet; a = ucapan idA) danwindow.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), ataunullbila pasangan tidak punya dialog (karakter sama) sehingga kutipan umumQUOTESjadi cadangan. Pasangan ber-dialog menampilkan dua balon.vs-quote.vs-bubble(pembuka = ucapanamuncul dulu, pembalas = ucapanbmenyusul lewat kelas.re, apa pun sisi pemainnya) dan layar VS berlangsungVS.durationDialog(3,3 dtk;VS.duration2,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-secretDIALOG RAHASIA) dan memanggilMeta.pairEgg(kunci): +75 koin dan satu toast hanya sekali per pasangan (Meta.save.eggs['dialog:<kunci>']), pencapaianreuni(tampil ??? sampai terbuka, deskripsi umum tanpa menyebut pasangan) setelah semua kunciDIALOGS.rahasiaditemukan. Toast ikut tertahan selama VS (aturanMeta.toast). Berlaku di versus, 2 pemain, Arcade, Survival; latihan tidak punya layar VS. Pedoman isi sama denganquotes.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}), laluMeta.save.eggsdanVS.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 darimodeOf,bwx.rage, bentuk FENR; DOM hanya ditulis saat tampilan berubah). Menu jeda#pause-paneldua panel;buildMoveList()membangun ulang Daftar gerakan tiapsetPaused(true), pilihan lipat diingat dilocalStorage['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;genericcadangan dengan penampung{name}{rival}{n}) danstory.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 danMetamenahan toast (Story.active). Mengembalikan false tanpa kartu bila tidak ada teks, jadi alur tetap jalan tanpa cerita.meta.jsArcade: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=Nmemendekkan Arcade menjadi N lawan. Bayangan =startMatch({player: X, enemy: X, level: 'excellent', shadow: true})(id sama; stateherodandummytetap terpisah):setShadowFoemengaturBALANCE.dummyMax(+40%),foeDamagemenambah 10% pada damage ke pemain (pembungkus__game.receiveHitdi akhir game.js dandotDamage),dummyHpShownmemberi skala HP dasar untuk bar dan penentu waktu habis (MatchRules.tick),drawDummymemakaictx.filtergelap + lapisan ungudrawShadowTint+ aura, percikan ungu lewatfxr()diupdateVisuals. HUD: nama BAYANGAN dengan label nama asli, kelas.shadow-foe(story.css). VS: game.js memanggilStory.decorateVs(info)tepat setelahVS.show(nama 'BAYANGAN ', ucapan bos); layar hasil:ResultView.viewmembacarun.boss(Bayangan di sisi kalah; kalah dari Bayangan tetap tampil TAMAT).__game.shadowFoe()melaporkan keadaan. - Statistik pertandingan (RENCANA 5.5):
stats.js(window.MatchStats) +stats.css; tag-nya ada di baris sendiri sebelum</main>diindex.html. Statistik pemain 1 per pertandingan versus (juga 2 pemain, Arcade, Survival, Bayangan; latihan =reset(false), tanpa statistik). Kait di game.js, semuanya satu baris denganwindow.MatchStats?.:reset(versus)distartMatchdanreset;tick(dt, match, hero.hp, dummy.damage, BALANCE.dummyMax)di awaladvanceMatch(damage keluar/diterima = selisih HP per langkah, durasi = jumlah waktu fase fight, ronde = peralihan fight -> ko);cast(jenis, hero.action)dicastdanstartAttack;hit(hitSource(), counter, comboHits, hero.action)dihitDummy(juga jalur chip blok CPU) danhit(null, ...)di burstdotDamage;parry()/block()diheroGuard;finish(info)di event 'complete' menaruh ringkasan diinfo.stats(dibawaMeta.awarddanFrontEnd.showResult). Akurasi jurus = pemakaian yang kena sekali; pukulan proyektil/ultimate dikaitkan ke jurus terakhir dalam jendela waktu (MatchStats.WINDOW), jadi bila dua jurus bertumpuk bisa meleset satu. Layar hasil:ResultView.viewmemanggilMatchStats.resultHtml(tombol STATISTIK di kanan baris skor + kartu.rs-msyang menutup kolom tengah di atas tombol aksi, diaturMatchStats.mount; Esc menutup kartu lebih dulu daripada kembali ke menu); tanpainfo.stats(data uji) tidak ada tombol. Simpanan seumur hidup:Meta.awardmemanggilMatchStats.commit(save.stats, r.stats)(kunci barudmgDealt, dmgTaken, parries, blocks, throws, counters, rounds, playSec, statMatches;bestComboikut dinaikkan ke kombo terbaik seluruh ronde; simpanan lama tanpa kunci terbaca 0), tampil di Koleksi lewatMatchStats.lifetimeHtml. Uji manual cepat:MatchStats.snapshot()di konsol saat pertandingan berjalan. Belum ada skenario otomatis khusus statistik (mode ringan, diuji manual). - Kode tantangan dan kebiasaan bermain (RENCANA 7.5 dan 7.6):
challenge.js(window.Challenge,window.Habits) +challenge.css; tag-nya sebelummenu.jsdiindex.html. Kode = 8 karakter dari 30 simbol tanpa 0 1 I L O U (contohTNNB-TPZM), isinya 24 bit (versi, variasi, aturan, tingkat, arena, lawan, pemain) + 15 bit pemeriksa yang dicampur;Challenge.encode({player, enemy, stage, level, rule})/Challenge.decode(teks)(menerima huruf kecil, spasi, tanda hubung, tautan?kode=, atau kalimat yang memuat kode; hasil{ok, cfg}atau{ok:false, err, msg}). Daftar karakter, arena, tingkat, dan aturan di berkas itu menentukan arti kode: urutannya tidak boleh diubah, hanya ditambah di belakang (karakter/arena baru yang belum didaftarkan ikut otomatis di belakang menurut urutan deklarasi). Aturan khusus (RULES, satu per kode):blitzAdu cepat (satu ronde, HP awal separuh),oneSatu ronde,fastLawan gesit (jalan +25%, cooldown pulih +35%),noblockTanpa blok (blok dan parry mati untuk kedua sisi),heavyPukulan berat (damage +50% kedua sisi); kuncinya hanya hidup di kode, tidak disimpan di localStorage (jadi tidak bentrok dengan aturan harian). Alur: tombol KODE TANTANGAN di pojok kiri atas menu utama (di sebelah TUTORIAL, absolut supaya tinggi menu tidak berubah) membuka dialog#ch-dialogyang memvalidasi sambil mengetik dan langsung memulai (Challenge.start(cfg)memanggilstartMatch({... mode:'versus', vs:{label}, codeRule:{rule, code}}));?kode=XXXXXXXXdi alamat membuka dialog yang sama sebagai undangan setelah boot (parameter dihapus dari alamat sesudahnya;mobile.htmlmeneruskan query ke iframe). Layar arena (hanya versus melawan CPU) mendapat barisATURAN(menggilir aturan, berlaku juga untuk LET'S FIGHT dan MAIN LAGI lewatChallenge.pending(mode, p2)dimenu.js start(); menu utama mengosongkannya) /SALIN KODE/TAUTANdi kolom kanan.match-rules; layar hasil mendapatSALIN KODE/TAUTANdi pojok kanan atas.rs-head(tidak untuk Arcade, Survival, dua pemain, atau Bayangan). Penyalinan:navigator.clipboard, laluexecCommand('copy'), laluprompt(). Kait (semuawindow.Challenge?.x?.(), dibungkusguard: galat hanya dicatat di konsol, tanpa berkas ini game berjalan seperti dulu): game.jsbegin(options)distartMatchsesudah validasi dan sebelumrefreshPassives(menetapkan aturan aktif, judul layar VS "TANTANGAN · ...", penanda.ch-badgedi bawah jam HUD, dan mencatat kebiasaan),tune(sideMods)di akhirrefreshPassives(aturan = pengali kuncipassiveMod),roundStart(hero, dummy, BALANCE)di akhirresetCombat(HP awal),roundEnd(match, event)danfinish(info)diadvanceMatch(satu ronde menetapkanmatch.winnersebelum efek K.O. akhir;finishmengingat pengaturan untuk SALIN KODE dan mematikaninfo.perfectpada aturan satu ronde supaya bonus/pencapaian BERSIH tidak murah),hud(match)diupdateMatchHud(teks "SATU RONDE"),noBlockdipress(tombol L) danheroGuard; menu.jsdecorate(root, screen, {player, enemy, stage, level, mode, p2, runKind, result})di akhirrender().startMatchmengabaikancodeRuleuntuk latihan, dua pemain, dan Bayangan. Aturan tidak memakai pengacak, jadi hasil pertandingan tetap deterministik. Kebiasaan bermain (Habits):localStorage['konoha.habits.v1']={v:1, n, ch, mode, stage, foe}(hitungan per karakter yang dipilih, mode, arena, dan karakter lawan; tanpa waktu dan identitas), dicatat dibegin()tiap pertandingan dimulai (latihan juga; Bayangan tidak dihitung sebagai lawan; mode dikenali darioptions.mode,vs.p2, awalan label ARCADE/SURVIVAL,codeRule, atauoptions.kinduntuk mode baru). Terpisah dariMeta.save, jadi simpanan lama tak tersentuh dan data rusak dibaca sebagai nol. Bagian "Kebiasaan bermainmu" (karakter, mode, arena favorit, lawan paling sering, tombol HAPUS STATISTIK) disisipkan ke tab Koleksi olehMutationObserverpada#meta-overlay(sebelum daftar pencapaian;meta.jstidak diubah); catatan privasi satu baris di layar About lewatdecorate. Belum ada skenario otomatis dan UI belum diperiksa di peramban oleh penulisnya (hanyanode --check, smoke, dan pemeriksaan penyandi sekali pakai: 5.880 kombinasi bolak-balik tanpa tabrakan): uji manual di 1024x520 dan 844x390.precache.js/files.jsonperlu dibangun ulang agarchallenge.js/challenge.cssikut tersimpan offline. Konsol:Challenge.state(),Challenge.open(),Habits.data(). - Tantangan harian dan hadiah harian (RENCANA 7.1 dan 7.2):
daily.js(window.Daily) +daily.css, tag<script>paling akhir diindex.html(sesudahmeta.js, karena membungkusMeta.award) dan<link>CSS di<head>. Tanggal lokal -> hash -> satu tantangan yang sama untuk semua pemain: aturan (10 entriRULES: Darah satu, Hanya ultimate, Tanpa blok, Lawan cepat, Lantai licin, Tanpa lompat, Kombo wajib, Satu ronde, Counter saja, Raksasa), lawan, arena, dan tingkat (tiap aturan punya daftarlevels; aturan yang menyulitkan hanya memakai easy/medium). Tombol kecil HARIAN dipasangMutationObserverpada#front-menudi layarhome(item flex di.home-top-right, tepat di kiri teks versi, dengan margin atas dan bawah negatif setinggi setengah tombol sehingga kotak margin-nya 0 dan tinggi menu tidak berubah; sengaja bukanposition:absolutedan.home-top-righttidak dijadikan induk posisi karena tombol layar penuh memakai.home-topsebagai induknya; tanpa kait di menu.js) dan membuka kartu<dialog id="daily-dialog">(Esc dan panah ditangani pendengarkeydownfase tangkap di window karena game.js menelan Esc): aturan, deskripsi, lawan, arena, tingkat, hadiah, streak, pilihan petarung (lawan hari ini tidak bisa dipilih). Mulai =__game.startMatch({..., mode:'versus', daily:'YYYY-MM-DD', vs:{label}}); pertandingan versus biasa.Daily.arm(options.daily, {sideMods, balance, versus})dipanggil di SETIAP startMatch (setelahsetShadowFoe), jadi pertandingan biasa otomatis mematikan aturan dan mengembalikanBALANCE.heroMax. MAIN LAGI di layar hasil mengulang tantangan yang sama (klik pada[data-cmd="rematch"]ditangkap fase capture dan menandaiagain). Menang = 150 koin + 25 per hari beruntun (maks +150) lewatMeta.addCoins, sekali per tanggal; pembungkusMeta.awardmenambah satu baris keMeta.lastAward.lines(muncul di rincian koin layar hasil) dan menaikkangain. SimpananMeta.save.daily = {last, streak, best, total, greet, fest, pick}(ikut kode simpanan dan HAPUS SEMUA; simpanan lama tanpa kunci terbaca kosong; menyimpan =Meta.addCoins). 7.2: hadiah hari pertama +25 koin sekali per tanggal saat menu utama pertama tampil (toast), kejutan tanggal sekali per tahun (FEST: 1 Jan, 14 Mar Hari Pi, 8 Agu Hari Kucing, 17 Agu Dirgahayu 100 koin). Kait di game.js, semuanyawindow.Daily?.xxxdan no-op tanpa tantangan aktif:arm(startMatch),deny(jenis)(jump, startAttack, cast, startCommand, startThrow, useShopMove,press('l')),cooldownsdanslip(step),slip(updateDummy),hit(hitDummy, sesudah bonus counter),parry(heroGuard),foeScale(drawDummy). Aturan yang cukup dengan angka memakaisideModsdanBALANCEyang diberikan kearm(HP Darah satu dan Raksasa,walk/cdlawan,parrylawan 0). HP pemain kini lewatheroHpShown()(skala 200 sepertidummyHpShown) untuk bar HP dan penentu waktu habis. Satu ronde:MatchRules.createdantickdibungkus di daily.js (match.toWin = 1, pemenang ronde pertama ditetapkan sebelum game.js membacamatch.winner);updateMatchHudmembacamatch.toWin. Layar VS: pembungkusVS.showmenambah baris.vs-daily(label VS jadi TANTANGAN HARIAN); arena:#daily-tagdi tepi atas tengah (berkedip saat aksi yang dilarang ditekan) danbody[data-daily-rule]. Tanggal tidak dipakai di dalam simulasi (hanya memilih aturan dan hadiah). Uji manual:?daily=YYYY-MM-DDmengganti tanggal untuk seluruh modul (aturan, hadiah, streak); konsolDaily.setDate('2026-08-17')/Daily.setDate(null),Daily.table(14),Daily.status(),Daily.startup(),Daily.resetState(),Daily.open(),Daily.start('megachan'). Belum ada skenario otomatis; angka aturan (COUNTER_*,LICIN,LAWAN_*,HP_*) belum disetel dengan permainan nyata.precache.js/files.jsonperlu dibangun ulang oleh integrator agardaily.jsdandaily.cssikut tersimpan offline. assets/<id>/atlas sprite (15 state),manifest.js(data + metrik),ui/;work/(di repo induk, luar folder ini) berisi pipeline pembuat karakter.- Bahaya arena (RENCANA 6.2):
hazard.js(window.Hazard) +hazard.css, dimuat tepat sesudahgame.js. Satu kejutan per arena (tabelDEFS; arena tanpa entri tidak punya bahaya): bellora = genting jatuh (6 damage), sunspire = silau matahari (layar memutih, tanpa damage; CPU ragu 0,55 dtk), harbor = ombak dari kiri yang mendorong ke kanan sampai batas bertanda (3 damage, lompat atau berdiri di kanan garis batas untuk menghindar), elderwood = dua akar menyembul (5 damage), moonrise = petir arkana (7 damage). Tiap bahaya diberi aba-aba 1 detik (cincin/garis di lantai, kilau di langit, suarasound(), dan teks "Awas: ..." di baris kecil.hz-tagdalam.stage-sign; saat tenang barisnya "Bahaya: "). Aturan: hanyamatch.mode === 'versus'fase fight (latihan dan tutorial tidak); bahaya pertama 6-12 dtk sesudah ronde mulai, berikutnya 9-14 dtk sesudah aba-aba sebelumnya; tidak mulai di 4 dtk pertama, saat ultimate/sinematik (hazardCtx.busy), saat salah satu petarung jatuh, atau di 3,5 dtk terakhir ronde. Damage dikali pasifdmgInsisi yang kena, tidak pernah mematikan (menyisakan 1 HP), tanpa luka/knockback, tidak menambah kombo, dan melewati petarung yang sedang kebal (invuln); zona = jarak datar ke pusat tanda (sama dengan cincin yang tergambar) dan tinggi kaki di atas lantai (70, petir 100), jadi lompat atau geser menghindar. Pemain, CPU, dan P2 kena aturan yang sama; CPU mencoba keluar dari zona lewat rencanasidestepbawaan (peluang =evadetingkat, jeda =reaction; hanya bahaya berzona, bukan ombak). Deterministik: pengacak LCG sendiri berbenih (benih mesin saat ronde mulai + nomor ronde + arena) dan waktu =dtsimulasi; pengacak kosmetik terpisah, dan "Kurangi gerakan" (Access.motion) hanya mengubah efek (tanpa getar dan kilat putih, silau jadi semburat redup, serpihan sedikit), bukan jalannya pertandingan. Kait di game.js:Hazard.step(dt)di akhirstep()(sebelumfeelWatchKo),Hazard.draw(ctx, 'back'|'front')didraw()(tanda lantai sebelum petarung, efek sebelumdrawSquadron/drawEffects), dan__game.hazardCtx(gettermatch,stage,time,seed,menu,busy,cpu;sound;cpuRead();cpuDodge(dir, dur)). Semburat layar memakai elemen DOM#hz-glare(di atas kanvas, di bawah HUD). Dimatikan lewat Pengaturan ("Bahaya arena", bawaan Aktif,localStorage['konoha.hazard']) atauHazard.enabled = false(sementara, tidak disimpan; untuk tantangan khusus); keduanya harus menyala. Uji manual:?hazard=cepatdi URL (bahaya tiap 3-4,5 dtk),Hazard.trigger()di konsol saat versus berjalan,Hazard.state(),Hazard.onEvent(fn)(jenisaba-abadankena). Catatan: statistik pertandingan menghitung damage dari selisih HP, jadi damage bahaya ikut tercatat sebagai diterima pemain atau dikeluarkan pemain; simulator keseimbangan (tests/bot.mjs, arena Bellora) ikut memainkan bahaya, setHazard.enabled = falsedi sana bila ingin angka tanpa bahaya. Belum ada skenario otomatis dan belum dilihat di peramban oleh penulisnya (hanyanode --checkdan smoke). precache.js/files.json: dibuat otomatis olehtools/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. Satu perintah untuk semuanya: node tests/all.mjs (sintaks semua berkas JS, smoke, 49 pasangan arah singkat, data peringkat dan Koleksi; lihat bagian Tahap 5 di bawah).
Simulator keseimbangan, peringkat, dan uji otomatis (Tahap 5)
tests/bot.mjs:installBalance()dijalankan di dalam halaman (page.evaluate(installBalance)) dan memasangwindow.BalSim = { bot, play, round, think, reset, ... }. Game tidak diubah: bot hanya memakai API uji (__game.press/release/advance/startMatch/snapshot/hero/dummy/projectiles/config/balance) dan__game.manual = true. Bot menggerakkan sisi hero; sisi lawan dikendalikan CPU bawaan. Bot mendekat (berlari lewat ketuk dua kali bila jauh), menyambung kombo hanya bila pukulan berikutnya akan mengenai (landsAt: lawan tidak kebal, jatuh, atau di luar stun), memakai skill 2 lalu skill 1 saat jaraknya cocok (jangkauan dibaca dari data karakter, jadi ikut berubah saat angka disetel; Bro Wo ditulis tangan), mengakhiri kombo dengan skill, menjaga jarak saat lawan kebal, memblok atau parry sebagian serangan yang terbaca setelah jeda reaksi ~0,17 dtk, melompati proyektil rendah, dan membalas ultimate lawan dengan ultimate (atau memakainya sendiri setelah 8 dtk; di mode pasca-ultimate setelah 2 dtk) sambil menunggu lawan bangun. Tidak memakai lempar, jurus perintah, atau jurus toko, dan tidak menghindari genangan api/asam. Penyetelan bot ada di objekC(bisa ditimpa lewat envBAL_BOT).tests/balance.mjs(simulator):node tests/harness.mjs tests/balance.mjs [tanggal]. Tiap pasangan 7 x 7 (termasuk cermin) dimainkanBAL_Nronde per arah (bawaan 6), di kedua posisi, tanpa render (advance(dt, true)). Satu ronde =startMatch(gaya Seimbang, arena Bellora, tingkatBAL_LEVEL, bawaan hard) lalu bot bermain sampai fase K.O.; simulator berhenti tepat di fase itu, jadiMeta.awarddan layar hasil tidak pernah terpanggil. Hasil per ronde: pemenang, HP akhir, durasi, waktu ultimate pertama tiap sisi, bendera jurus terpakai. Env:BAL_N,BAL_LEVEL,BAL_DATE(atau argumen ke-2; tanggal diberikan, bukanDate),BAL_SEED,BAL_ONLY=a,b,BAL_WORKERS=k(k proses Chromium paralel),BAL_BOT(json),BAL_REPORT(bawaandocs/balance-report.md,none= jangan tulis),BAL_JSON(simpan hasil ringkas),BAL_BASE(hasil "sebelum" untuk tabel perbandingan),BAL_TIERS=1(tuliscontent/tiers.js),BAL_LABEL. Deterministik untuk benih, N, dan jumlah pekerja yang sama (pengacak mesin tidak diatur ulang tiap ronde, jadi urutan ronde ikut menentukan). Satu ronde memakan ~0,1 dtk nyata; N=8 (392 ronde) sekitar 30 sampai 50 dtk dengan 2 pekerja. Bagian laporan di bawah penandacatatan-manualdipertahankan saat laporan dibuat ulang. Cara membaca hasilnya dan batasannya ada didocs/balance-report.md(bot menang ~80% karena keunggulan posisi hero; persen menang per karakter menggabungkan kedua posisi).- Peringkat (RENCANA 5.2):
content/tiers.js(window.TIERS = {updated, order, rate, tier}) dibuat otomatis oleh simulator (BAL_TIERS=1), jangan diedit tangan; batas tier diTIER_CUTpadatests/balance.mjs(S >= 58, A >= 52, B >= 46, selebihnya C).meta.js(tiersHtml,loadTiers) memuat berkas itu secara malas lewat elemen script saat Koleksi pertama dibuka (index.html tidak diubah) dan menampilkan "Peringkat karakter (versi simulator)" dengan keterangan bahwa angkanya bukan kebenaran mutlak; tanpa berkas atau bila gagal dimuat, bagian itu tidak tampil. Berkas baru ini ikuttools/build_precache.py(integrator membangun ulang precache). - Penyetelan angka (RENCANA 5.3): riwayat sebelum dan sesudah, daftar angka yang diubah, dan temuan (rangkaian ultimate menentukan hampir semua ronde, CPU hampir tidak pernah memakai skill 2 area tanpa
aheadmilik Ethanol Man dan Solo Man) ada didocs/balance-report.md. Setelah mengubah angka jurus atau pasif, jalankan ulang simulator denganBAL_BASE=docs/balance-after.jsonsupaya laporan membandingkan dengan keadaan sebelumnya. - Uji otomatis satu perintah (RENCANA 5.4):
node tests/all.mjsmenjalankan (1)node --checkuntuk semua berkas JS proyek, (2)node tests/harness.mjs smoke, (3)node tests/harness.mjs tests/pairs.mjs: 49 pasangan arah dimainkan singkat oleh bot (envPAIRS_SECONDS, bawaan 24 dtk permainan), memeriksa galat halaman/konsol, angka tidak wajar (NaN, HP di luar 0..200), bahwa tiap karakter memakai skill 1, skill 2, dan ultimate di kedua sisi (sedikitnya satu dari 7 ronde per sisi; celah CPU yang sudah diketahui,KNOWN_CPU_GAPS, hanya diperingatkan), dataTIERS, dan bagian peringkat di Koleksi. Keluar dengan kode 0 bila lolos dan 1 bila ada yang gagal;ALL_SKIP=sintaks,smoke,pasanganmelewati langkah. Total sekitar 1 menit.
Aturan kerja
- Jangan memakai
Math.random/Date.nowuntuk logika yang memengaruhi hasil pertandingan bila ada pengacakrandom()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 pernahgit pushdan jangan mengubah remote. - Teks HUD di
index.html(nama, kelas, jurus) hanya placeholder netral;game.jsmenimpanya lewatfighterInfo. Cabang lama difighterInfodan manifestassets/mecha,assets/fenradalah 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).