🐟 www.DigitalFishRain.com_BETA-v9.0 🐬 ~ 🐳 BETA TESTING UNDERWAY πŸ‹ ~

Technical & Design Whitepaper
Version 1.0  ·  Project Research LLC  ·  August 2026
Abstract

Digital Fish Rain is a browser-based feeding arcade game built as a single self-contained HTML file with no external dependencies, no user accounts, and no server-side data collection. Animals fall through a tank as fixed 2Γ—3 blocks; the player steers each one into the food bucket it actually eats before it reaches the bottom. This document describes the game's design principles, technical architecture, animal taxonomy and diet system, spawn fairness, scoring model, security posture, and responsive layout strategy.

Contents
  1. Design Philosophy
  2. Technical Architecture
  3. Fish Taxonomy
  4. Randomization & Fairness
  5. Scoring Model
  6. Security & Privacy
  7. Responsive Design
  8. Touch Input
  9. Audio Design
  10. Project Information

1. Design Philosophy

Digital Fish Rain was built around three constraints, each deliberate:

Zero install, zero friction

The game loads as a single HTML page. There is no app to install, no account to create, no download to approve. Any device with a modern browser can play it immediately. This shapes every technical decision in the project β€” if it requires a build step, a server, or an external dependency, it doesn't belong.

Privacy by default

No analytics, no advertising network, no social login. The leaderboard exists only in the player's own browser storage. The server never sees a score, never receives a name, and cannot correlate one session with another. This is not a compliance posture β€” it is the design. A game about catching fish has no business casting a data net.

A feeding game, not a stacking game

Digital Fish Rain started as a falling-block game and was rebuilt around a different core loop: matching, not stacking. Every piece is a fixed 2Γ—3 animal β€” there is no rotation and nothing to clear. The player's only decision is which of seven food buckets to steer into before the piece lands. Twelve marine species, each with its own real (or real-ish) diet, are rendered as either photo-illustrated sprites or bespoke Canvas 2D artwork, layered over a procedural water-sound ambient track. The result plays like a simple sorting game and looks like an aquarium.

2. Technical Architecture

Single-file HTML

The entire game β€” roughly 2,200 lines β€” lives in one index.html. CSS, JavaScript, and HTML structure are co-located. There is no bundler, no transpiler, no CDN link, and no node_modules directory. This choice is not a shortcut; it is a deliberate constraint that keeps the game auditable, portable, and offline-capable after first load.

Canvas 2D rendering

All game graphics β€” the tank, the falling piece, the On Deck preview, and the Field Guide β€” are drawn with the HTML5 Canvas 2D API. Where a photo-illustrated sprite exists for a species, drawPieceFish() draws that image, contain-fit and centered in the piece's 2Γ—3 footprint, mirrored horizontally for the piece's facing. Where no sprite exists yet β€” or while one is still loading β€” a hand-drawn vector renderer takes over: a per-species profile of body color, fin shape, tail style, and markings, bent along a procedural spine. Nothing is a plain colored rectangle.

Game loop

The game loop runs on requestAnimationFrame, tracking elapsed time via performance.now() to compute frame delta, so drop timing is independent of frame rate. At each tick, the engine checks whether the gravity timer has expired and, if so, advances the active piece one row β€” unless it has reached the floor row above the buckets, in which case land() resolves the piece immediately: it reads which of the seven bucket lanes the piece's center column falls into, checks that bucket's id against the piece's diet list, awards points and a streak bonus (or spends a life), and spawns the next piece.

Storage

The leaderboard is persisted with a Store wrapper around localStorage that catches quota and security exceptions silently, and validates that whatever comes back out is actually an array before use β€” a tampered or corrupted value falls back to the seed list rather than crashing the page. Up to 20 entries are retained; the display shows the top 8. No other data is written to storage. The game works fully β€” minus leaderboard persistence β€” in private browsing mode where localStorage is blocked.

3. Animal Taxonomy & Diet

Twelve species inhabit the tank. Every piece is the same fixed 2Γ—3 block β€” there is no rotation and no piece-shape variety. What varies is diet: each species is coded to a list of valid bucket ids, and landing in any bucket on that list is a correct catch. Several species eat more than one thing, which gives the player more than one safe landing spot.

