Snarky's
LOADING
Arrows / WASDMoveMouseAim / move cursor to turnMouse click / SpaceShoot
shooter#twin-stick#shooter#webgl#top-down#arcade#js13k

Reboot every CPU before the spiders reboot you.

A fast top-down twin-stick shooter through a glitching WebGL facility. Fight through swarms of spiders and turret sentries to reboot every offline CPU and jump to the next sector before your last backup runs out. Originally phoboslab's 2018 js13k entry, built in ~13KB of JavaScript.

0 plays0 players0 remixesPublished 8d ago
Open source

Made by Dominic Szablewski (phoboslab.org), Andreas Lösch (no-fate.net) — music, Sonant-X audio engine (zlib license, bundled & modified): Nicolas Vanhoren, Marcus Geelnard, Jake Taylor. Released under the MIT license — original source.

Changes for this platform: Shipped the unminified debug sources (source/*.js, as upstream's own index-debug.html already loads them) instead of running build.sh's Closure Compiler/uglify-es pipeline - the whole game is ~13KB so minifying it was not worth chasing a working old toolchain for. Switched source/game.js's keyboard handling from legacy numeric event.keyCode to event.code (key_convert is now keyed by code strings: KeyA/KeyW/KeyD/KeyS plus the arrow keys and Space), both because that's what the platform's touch shim's synthetic KeyboardEvents set and because it also gave keyboard-only desktop players a real fire key for the first time (upstream only ever wired key_shoot to mousedown/up) - added a `512: 0` entry to the initial `keys` map so that binding is actually reachable. Added touch support: meta.json's joystick scheme already drives movement via the platform's synthesized arrow-key events; a FIRE button (code Space, same key_shoot binding above) was added, and since there's no second analog stick to aim with on a touchscreen, arcade-bridge.js tracks the nearest living enemy every frame and, only while FIRE is held on a coarse/touch pointer, hands its world position to a tiny patch in source/entity-player.js's _update() that aims at it with the same atan2(dz, dx) math entity-sentry.js's own AI already uses to aim at the player - desktop mouse-aim is completely untouched and only used when that target is unset. Removed the personal dedication in source/terminal.js's outro text (a thank-you to a named friend, the js13k competition staff and that year's other entrants, signed off by the original author's first name) and replaced it with a plain two-line authorship credit (Dominic Szablewski / Andreas Lösch, matching the names already on the title screen) - genuine author credit was kept, the private message was not. Rewrote the page CSS (index.html) to satisfy the platform's viewport contract: html/body now fill the viewport with no scrollbars, and the canvas letterboxes via max-width/max-height:100% with width/height:auto instead of a bare `width:100%` tuned for desktop windows only, so it stays correctly proportioned (and fully on-screen) at 390x844. Added an animated placeholder canvas (#ur-title-canvas, drawn in arcade-bridge.js) stacked above the real WebGL canvas, since upstream's canvas is solid black until the player's first click (the renderer and all draw calls only start after that) and the platform expects a canvas that's already drawing something on the title screen; it's removed the instant the real game loop runs its first frame, so gameplay is always judged on underrun's own canvas. Added arcade-bridge.js: calls ArcadeSDK.ready() once the placeholder is drawing, derives a score from enemy kills (wraps entity_spider_t/entity_sentry_t's own _kill() rather than editing those files: +10 per spider, +25 per sentry) and calls score()/gameOver()/submitScore() on every player death (won:false) and on clearing the final level's last CPU (won:true, by wrapping the function next_level()), and wires onPause/onResume to stop the main loop and AudioContext.suspend()/resume() the procedurally-generated audio (also on tab-hide). Explicitly resumes the AudioContext on the first real pointer/keyboard gesture rather than relying only on the browser's own default autoplay block, since upstream starts looping its generated background track as soon as generation finishes with no gesture at all - modern browsers already keep that silent until resumed, but this makes the no-audio-before-input rule an explicit guarantee instead of an implicit side effect of the autoplay policy. No network calls, localStorage/cookie/indexedDB use, alert/confirm/prompt, window.open or navigation existed anywhere in the upstream source (it's a self-contained js13k entry by design), so nothing needed to be removed or wrapped there. Fixed three bugs found while verifying this actually runs in a current, GPU-backed Chromium (not just passing the smoke checker): (1) source/game.js's load_image() now sets img.crossOrigin='anonymous' before setting .src - the sandboxed frame's opaque origin makes every fetch count as cross-origin for canvas/WebGL purposes, so without it texImage2D() in renderer.js and the getImageData() level-decoder in load_level() both threw SecurityErrors (this platform's own game-file routes already send access-control-allow-origin:*, so the CORS-mode request succeeds cleanly once requested). (2) source/renderer.js's WebGL context is now created with {preserveDrawingBuffer:true} - without it, reading the canvas back from outside the render loop (what the seed-game checker's headless pixel-change test does) could see a blank/cleared buffer. (3) source/main.js now attaches the click-to-start handler immediately instead of inside audio_init()'s completion callback - upstream gated it behind Sonant-X finishing generating the entire soundtrack and every sound effect first (a self-throttled, CPU-bound process), silently dropping an early click since no handler existed yet; audio_play() on a sound-effect buffer that hasn't finished generating is a safe no-op, so decoupling the two loses nothing.

Comments 0

Log in to comment.

  • Loading…