Skip to content
phon-tPublic

About

Beautiful ASCII animations

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

ASCII World

A small, living ASCII universe built with TypeScript, React, Vite, and Canvas 2D. Compose a scene from 20 animated creatures, tune its atmosphere, and keep your favorite worlds on your device.

Use it on your browser tabs: Chrome, Brave, Edge, Firefox, Opera, and Vivaldi setup guide.

The initial library includes sharks, hammerhead sharks, koi, fish, jellyfish, sea turtles, whales, octopuses, birds, butterflies, bats, cats, ghosts, aliens, robots, dragons, spaceships, satellites, shooting stars, and comets. Five environments provide open ocean, deep sea, outer space, a minimal dark void, and a black hole.

Run locally

The appearance combines black and charcoal environments with pale creature colors and shaded ASCII sculptures. A single ink color remains available in Advanced appearance. Marine swimmers use original 3D point-cloud bodies projected into ASCII, with continuous tail flex, turning, and depth shading. Other creatures use shaded text frames generated from original procedural meshes. Both paths render through Canvas 2D without WebGL.

Use Node.js 22.12 or newer and npm.

npm install
npm run dev

Open the local address Vite prints, normally http://127.0.0.1:5173. No account, API key, environment file, or backend is required.

npm run build       # TypeScript checking and production build into dist/
npm run preview     # Serve the production build locally

The output is a static site. Vite uses a relative asset base so the build can also be served beneath a repository subpath. Serve dist/ through an HTTP server rather than opening its HTML directly.

Use your world

  • Search by name or tag, filter categories, and add creatures from the library. Press / outside a text field to focus search.
  • Adding a type creates three instances, or the remaining available slots. Adjust each type's quantity in In your world; a quantity of zero keeps the type selected. Remove a type to remove its selection and instances.
  • The overall population slider distributes the requested total proportionally across selected types, with exact integer rounding. If every selected type has quantity zero, it distributes the new total evenly.
  • Change environments, speed, size, ink, individual colors, particles, scanlines, movement settings, and wrap or bounce boundaries without discarding the rest of the scene.
  • Pause freezes the world; Shuffle rearranges it using a new seed while keeping its creatures and settings. Reset restores the original ocean and preserves named worlds.
  • Click or tap open water to drop food. Ocean swimmers turn toward it and eat it; the Drop food button offers the same action from the keyboard. Paused and reduced-motion worlds display the food immediately, then wait until movement resumes. Food is temporary runtime state and is excluded from saves and exports.
  • Reduced motion keeps the entire canvas still. The app honors the operating system preference when opening a scene. Secondary settings collapse on smaller screens, and controls support keyboard focus.
  • Choose Black hole in World settings for a tilted ASCII accretion disk, an upper gravitationally lensed arc, sparse stars, and a dark event horizon. It uses the scene clock, so pause and reduced motion freeze the entire effect. Pale ivory and blue-gray tones follow the creature-color setting; single-ink scenes use grayscale.

The limit is 120 simultaneous creatures. It is a safeguard, not a guaranteed frame rate; rendering cost varies with device, canvas size, artwork, and scale.

Save, import, and export

Changes to the current configuration are saved automatically in browser local storage after a short delay. Refreshing restores that configuration. Save world / My worlds supports up to 24 named presets, with load, duplicate, rename, and delete actions. Names contain 1–60 plain-text characters.

Storage is local to the current browser and site origin. Clearing site data removes these saves; another browser or device has separate saves. Storage access and quota errors are reported in the interface, and JSON export remains available. Invalid saved scenes fall back to the initial ocean with a warning; invalid preset records are skipped while valid records remain usable.

Export JSON downloads scene settings and creature IDs. It does not contain artwork, creature definitions, current positions, animation progress, or the playback pause state. Import validates the complete configuration before replacing the current scene. Imports are limited to 65,536 bytes (64 KiB). Unknown creature/environment IDs, unsupported versions, duplicate creature types, incorrect value types, invalid colors, and excess populations are rejected with an explanation. Unknown extra fields are discarded.

The current format is version 1. This is a complete minimal example:

{
  "version": 1,
  "name": "A few fish",
  "environment": "ocean",
  "populations": [{ "creatureId": "fish", "count": 6 }],
  "speed": 1,
  "scale": 1,
  "ink": "#a8e6b0",
  "useCreatureColors": true,
  "ambient": true,
  "scanlines": false,
  "reducedMotion": false,
  "intensity": 1,
  "boundary": "wrap",
  "seed": 2026
}

