Builder's Manual · Night Arcade Cabinet
Assets and Sprites
Source-backed guides to the speed curve, ReactLynx architecture, sprites, native hosts, documentation site, and release pipeline.
Assets and Sprites
Game sprites are pre-sliced individual PNG files. The ReactLynx renderer uses a separate <image> element for each visual rather than slicing a sprite sheet at runtime.
Directory Structure
assets/
├── sprites/
│ ├── bird-0.png # Wings down
│ ├── bird-1.png # Wings level
│ ├── bird-2.png # Wings up
│ ├── background.png # Sky + city background tile (276x228)
│ ├── ground.png # Ground tile (224x112 source, drawn at 224x129)
│ ├── get-ready.png # "Get Ready" overlay (174x160)
│ ├── game-over.png # Game over panel with score boxes (226x158)
│ ├── pipes/
│ │ ├── pipe-top.png # Top pipe body tile
│ │ ├── pipe-top-mouth.png # Top pipe mouth (lip)
│ │ ├── pipe-bottom.png # Bottom pipe body tile
│ │ └── pipe-bottom-mouth.png # Bottom pipe mouth (lip)
│ ├── digits/
│ │ └── digit-0.png … digit-9.png # Score display (18x27 each)
│ └── medals/
│ ├── medal-bronze.png
│ ├── medal-silver.png
│ ├── medal-gold.png
│ └── medal-platinum.png
│
└── audio/
├── sfx_wing.wav # Flap
├── sfx_point.wav # Score
├── sfx_hit.wav # Pipe collision
├── sfx_die.wav # Ground collision
└── sfx_swooshing.wav # Game reset
How Assets Are Loaded
In the Lynx app, assets are loaded via top-level import statements at the module level:
import bird0 from '../../assets/sprites/bird-0.png';
import bird1 from '../../assets/sprites/bird-1.png';
import bird2 from '../../assets/sprites/bird-2.png';
const BIRD_SPRITES = [bird0, bird1, bird2, bird1]; // ping-pong cycle
lynx.config.ts sets an image inline limit of 64 KiB, large enough for every current game sprite. Rspeedy embeds those images as data URLs so native hosts can load the single bundle offline. Audio imports are emitted as separate WAV assets; native audio remains a placeholder rather than a packaged playback implementation.
Pipe Rendering
Pipes use a tile-based approach instead of stretching a single image. Each pipe is composed of:
- Body tiles — repeated vertically to fill the pipe height (extends well past the screen edge)
- Mouth tile — placed at the opening where the bird flies through
This avoids visual stretching artifacts and matches the original game’s pixel-art style. Each tile is 55px wide and ~53px tall.
Background and Ground Tiling
Both the background and ground use 5 copies laid out horizontally in a flex row. The container is translated left via CSS transform. Background offset wraps at one tile width (276px); ground offset wraps at half its tile width (112px), matching the repeated ground pattern.
- Background: 5 tiles at 276px each = 1380px total, scrolls at
0.2 * speedMultiplierpx/frame - Ground: 5 tiles at 224px each = 1120px total, scrolls at
2.7 * speedMultiplierpx/frame
Score Display
The in-game score uses sprite-based digit rendering (ScoreDisplay.tsx). Each digit is drawn in an 18x27px <image> element with 2px gaps, centered on screen. These are display dimensions, not the source PNG dimensions (for example, digit-0.png is 12x18px).
The game-over panel score uses <text> elements positioned absolutely over the panel sprite.
Credits
- Sprites from The Spriters Resource
- Sound effects from The Sounds Resource
- Original Flappy Bird by Dong Nguyen
- Earlier Canvas recreation by noanonoa, preserved in this repository’s history
Website copies and social assets
The Canvas demo serves copies from docs/public/assets/. Keep each sprite and sound byte-identical to its canonical counterpart in root assets/ when updating assets. The docs build copies public files unchanged; it does not synchronize those two trees automatically.
Social previews and icon fallbacks are generated by docs/scripts/generate_social_assets.py. It uses the Pillow version pinned in docs/scripts/requirements.txt, accepts display/monospace font paths, and generates a 1200x630 og-image.png. See docs/README.md for commands and platform-specific font examples.