Type Species Diet (valid buckets) # Buckets
W Humpback Whale Krill 1
D Bottlenose Dolphin Squid 1
T Stingray Shrimp 1
J Bluefin Tuna Mackerel 1
O Puffer Fish Shrimp 1
S Striped Bass Squid, Shrimp 2
I Barracuda Mackerel, Small Fish 2
G Goliath Grouper Shrimp, Small Fish 2
Z Snapper Shrimp, Small Fish, Squid 3
P Ocean Sunfish Squid, Zooplankton, Small Fish 3
Q Giant Squid Shrimp, Krill, Squid (+ Crabs β€” no bucket yet) 3
K Orca Small Fish, Mackerel, Squid, Seal 4

All twelve species now draw from photo-illustrated sprites in animals/sprites/ β€” background-removed, cropped, and downscaled from source renders. Each one also keeps its original hand-drawn Canvas 2D renderer as a fallback, used only while its sprite is still loading or if it fails to load, so the tank is never missing a piece.

4. Randomization & Fairness

Twelve-bag shuffle

Every active species is drawn from a single shuffled bag containing all twelve types (ACTIVE_TYPES). When the bag empties, refillBag() rebuilds and reshuffles it with a Fisher–Yates shuffle. This guarantees that within any window of 24 spawns, no species appears more than twice and no species is absent entirely β€” a property purely random (Math.random() per spawn, no bag) selection does not have.

There is no rarity tier and no intercept system. Every species β€” including Orca and Humpback Whale β€” has an equal 1-in-12 chance per bag cycle. Nothing is designed to feel rare; the challenge is entirely in matching, not in waiting for a special piece to appear.

Why a flat bag instead of weighted rarity?

An earlier version of the game used a rarity-weighted intercept system (some species spawned far less often than others, independent of the shuffle bag) β€” a holdover from the game's falling-block predecessor, where rare oversized pieces were a deliberate spike in difficulty. That doesn't map onto a matching game: every piece here is the same size and takes the same amount of skill to place correctly, so there's no design reason for one species to be scarcer than another. A flat, equal-probability bag keeps the feeding pace predictable and fair.

5. Scoring Model

Every piece resolves in exactly one of two ways when it lands: a correct catch or a miss. There are no line clears and no per-cell drop bonuses β€” soft drop and hard drop only control speed, not score.

Correct catch

base+100
streak bonus+20 Γ— min(streak, 5)
cap+200 at streak β‰₯ 5

Landing in any bucket on the piece's diet list scores. The streak counter increments on every correct catch and resets to zero on any miss, so payouts range from 120 (first catch of a streak) up to 200 (five or more in a row). A combo banner names each tier β€” Nice Catch!, Good Eats!, Tasty!, Well Fed!, and Feeding Frenzy! at the five-plus tier.

Wrong bucket or a miss

Landing in a bucket not on the piece's diet list β€” or letting a piece reach the floor row without a match β€” breaks the streak back to zero and costs one life. The player starts each run with 9 lives; the run ends at 0.

Speed progression

The drop interval starts at 900 ms and tightens every 8 correct catches: dropInterval = max(280, 900 βˆ’ 60 Γ— floor(catches / 8)). Speed is driven entirely by successful catches, not by elapsed time or lives lost, so a cautious player who takes their time between drops does not face a faster game than an aggressive one β€” only accuracy moves the needle.

6. Security & Privacy

Attack surface

Digital Fish Rain has no backend server, no API, no user authentication, and no payment system. The only external interaction is the browser loading a static HTML file over HTTPS. This eliminates entire categories of server-side vulnerabilities: SQL injection, broken authentication, insecure deserialization, and server-side request forgery are not applicable by construction.

OWASP Top 10 (2021) review summary

Reviewed August 18, 2026 against the current codebase β€” every <script> block across all seven pages, every DOM-write call, every outbound link, and the CSP delivered by each page.

Content Security Policy

All seven pages carry an identical CSP restricting the page to same-origin content, with no external connections (connect-src 'none'), no plugin objects (object-src 'none'), no external form targets (form-action 'none'), and a locked base URI (base-uri 'self'). Because the game uses inline <script> and <style> blocks, 'unsafe-inline' is necessary; a future refactor to an external script file would allow replacing this with a hash-based CSP for stronger XSS protection. The .htaccess header version of the policy additionally sets frame-ancestors 'self', which β€” unlike every other CSP directive here β€” only takes effect when delivered as a real HTTP header, never via <meta>.

Privacy

No analytics, no tracking pixels, no advertising network. The game collects nothing from the player. The leaderboard stores up to 20 entries (3-character initials + integer score) in the player's own browser. This data never leaves the device. The server receives standard HTTP access logs from the hosting provider β€” IP address, timestamp, resource path β€” which are outside the game's control and are described in the Privacy Policy.