Optional population color values and the global ink use six-digit hex colors. Speed accepts 0–3, scale 0.5–2, intensity 0–2, and the seed is an unsigned 32-bit integer. Quantities are nonnegative integers with a combined maximum of 120.

The migration entry point is src/storage/migrations.ts. The supported legacy version 0 shape uses creatures: [{ id, count, color? }] and global color in place of ink. Other setting names match version 1; missing settings receive current defaults, and present values still undergo full validation. For example:

{
  "version": 0,
  "name": "An older ocean",
  "creatures": [{ "id": "fish", "count": 4 }],
  "color": "#a8e6b0"
}

Future schema changes should add explicit migration steps before validation. Unknown future versions are rejected rather than guessed.

Bring the world to another browser tab

The optional browser extension places the same animated creatures over a normal webpage. Its canvas is transparent and ignores pointer events, so links, selection, scrolling, and typing continue to work. The browser setup guide covers Chrome, Brave, Edge, Opera, Vivaldi, and the Firefox developer build, including troubleshooting.

npm run build:extension
  1. Open chrome://extensions in Chrome, edge://extensions in Edge, or brave://extensions in Brave.
  2. Enable Developer mode, choose Load unpacked, and select the generated dist-extension directory.
  3. Pin ASCII World, open a website, and click its toolbar icon.
  4. Choose your creature, quantity, size, speed, and white, black, or pale-colour ink. Click Place on this tab.
  5. Use Pause, Remove from tab, or Alt+Shift+A to control it. Browser shortcut assignments can be changed in the extensions shortcuts page.

Use white ink or pale colours on dark pages and black ink on light pages. Export a scene JSON from the website and import it in the extension popup to place a mixed scene. Extension settings are saved separately from the website's local storage.

Click an empty part of the page to drop food for ocean swimmers. The content script passively observes primary pointer presses; links, buttons, form controls, editable areas, text blocks, media, modified clicks, and active text selections are ignored. It never cancels the page's events. Food waits while the overlay is paused or reduced motion is active, and disappears when the overlay is removed or the page reloads.

The overlay is enabled for the tab you explicitly choose. Enable it again after reloading or navigating to another page. Browser settings pages, new-tab pages, extension stores, and other protected surfaces cannot host it. This follows the browser's activeTab access rules. Run npm run build:extension:firefox for a separate Firefox 140+ developer package, loaded through about:debugging as described in the guide. Safari is available through the web app; no Safari extension is packaged. These are development builds, not store listings.

It requests activeTab, scripting, and storage. It does not request persistent access to all websites, and its code does not read page text, form values, or browsing history. The content script mounts one isolated overlay, validates commands and scenes, and destroys its animation loop when removed.

Rebuild the shaded artwork

node tools/generate-art.mjs
node tools/generate-marine-previews.mjs

The generator creates the static modules in src/creatures/art/ from original procedural meshes. It samples surface normals, directional light, shadows, and highlights into ASCII density levels. These generated files provide gallery previews and frame-based creatures, while marine swimmers use a separate deformable point-cloud renderer during animation. The generated files are committed, so the normal app build does not need to run the generator. An optional --preview=PATH.html argument produces a local contact sheet. New creatures may still supply ordinary ASCII frame strings directly; using the modeling generator is optional.

Architecture

src/
  app/App.tsx            Authoritative scene state and UI coordination
  components/            Canvas host, creature library, controls, presets dialog
  creatures/             Declarative artwork, metadata, validation, registry
  behaviors/registry.ts  Reusable swim, drift, fly, orbit, and wander movement
  environments/          Extensible background and atmosphere definitions
  engine/                Canvas renderer, entity lifecycle, seeded randomness
  state/                 Serializable scene contract and immutable actions
  storage/               Schema validation, migrations, JSON, local persistence
  styles/                Responsive layout and accessible control styling
tests/                   Vitest tests for registry, engine, state, persistence
e2e/                     Playwright browser journeys

CreatureDefinition describes a type; SceneConfig records its selected population; SceneManager owns independently moving instances. UI state such as dialogs and pause remains separate from persisted scene data. Registering a creature makes it available to the library without a special UI branch.

SceneEngine owns one requestAnimationFrame loop. Movement uses elapsed seconds with a 50 ms maximum step, independent entity phases, and normalized positions. The renderer caches parsed and mirrored frames and measured character widths, supports high-density displays up to a device-pixel ratio of 2, and observes canvas resizing. Population changes fade instances in or out while respecting the cap; paused and reduced-motion scenes settle changes immediately so edits remain visible. Background tabs suspend the loop, and cleanup cancels animation and disconnects listeners and the resize observer.

