Beat the AI's time
A browser game where you race an AI agent's recorded run through a reconstruction of Pine Gap, on the same seed, with the same controls.
Guard the sea route in a sonar defence game
Translation pending
AI summary · website content
GamesJavaScript · CSSGitHub @prradyumnOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Open index.html in Chrome: double-click it, or press Go Live in VS Code (Live Server serves this folder, so the game opens straight away).
The game is played from inside the submarine cockpit (see The cockpit below).
Flow: the hook (one short scene: enemy submarines slip in, they fire, ALERT) → Level 1 launch as many opposite missiles (intro + tutorial + 6 practice rounds) → Level 2 fire at the submarine with the smaller number (closer to our border) → Level 3 fire from the smaller number up → the ending (ROUTE CLEARED!).
Level 2 number line: only the first example (the tutorial, after the intro) shows the number line, with each submarine above its number. From round 2 on the line is put away: the two submarines float at random places high in the water with their numbers on them, and the child compares the numbers themselves (hints pulse the numbers on the submarines). After a wrong tap the number line comes in to explain: the two numbers glow, a light runs from the bigger to the smaller, with "◀ smaller" and "bigger ▶" tags at its ends; then the line goes again and the child fires at the submarine (explain() in js/levels.js). The guided replay brings the line in the same way for its number step and puts it away for the shot.
Level 2 guided replay: the first wrong tap shows Incorrect Feedback 1 (with the number line's "◀ smaller" / "bigger ▶" tags). The second wrong tap (rounds 2–10) starts a guided replay in two steps the child does, with wrong options blocked: A · both submarines grey out and only the two numbers on the rope can be tapped ("Check which is smaller: −10 or −12?"; a wrong number → a light runs towards the smaller one); B · "The smaller number is −12…", then only the submarine above −12 comes back, its sonar line lit, and the child fires. A guided round is followed by one more item of the same kind with no hints ("One more like this!"; +14/+12, −11/−13 or −7/+2, once per kind per level, not scored; D.L2_EXTRA in js/script.js).
Dialogue: only 7 lines are spoken, all Meera (the dialogue list for the game, SCRIPT.S in js/script.js): the hook (2), Level 1's intro (2), Level 2's intro (2, over the number line while light sweeps left, "smaller · closer", then right, "bigger · farther") and Level 3's intro (1). Everything else from the L1–L3 sheets — each round's instruction (Level 2: "Fire at the submarine with the smaller number."), the comparison after a hit (−12 < −10), the correct lines (Level 1's open with "Well done!" / "Good job!"; Level 2's are all "Correct! −12 is smaller than −10. Good job."), the wrong-answer hints and the inactivity lines — shows in the mission panel and the narrator reads it aloud (102 lines, the same Indian-English voice, n_<hash>.ogg; SD.narrate: voice only, no speech box; a tap cuts it; the panel's hear-again knob repeats it). A hint shows until it has been said, then the round's text comes back. Not read: Level 3's list of numbers and the big banners.
Level 1: the missiles that come sit in a row in the window (the enemy submarine's positive ones are red, the defence system's negative ones blue). The bow cannon becomes the launcher of the other sign (a − or + badge): each tap launches one missile into the launch bay above it, where they collect in rows of 5 (tap one in the bay to take it back). FIRE: each missile leaves the bay, streaks to one missile that came and both are absorbed into a bright core (a small 0 is left for a moment). If the numbers don't match it still fires: the pairs are absorbed; too few, and the missiles left over get through and hit the window (the cockpit shakes, the glass cracks); too many, and ours shoot past and fizzle out. The enemy launches again and the sheet's Incorrect Feedback 1 → 2 → 3 plays; from the third mistake on, the launcher stops at the right number, and from the fourth on FIRE stays off until the bay holds exactly that many (a tap on it clunks and points at the count), so a child can't loop through wrong volleys. There is no shield and no sinking (dropped after review): the feedback is to count the missiles. The "check how many" hints count the missiles that came: each glows in turn with 1, 2, 3 … over it, and the numbers fade a few seconds after the last (W.mslCount). The cracks mend at the end of the mission. Inactivity repeats the round's instruction.
Loading: the title's art comes first (high-priority preloads in index.html). The story's art comes next, and the game starts as soon as it and the fonts are in (SD.ready, about 2.4 s on a 10 Mbps link, down from 6.5 s). Everything else loads after that, in order of need, while the title or the story is on screen: Meera's talking sheet, the game art, the sounds, and the missions' music last (LATER in js/core.js). The game world behind the title waits too (body.booting). Riya's sheet (1.2 MB) loads only with the full effects, because in calm mode only the speaker is on screen and she has no lines. The heaviest images are WebP at quality 80. On Vercel, assets/ are cached for a day and served stale while they refresh for a week (vercel.json), so a returning player loads almost nothing; the voice manifests and layout.js always revalidate.
Stars: every finished level shows all three stars on its completion card. The first-try count is still recorded in window.GAME_RESULT for reporting.
Title screen: a live scene, not a picture (css/cover.css, layers in assets/img/cover/): the Coast Guard ship comes towards us bow-on, bobbing, rolling about its waterline and surging forward, its radar turning; the bow spray peels off both sides of the hull and swells as it dips, and a wave crest washes over the bow's foot; Meera says the game's name ("Sonar Defence!", line title, English and Hindi) as the logo lands and again on any tap on the cover; the title plays a soothing bow-wave loop (assets/sfx/cover_sea.ogg, CC0, from the first tap where the browser blocks sound until then) that fades out as the game starts; foam streams past both sides towards us; three wave bands scroll at different speeds (each band is two tiles wide and slides one tile, so it loops without a seam); clouds drift, gulls flap across, the sun glints on the water, and the logo drops in and floats. Only transform and opacity animate; the loops pause once the game starts, and hold still for people who ask for reduced motion. The source PNGs stay out of git and the deploy (.gitignore, .vercelignore).
Tutorial hand: in the first example of Level 1 and Level 2 a hand taps the thing to do next (the launcher until there are enough missiles, then FIRE; in Level 2 the smaller number, then its submarine), over the gold helper glow (#hand in index.html).
Level 3: empty places for the numbers sit above the cannon. Every submarine hit leaves its own number: it pops up where the submarine sank and flies into the next place, so the row fills from the smallest up; when the last one is in, "<" signs pop in between (−12 < −9 < −6 < −3) and the numbers light up left to right. (The sheet's correct animation shows firing-order numbers 1, 2, 3, 4; this replaces them.)
index.html the game
css/ game.css (layout, stage px) · cockpit.css (window frame, cannon, crew, console screen) · editor.css (layout editor only)
js/ script.js (every spoken line) · core · world · cockpit · levels · qa · editor · layout.js · gsap.min.js
assets/img/ all art, .webp (cover/ = the title scene's layers) assets/vo/ all voice lines, .ogg + vo_manifest.js assets/fonts/
tools/ make_voices.py (records the voices) · dev_server.py (lets the editor save)
The original art pack (PNG sources, concepts, briefs) is not needed by the game; it was moved out to ~/Downloads/Sonar_Defence_Archive_2026-10-09/ and is also in this repo's git history.
SD.hold in js/core.js; the layout editor's pause uses it too).body.lite); ?fx=lite forces it. Meera's talking sheet decodes on a worker, and the story's WebGL water is prepared while the title is up, at the screen's own resolution.It is a static site: no build step. Import the repo (or drag this folder into Vercel) and keep Framework Preset: Other, no build command, Root Directory ./. vercel.json pins the preset; .vercelignore leaves tools/ and the docs out of the upload. If the Vercel project was created while the game lived in Sonar_Defence_Game/, set its Root Directory back to ./. The layout editor's Save needs tools/dev_server.py, so on Vercel it offers Copy / Download instead.
Every line (Meera's 7 + the narrator's 102) is recorded with Chatterbox (Resemble AI, MIT license): natural delivery in the voice of tools/ref_meera_en_india.wav (our own MeloTTS EN_INDIA clip, so the accent stays Indian English). Each take is checked word by word with Whisper and goes into the game as soon as it passes (all 109 passed; --seed0 3 --tries 6 found clean takes for the last two); a line without a clean Chatterbox take keeps its MeloTTS recording. Done: assets/vo/takes/chatter/done.json.
~/chatter/bin/python tools/make_voices_chatter.py # the lines not made with Chatterbox yet (slow: ~2 min a line; resumable)
~/chatter/bin/python tools/make_voices_chatter.py --only h1 --force --exaggeration .7
?lang=hi)Add ?lang=hi to the link for the whole game in pure Hindi (धनात्मक / ऋणात्मक; numbers read "ऋण बारह", "धन आठ"; "−12 < −10" read "ऋण बारह, ऋण दस से छोटा है"). js/lang_hi.js holds every translation (for the learning designer to review), how Hindi is read aloud, and the screen's other words (banners, buttons, labels: swapped in on screen). js/script.js builds the lines in English first, so the Hindi voice files keep the same ids: assets/vo/hi/<id>.ogg + assets/vo/hi/vo_manifest.js. Devanagari letters use Baloo 2 Devanagari (OFL, assets/fonts/baloo-2-devanagari-*), downloaded only when Hindi is on screen. The title logo and the FIRE button are artwork and stay in English.
~/chatter/bin/python tools/make_voices_chatter.py --lang hi # Chatterbox multilingual, the same Meera voice; checked with Whisper large-v3-turbo
Whisper's Hindi spelling varies, so a Hindi take passes at 80 % similarity; lines that never pass are kept and listed (? kept take …, please listen). A Hindi speaker should listen through once.
Before Chatterbox, the recorded lines were MeloTTS EN_INDIA in the expressive style (--style) (MIT license, free for commercial use; runs on this Mac, no API key). tools/make_voices_parler.py makes them:
source ~/melo/bin/activate
python tools/make_voices_parler.py --model melo --speaker EN_INDIA --force # every line, 3 takes each; take 1 goes into the game
python tools/make_voices_parler.py --model melo --speaker EN_INDIA --pick h2=2,l2b=4 # swap in another take
assets/vo/takes/melo/<speaker>/<id>_<n>.wav (not committed, not deployed); listen with afplay. In the game now: h1 1 · h2 2 · l1a 1 · l1b 1 · l2a 2 · l2b 4 · l3a 1, each checked word by word with Whisper. EN-US takes are there too as an alternative (--keep-only makes takes without touching the game).--model large uses Parler-TTS Large (Apache 2.0, no Indian accent, CPU only: the Apple GPU gives noise). --model indic uses AI4Bharat Indic Parler-TTS (Apache 2.0, Indian voices incl. "Meera") once the terms are accepted at huggingface.co/ai4bharat/indic-parler-tts; run those two from ~/parler.tools/make_voices.py records them (Python 3 standard library + node to read the script):
export GEMINI_API_KEY=… # from https://aistudio.google.com/apikey — never put it in a file
python3 tools/make_voices.py # makes every line that has no wav yet
assets/vo/<id>.ogg (Ogg Opus, 48 kbps mono, through ffmpeg; .wav if ffmpeg is missing or with --wav) and rewrites assets/vo/vo_manifest.js (the list the game plays) after every file. Reload the game.--check transcribes the recorded lines and lists any whose words differ (TTS sometimes ad-libs); --verify re-takes a line until the words match.--only h1,h2 / --who riya pick lines · --force re-records · --dry-run shows what would be made (no key needed)--meera Kore --riya Leda change the prebuilt voices · --model auto (default) picks the newest Flash TTS model · --lang en-IN also sends a language codePROFILE at the top of the script. Audio is trimmed, faded and normalised to −1 dBFS (--no-polish keeps it raw). Newer TTS models return a whole WAV with a C2PA block: only its audio data is kept.The camera is inside the submarine, looking out through the big bow window (assets/img/cockpit.webp, the window glass is transparent; glass y 112–914).
assets/img/cannon.webp, made with Nano Banana 2) is seen from behind, aiming out to sea, and rides the console rail under the scale. Tap a submarine and it slides along to that submarine's column (it lines up with the number), then fires straight ahead: recoil, muzzle flash, and the torpedo runs away from us (it shrinks) behind the scale to the target. Home is under 0; it glides back there between missions. The cockpit jolts on launch and hit; a hit submarine sinks behind the scale.js/cockpit.js, bottom): every hit shakes the view (one whole-pixel shake of all layers, W.shake in js/world.js) and the submarine's number pops up big over its label; fish scatter from the blast; blast bubbles stick to the glass and slide down. Three first-try hits in a row turn the cannon gold and the next shot is a golden super torpedo. Each level dives deeper: the sea darkens a step, glowing plankton appear, and the console shows the depth (60 → 110 → 160 → 220 m). When a level is done, Meera's and Riya's stations glow and sparkle.assets/img/cockpit.webp, glass cut out) and the cannon are Gemini images in the game's style; the cannon's muzzle position is MUZZLE_Y in js/cockpit.js (read from #cannon in css/cockpit.css).js/world.js draws the sea, scale, submarines and effects; js/cockpit.js adds the sliding bow cannon (W.aimHook, W.fire), the crew and the cockpit effects; css/cockpit.css holds its layout. Layout constants (SUB_Y, SUB_ROW, SIG_Y1/2, SCALE_DY) are DEFAULTS in js/world.js.Press Q in the game (Q again hides it), or add ?qa=1 to the URL (e.g. https://<your-vercel-link>/?qa=1). A small QA ▾ panel (top-left) jumps straight into any part and starts playing:
story (Harbour · Control room · Dive), Teaching, every round of Level 1–3 and the Final mission. While playing: Skip line, Answer ✓ (taps the right submarine / pair), Restart part; choose 1×/2×/3× speed and sound for the next jump. After a jump the panel stays on. (URL equivalents: ?at=hook&scene=2, ?at=l3&round=4, ?at=final&round=3.)
Recorded music + ambience + effects live in assets/sfx/ (all .ogg): two loops composed for the game with Google Lyria (a mysterious deep-sea atmosphere for the story, a focused naval-mission underscore for the missions, crossfading at the dive), harbour gulls + waves, sea waves, and CC0 Freesound effects (sonar, torpedo, underwater explosion, splash, water rush, sci-fi door, bubbles) — see assets/sfx/CREDITS.md. Taps, chimes and the reward arpeggio are synthesized. Music dips under every voice line. Over http(s) everything plays through Web Audio; from file:// through <audio> elements.
In the story conversations (harbour + control room) and at their cockpit stations, Meera and Riya are the Ludo talking sheets meera_talk_sheet.webp / riya_talk_sheet.webp: 6×6 whole full-body frames, played exactly as drawn while that character's line is spoken (Meera 79 ms/frame, Riya 90 ms/frame). When the line ends the character runs on to the next closed-mouth frame and holds it; the listener stays still. Frame sizes and the closed-mouth frame lists are in SHEET in js/core.js; the source PNG + JSON are in the archive folder.
Players never see it. Open index.html?edit=1, or press E in the game.
python3 tools/dev_server.py # serves the game and lets Save write js/layout.js
open http://localhost:8000/?edit=1
CONFIG in js/world.js): scale origin/step, submarine rows, L3 signal rows, scale offsetjs/layout.js through tools/dev_server.py. From file:// the browser can't write files: use Copy JSON / Download layout.js and put it in js/. ⌘/Ctrl+Z, ⇧⌘/Ctrl+Z undo/redo; unsaved edits survive Jump/Restartjs/layout.js = window.LAYOUT = { overrides: { "<selector>": { left, top, width, height, rotate, scale, opacity, zIndex, fontSize … } }, config: { SUB_Y: … } }, applied at boot by core.js as one <style id="layoutOverrides"> (so submarines and story bubbles created later pick it up too). Story actors/bubbles are keyed by their data-edit name?at=hook | l1 | l2 | l3 start at a section, &round=2 start at a round&speed=2 faster animations, &mute=1 no sound/voice, &autostart=1 skip the title buttonjs/script.js EVERY spoken line ({id, who, text}) + level data. The game and tools/make_voices.py both read it — edit lines here (and re-record: --only <id> --force).js/core.js stage fit, sound (Web-Audio sfx + tanpura/underwater music bed, ducked under voice), voice playback (recorded VO → browser voice), captions + name tag, the talking sheetsjs/world.js parallax sea (far reef, seabed, fish schools, rays, caustics, marine snow, bubbles), navigation scale (x = 960 + 100·value, −8…+8), submarines (tap rings, propeller bubbles), lock-on/blast (flash, shockwave, debris), hints, the helper glow and tapping hand (tutorial + nudges), Level 1's missiles + launch bay, Level 3's order rowjs/cockpit.js the bow cannon + torpedo, the crew at their stations, console screen, warning lamps, hull kickjs/qa.js the QA jumper (?qa=1)js/editor.js + css/editor.css the pause + layout editor (inert unless ?edit=1 / E) · js/layout.js saved layout overrides · tools/dev_server.py local server that saves themjs/levels.js flow + pedagogy: the hook (lines h1, h2), the level intros (l1a l1b, l2a l2b, l3a), the ending (ROUTE CLEARED!), tutorial glow in each level's first round, 3-strike feedback as panel text, idle help (6 s visual → 12 s text → 22 s reminder), starstools/make_voices.py makes the voice filesassets/img web versions of the art pack (all .webp)#rule)sig.webp mirrored + recoloured in CSS (.sig.neg)world.jsThe mission panel (#hudbar) is part of the ship: a slim navy steel plate with a brass rim and rivets, hanging from two brass ceiling mounts on thin struts. It comes down when a mission starts (servo whirr, latch clunk, the screen fades on), slides a new line in when the text changes, and goes back up into the ceiling between missions (W.strip / W.stripOff, cockpit.js). The hear-again button is a brass knob on the left; progress lamps sit on the right. The panel is 1400 px wide with 36 px text; a long line wraps onto two lines at 32 px (it never shrinks below that).
During play, what Meera or Riya says appears in a speech bubble beside her (Meera bottom-left, Riya bottom-right) with its tail pointing at the speaker.
One bar on the ceiling holds the hear-again button, the mission text and the progress pips (#hudbar). The ceiling lamps are toned down (#lampDim), the crew are 15 % smaller and dim until they speak, the console shows the sonar trace + depth only between missions, fish swim below the scale (never behind the submarine row), and number labels not in play are faded to 50 %.
js/fx.js)surface_light.webp, the fade is baked into the image), and a soft depth haze sits at the window edges (pre-softened gradients, transform/opacity only).The game starts in Calm mode to keep the screen simple. The QA panel (Q) has a Calm / Full switch (saved per browser; ?fx=full / ?fx=calm also work).
Calm keeps the hit's hero beats (blast + the sub breaking in two, the number popping up, the sonar ping flying to progress) and drops: confetti, the floating praise word (Riya still says it), glass bubbles, frame glow, shock ring, debris, distance beam, current streaks, plankton, propeller and muzzle bubbles, the waiting rings on every sub (the 6 s help still pulses them) and the cannon's ring. Fish and rising bubbles are halved, the console screen box shows only between missions, and only the crew member who is speaking is on screen.
buoy.webp, buoy_zero.webp, Nano Banana 2), each anchored by a short chain, the number under it. Numbers are 106 px apart (STEP). Buoys swing gently on their rings (rotation only, so their spacing never changes). A buoy with a submarine above it glows and grows; its number lights up in a chip.bubble_wide_tail*.webp), in the story the oval's tail (bubble_oval_tail*.webp). Dialogue text is Poppins SemiBold 32 (assets/fonts/poppins-latin-600-normal.woff2).The whole story is one continuous underwater scene: the camera never cuts.
js/water.js): one WebGL canvas draws the backdrop with slow refraction ripples, moving caustic light, god rays and a soft Gaussian depth blur toward the bottom and edges. When the game is opened straight from a file (file://), browsers don't let WebGL read the image, so the plain background is used instead; Live Server and Vercel get the full effect.Our border: our territory is the negative side of the scale, so the smaller (more negative) number is closer to our border. Level 2 always fires the smaller number and Level 3 fires from the smallest up, as both teaching lines say. Where the L2 sheet picked the greater number (rows 3–8 and 10), the answer and its lines were changed to the smaller one, worded like rows 1–2; the sheet's "border 0" reads "our border". Level 2's intro sweeps the number line left (smaller, closer to our border) then right (bigger, farther).
python3 tools/make_voices.py with a working key).Every file the game loads is WebP (images) or OGG (sound); fonts are woff2. The supplied story source art in assets/submarine_story_assets/ is kept as full-resolution WebP (19 MB of PNG → 2.5 MB); the original PNGs are in ~/Downloads/Sonar_Defence_Archive_2026-10-09/story_source_png/. Source art is excluded from the Vercel upload (.vercelignore).