NEON SURVIVORS v1.1 — full-featured bullet-heaven survivor game
Vanilla JS + Canvas, zero dependencies, offline-first PWA. Gameplay: - 11 weapons x8 levels + 11 evolutions (chest-based), incl. timed mines - 13 passives, crit system with directional hit-sparks & hit-stop - 10 characters w/ unique mods + unlock conditions, gold cosmetic skins - 3 biomes (Neon Graveyard / Frozen Hollow / Magma Rift) each with own spawn tables, boss plans and music flavor; Endless mode + surges; 4 difficulty grades; breakable crystal-lamp props - Elite random affixes (Swift/Sturdy/Volatile), 4 bosses, win flow - Achievements (23) w/ gold rewards, run history, daily seeded challenge Tech: - Cinematic canvas main-menu scene, game-feel FX suite (trails, muzzle, status tints, low-HP pulse), viewport culling + particle pooling - WebAudio synth SFX + generative per-biome soundtrack - Gamepad support, remappable keys, touch joystick, fullscreen - i18n VI/EN, localStorage saves w/ export-import codes - Cloudflare Workers leaderboard scaffold (KV) w/ signed submits - Headless integrity test-suite (node test/integrity.js)
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// dev tool: scan for calls to members/functions that are never defined
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const files = fs.readdirSync('js').filter(f => f.endsWith('.js'));
|
||||
let all = '';
|
||||
for (const f of files) all += fs.readFileSync('js/' + f, 'utf8') + '\n';
|
||||
|
||||
const defs = new Set();
|
||||
for (const m of all.matchAll(/(?:^|\n)[ \t]*(?:async\s+)?([A-Za-z_$][\w$]*)\s*\([^()]*\)\s*\{/g)) defs.add(m[1]);
|
||||
for (const m of all.matchAll(/\bfunction\s+([A-Za-z_$][\w$]*)/g)) defs.add(m[1]);
|
||||
for (const m of all.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/g)) defs.add(m[1]);
|
||||
|
||||
const allow = new Set([
|
||||
'push','pop','shift','unshift','splice','slice','concat','join','map','filter','forEach',
|
||||
'reduce','find','findIndex','some','every','includes','indexOf','sort','fill','keys',
|
||||
'values','entries','from','isArray','assign','create','freeze','getOwnPropertyNames',
|
||||
'setItem','getItem','removeItem','clear','log','warn','error','now','floor','ceil','round',
|
||||
'abs','min','max','sqrt','pow','sin','cos','tan','atan','atan2','random','hypot','sign','exp',
|
||||
'add','has','delete','get','set',
|
||||
'translate','rotate','scale','save','restore','beginPath','closePath','moveTo','lineTo','arc',
|
||||
'arcTo','quadraticCurveTo','bezierCurveTo','rect','fill','stroke','clip','fillText','strokeText',
|
||||
'measureText','drawImage','createLinearGradient','createRadialGradient','createPattern','fillRect',
|
||||
'strokeRect','clearRect','setTransform','resetTransform','ellipse',
|
||||
'play','pause','resume','connect','start','stop','setValueAtTime','linearRampToValueAtTime',
|
||||
'exponentialRampToValueAtTime','createGain','createOscillator','createBuffer','createBufferSource',
|
||||
'createBiquadFilter',
|
||||
'addEventListener','removeEventListener','preventDefault','stopPropagation','appendChild',
|
||||
'removeChild','remove','querySelector','querySelectorAll','getElementById','createElement',
|
||||
'toggle','contains','setAttribute','getAttribute','insertBefore','closest','matches',
|
||||
'test','exec','replace','split','trim','toLowerCase','toUpperCase','charAt','charCodeAt',
|
||||
'padStart','padEnd','repeat','match','matchAll','toString','toFixed','stringify','parse',
|
||||
'requestAnimationFrame','setTimeout','setInterval','clearTimeout','clearInterval','fetch',
|
||||
'then','catch','finally','bind','call','apply'
|
||||
]);
|
||||
|
||||
let bad = 0;
|
||||
for (const m of all.matchAll(/\b(UI|Sys|R|Snd|Store|GAME)\.([A-Za-z_$][\w$]*)\s*\(/g)) {
|
||||
const meth = m[2];
|
||||
if (!allow.has(meth) && !defs.has(meth)) { console.log('MISSING:', m[1] + '.' + meth); bad++; }
|
||||
}
|
||||
console.log(bad === 0 ? 'No missing member calls OK' : bad + ' issues');
|
||||
|
||||
for (const f of ['render.js', 'systems.js', 'ui.js', 'audio.js', 'save.js']) {
|
||||
const src = fs.readFileSync('js/' + f, 'utf8');
|
||||
for (const m of src.matchAll(/this\.(_?[A-Za-z_$][\w$]*)\s*\(/g)) {
|
||||
if (!defs.has(m[1])) console.log('MISSING this.' + m[1] + ' in ' + f);
|
||||
}
|
||||
}
|
||||
console.log('this-method scan done');
|
||||
Reference in New Issue
Block a user