Frame-based creatures share rasterized poses at different depths. A least-recently-used artwork cache has a 12-million-pixel budget (about 48 MB of pixel storage), caps individual sprite dimensions, and refreshes when size, resolution, or global ink changes. MarineModel builds original trunk, fin, and eye surface points once per kind. MarineRenderer bends the trunk and tail with a traveling wave, banks into turns, projects lit normals through a small depth buffer into a 64-by-32 character field, and refreshes articulated poses at up to 24 times per second while movement follows the single animation loop. Per-instance marine canvases have their own 12-million-pixel budget. Food positions and feeding targets stay in runtime memory and never enter persisted scene configuration.

drawBlackHole samples a tilted accretion disk and lensed arc into contiguous ASCII rows. Differential orbital rates, density shading, a brighter approaching edge, sparse stars, and a dark horizon give the fifth environment its depth. It follows the scene clock. The renderer also caches the static background and glyph coverage; articulated pose updates are spread across frames to keep translation responsive.

Frame pacing depends on graphics acceleration, population, and creature size. The 120-creature cap is a population limit rather than a frame-rate guarantee; software-rendered browsers benefit from fewer creatures, smaller sizes, and disabled atmosphere.

The engine consumes behavior identifiers and environment data rather than hardcoding species. Persisted configurations contain no renderer objects and can support future sharing features without changing entity runtime state.

Adding a creature

  1. Create an original definition in the appropriate src/creatures/ collection, or create a new module. Implement the existing contract from src/creatures/types.ts:

    import type { CreatureDefinition } from "./types";
    
    export const minnow: CreatureDefinition = {
      id: "minnow",
      name: "Minnow",
      description: "A tiny visitor with a flickering tail.",
      category: "Ocean",
      tags: ["small", "fish"],
      frames: ["><o>", "><o~"],
      behavior: "swim",
      speed: 22, // Pixels per second before scene/instance variation
      scale: 0.8,
      frameDuration: 280, // Milliseconds per frame
      color: "#a8e6b0",
      facing: "right",
    };
  2. Add it to an existing exported collection, or import and include the new definition in createRegistry([...]) in src/creatures/registry.ts. IDs must be unique and stable because scenes refer to them.

  3. Reuse an existing behavior when possible. To add a new one, extend BehaviorId, the supported behavior set in creature validation, and the implementation map in src/behaviors/registry.ts.

  4. Extend tests/creatures.test.ts with meaningful checks where needed. validateCreature checks metadata, behavior, numeric bounds, colors, and artwork budgets; createRegistry reports and skips invalid definitions and duplicate IDs. The interface reports unavailable artwork while keeping valid creatures usable.

  5. Run npm run check, then confirm the creature appears in search, can be added and removed, and renders at desktop and mobile sizes.

Artwork uses printable ASCII and line breaks, with whitespace preserved. Each definition supports 1–24 frames, each no larger than 96 columns, 32 rows, or 3,072 characters; the total frame budget is 24,000 characters. facing allows the renderer to mirror directional artwork. Optional attribution: { author, license, url? } records source information; use an HTTPS URL and verify permission before adding third-party art.

Checks

npm run check        # ESLint, Vitest, TypeScript, production build
npm run test:watch   # Vitest during development

Unit tests cover creature validation and registry completeness, entity lifecycle, behavior and boundaries, seeded animation, population actions, schema limits, migration, JSON round trips, storage failures, and preset recovery.

Install a Chromium browser once before the browser suite:

npx playwright install chromium
npm run test:e2e

An existing compatible Chromium installation can be supplied through PLAYWRIGHT_EXECUTABLE_PATH. For example, in PowerShell:

$env:PLAYWRIGHT_EXECUTABLE_PATH = 'C:\Program Files\Google\Chrome\Application\chrome.exe'
npm run test:e2e

Set PLAYWRIGHT_BASE_URL to test an already running server, including the production preview. For hosts where launching a browser through a pipe is unavailable, PLAYWRIGHT_CDP_URL can connect to a separately started Chromium debugging endpoint. These options are only for test execution.

Playwright exercises the primary browser journey, including creature changes, settings, playback, saving, and refresh restoration. Inspect its configuration for the test server and browser launch settings.

Scope and credits

This release is a local creative tool: no authentication, backend database, cloud synchronization, public sharing service, custom artwork upload, or external creature-pack loader is implemented. Configuration exports depend on the built-in registry available in the receiving app.

The animated ASCII-creature concept was inspired by stackedlol/ascii-sharks. This project uses original models, sprites, and an independent implementation.

About

Beautiful ASCII animations

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages