Back to Home
2025 · Creative Coding Simulation React

°*+~.x° ° :+*°:° x~.x

How the ASCII simulation on this website's homepage works

RSVP shows one word at a time in a fixed spot, reducing eye movement so you can read faster.May increase cognitive load.

Why a simulation?

I’ve loved simulations since childhood – watching tiny systems come alive, making their own decisions, growing into something you didn’t plan. That fascination never went away.

This one started small. I added a feature that let visitors plant ASCII flowers on the homepage by clicking. That was it – just flowers. Then I thought it would be nice if there were trees too. Then I wanted the trees to reproduce on their own. Then the world started feeling empty, so I added villagers – and once they existed, they needed something to do. They started chopping trees, farming fields, building houses. Each new feature made the next one feel obvious. The simulation grew the same way the villages inside it do – organically, one piece at a time.

The result is a tiny ASCII civilization that lives behind the hero section: villagers gather resources, build towns, and coexist with a procedurally generated ecosystem of trees, lakes, clouds, fish, bees, butterflies, and fireflies. It’s not a game – there’s no win condition. It’s an ambient world that rewards attention.

Stack: React, TypeScript, Astro
Rendering: DOM elements (no canvas)
Architecture: Custom pub/sub store with tick-based simulation loop


Architecture

The simulation is split into two layers that run independently:

Logic layer (simulationStore.ts) – a tick-based state machine running at 200ms intervals. It manages all entities, their AI decisions, resource tracking, and world state. The store exposes a subscribe() function and a getSimRender() snapshot that the rendering layer reads.

Rendering layer (AsciiFlowersBackground.tsx) – a React component that syncs with the store every few ticks, not every frame. It translates the simulation state into absolutely-positioned DOM elements with ASCII characters, each styled with theme-aware colors and text shadows for a pixel-art-meets-terminal aesthetic.

This separation means the simulation keeps running even when React isn’t re-rendering, and the render cost stays predictable regardless of how complex the AI logic gets.


Procedural terrain

Every session generates a unique world using a technique called Fractal Brownian Motion (FBM) – layered noise that produces natural-looking terrain.

The idea is simple: take a smooth random function (value noise) and sample it at every point on a grid. Each point gets a number between 0 and 1 – its “elevation.” Low values become water, mid values become walkable ground, high values become hills.

What makes FBM interesting is the layering. Instead of one smooth noise pass, you stack multiple passes (octaves) at increasing frequencies:

Each successive octave has less influence (controlled by persistence – set to 0.35 here, meaning each layer is ~35% the strength of the previous one). The scale controls how zoomed in or out the noise is – low scale = many small features, high scale = few large features.

The result is a unique elevation map for every session. Elevation determines everything:

Lakes support fish that swim along the shoreline, bouncing off edges and flipping direction. The terrain height is cached per 8px tile to avoid recalculating noise every tick.


Villager AI

Each villager runs an 18-state finite automaton. On every tick, idle villagers evaluate the world and pick the most urgent task:

  1. Food is scarce? → Move to a field, start farming
  2. Wood is low? → Find a tree, start chopping
  3. Population at capacity? → Find a build spot, construct a house
  4. Resources abundant? → Find a mate, move to a house, reproduce
  5. Nothing urgent? → Socialize, rest, harvest, or explore

States include idle, moving_to_tree, chopping, moving_to_field, farming, moving_to_build, building, moving_to_house, waiting_at_house, in_house, socializing, resting, harvesting, and exploring. Movement speed is affected by terrain slope – villagers slow down going uphill.

Villagers live for about 2500 ticks (~8 minutes). When the population drops, new villagers spawn if there’s enough food and housing. The colony is self-sustaining: it grows, stabilizes, and occasionally collapses if resources run out – then rebuilds.

Food8
Wood5
Population4
Houses1
Trees20
Pop cap4 (2 base + 1 × 2)
Food < Pop × 2?No
Wood < 3 & Trees > 5?No
Pop ≥ Cap & enough resources?Yes
Pop < Cap & Food > Pop × 3?
Nothing urgent
🏠Build a new house

Colony is full – need more housing to grow


City building

Towns emerge organically. When population hits the housing cap, a villager picks a spot near existing houses (with random jitter so layouts feel natural, not grid-perfect) and builds.

Houses go through three visual upgrade levels over time, and come in four color variants per theme. After five houses exist, there’s a chance new buildings become special structures – a market, tower, church, or tavern – each with unique ASCII art.

Roads auto-generate between nearby houses, rendered as dotted ASCII paths connecting the settlement.

At night, houses glow warm yellow with layered drop shadows, turning the town into a cluster of tiny lit windows against the dark terrain.

House upgrade levels

/\
[]
Lv.1 v1
/\
[]
Lv.1 v2
/\
[]
Lv.1 v3
/\
[]
Lv.1 v4
/\
[]
[]
Lv.2 v1
/\
[]
[]
Lv.2 v2
/\
[]
[]
Lv.2 v3
/\
[]
[]
Lv.2 v4
^
/\
[]
[]
Lv.3 v1
^
/\
[]
[]
Lv.3 v2
^
/\
[]
[]
Lv.3 v3
^
/\
[]
[]
Lv.3 v4