7. Responsive Design

The game layout is built on CSS Grid with two breakpoints:

BreakpointLayout
> 880px Three-column desktop: Catch Log on the left, game tank center, On Deck + Field Guide on the right.
540px–880px Single-column tablet: panels stack vertically below the tank.
< 540px Two-column phone: the game tank spans the full height of three grid rows on the left; Catch Log and On Deck appear stacked in a 100px right column. A three-row explicit grid ensures each sidebar panel sizes its own row height independently rather than inheriting the tank's full height.

The tank canvas is a fixed 28Γ—18 cell grid at 22px per cell (616Γ—396 raster), and the whole element scales down via CSS (max-width: 100%; height: auto) to fit whatever width its grid column has at the current breakpoint β€” the pixel geometry inside the canvas doesn't change, only its on-screen size does. The On Deck preview canvas, by contrast, computes its cell size adaptively per render β€” Math.min(28, Math.floor(previewWidth / 3), Math.floor(previewHeight / 2)) β€” since every piece is a uniform 2Γ—3 block, that formula now always resolves to the same 28px cell, filling the 150Γ—120 preview frame consistently regardless of species.

8. Touch Input

On touch-enabled devices the game canvas listens for touchstart, touchmove, and touchend events registered with passive: false so that e.preventDefault() can suppress the browser's default scroll behavior during gameplay.

Gesture mapping

GestureActionImplementation
Swipe left / right Move piece Horizontal displacement > 28px, larger than vertical β†’ move(Β±1)
Drag down Soft drop (continuous) touchmove fires softDrop() every 28px of downward travel; a swipeLocked flag prevents the same gesture from also triggering a horizontal move at touchend
Double-tap (bottom 45%) Hard drop Two taps within 340 ms, both in the lower 45% of canvas height β†’ hardDrop()

A plain tap that doesn't meet the double-tap window is a no-op. Earlier versions used a single tap to rotate the active piece; rotation was removed once every piece became a fixed 2Γ—3 block with nothing to rotate into.

Mobile Reef Radio

On screens narrower than 540px, the desktop Reef Radio panel (inside the right sidebar) is hidden via CSS. A compact chip-based Reef Radio control (.mobile-radio) is displayed below the game canvas instead. It exposes the same four track options (Bubbles, Adrift, Fish Rain, Nightfall) as pill-shaped chips and a volume slider. Selecting a chip updates currentTrack, syncs the desktop panel's active state, and calls startMusic() if a game is in progress. The volume slider is bidirectionally synced with the desktop slider so that changing either one updates the other. Default volume on page load is 0 (muted) to respect mobile autoplay restrictions.

9. Audio Design

Digital Fish Rain has two independent audio systems. The default "Bubble Stream" track and all in-game event sounds are synthesized at runtime with the Web Audio API β€” nothing to load, no format to worry about. The other seven Reef Radio tracks (Adrift, Constellation Bay, Overcast, Fish Rain, Nightfall, Double Rainbow, Extension) are real recordings played back through local <audio> elements pointed at same-origin files in music/ β€” no CDN, no external host, same-origin only under the page's CSP.

Ambient bubble stream

A continuous underwater atmosphere is generated by scheduling randomized bubble events using the browser's audio context clock (actx.currentTime). Each bubble is a short sine oscillator with a gentle amplitude envelope and a pitch shift that mimics rising bubbles.

Game event sounds

A correct catch triggers a short cascade of rising bubble tones, scaled by the current streak (more bubbles at a longer streak). A wrong bucket or a miss triggers a single low square-wave blip. Both are built from the same oscillator primitives as the ambient stream, so event sounds never clash tonally with the background atmosphere.

Web Audio initialization

The AudioContext is not created until the player's first interaction (a click or keypress), per browser autoplay policy. A call to ensureAudio() at the start of every sound function handles lazy initialization and suspended-context resumption transparently.

10. Project Information

Digital Fish Rain is developed and operated by Project Research LLC.

The game is free to play at www.digitalfishrain.com.

Legal documents: Privacy Policy · Terms of Use

Contact: admin@projectresearchllc.com · Contacts page

AI disclosure. This website and our software are developed, tested and partly written with the help of AI tools, including Anthropic’s Claude. All AI-assisted output is reviewed by our team before publication. Read the full AI disclosure in our Privacy Policy.