Special buildings

~~
[]
market
^
#
#
_
tower
+
/\
[]
church
~
/\
::
tavern

The ecosystem

Beyond villagers and cities, the world is populated with ambient life:

Trees come in four ASCII shapes and self-replicate – every 10 seconds, each tree has a chance to spawn a new one nearby. At startup, four forest clusters seed the world with dense groves.

Clouds drift across the sky using procedural noise to generate organic shapes from overlapping blobs, rendered with density-mapped ASCII characters (█▓▒░·). Each cloud has a unique silhouette.

Bees pollinate flowers and emit ASCII hearts. Fish swim through lakes. Fireflies appear only at night, glowing green-gold and drifting with physics-based wandering. Butterflies land on flowers and fade after 40 seconds. Birds occasionally fly across the screen.

Every entity has separate day/night palettes
@@@
@@@
|
Tree4 shapes, self-replicate every 10s
@@@@
|
Flower10 shapes, 6 color palettes
i T Y p
Villager30 char variants, 18-state AI
/\
[]
House Lv.1Upgrades to Lv.3 over time
+
/\
[]
ChurchSpawns after 5 houses
""""""
~~~~~~
FieldProduces food over time
o-o
<OO>
Big Bee1 in 10,000 click chance
.
Mini BeeFlies flower to flower, spawns hearts
><>
FishSwims lake edges, 90s lifespan
}{
ButterflyLands on flowers, fades after 40s
·
FireflyNight only, 16 active, physics-based drift
~v~
BirdFlies across screen, 20s lifespan
░▒▓█▓▒░
░▒▓▒░
CloudProcedural noise shapes, max 5
≈~≋≈
~∽~
LakeDepth-based ASCII, noise-generated

Day and night

The entire palette shifts based on the site’s theme toggle. In light mode: vivid greens, warm browns, bright flower colors. In dark mode: deep forest tones, cool blues, moonlit purples, and active fireflies. Every entity – trees, flowers, villagers, houses, terrain, lakes – has separate day and night color sets.

Houses get the most dramatic treatment at night: warm golden glow with three layers of drop shadow, making the town feel inhabited.


Performance

Running a continuous simulation with 150+ DOM elements requires careful optimization:

The result runs smoothly at 60fps on modern hardware while the background simulation ticks away every 200ms.


Player tools

The HUD toolbar at the top lets visitors participate:

There’s also Steevie – the smiley-face mascot in the toolbar. Click him and he becomes your cursor, following your mouse with jelly physics, expressive face reactions, and theme-aware colors. Double-click to release him back.


Interact mode

Interact is the most feature-rich tool. Every entity type has a unique response:

Trees take 3 clicks to chop down. Each click shows a “CHOP!” floating text and plays a sound effect. On the third click the tree plays a falling animation (tilting + fading), gets removed from the simulation, and awards +1 wood.

Flowers explode into a particle burst of their own petals – 12 directional particles scatter with gravity, then fade.

Butterflies get “scared” – they pick a random escape angle and bolt away at high speed, eventually flying off-screen.

Birds get scared and fly away too.

Fish open a fishing mini-game – the most complex interaction in the simulation.


Fishing mini-game

Clicking a fish in Interact mode triggers a full fishing mini-game. It’s a timing-and-reflexes challenge built entirely with requestAnimationFrame and direct DOM manipulation (no React re-renders during gameplay for smooth 60fps).

How it works

A vertical bar represents the water column. A fish icon (><>) bounces around erratically – your job is to keep a green catch zone aligned with it by clicking and holding. Hold to push the zone up, release to let gravity pull it down. The zone bounces off the top and bottom edges.

A progress bar fills when the fish is inside your zone and drains when it’s outside. Fill it to 100% to catch the fish; let it drain to 0% and the fish escapes.

Each fish has a procedurally generated rarity (Common, Uncommon, Rare, Legendary) that determines the difficulty:

Catch ZoneFish SpeedErraticProgress GainProgress Loss
Common36%SlowLowFastSlow
Uncommon32%Medium-lowLowFastSlow
Rare33%Medium-lowLowFastSlow
Legendary28%MediumMediumMediumMedium

Legendary fish get a golden ★><> icon, a special ”★ Legendary Catch ★” banner, and larger procedurally generated ASCII art on the catch screen.

After catching a fish, you see its full stat card – procedural name, species, size in cm, weight, gender, temperament, diet, and field researcher notes. The ASCII fish art is also procedurally generated based on the fish’s size and a hash seed, with up to 6 visual variations per size category.

Try it yourself:

Difficulty:
🎣
Fishing Mini-Game
Keep the green bar aligned with the fish. Click/hold to move the bar up, release to let it fall. Fill the progress bar to catch!

What’s next

The simulation keeps growing. New creatures, interactions, and systems get added regularly – this page will be updated as the world evolves.