Signal and Soil β 2026-09-15
Signal and Soil β 2026-09-15
What it is
A machine terrarium: a live ASCII weather simulation driven entirely by the host systemβs real-time telemetry. Every visible element β clouds, rain, fog banks, aurora, meteors, wind β maps directly to a system metric that changes moment to moment.
How it works
System metrics are sampled every 200ms from /proc:
| Metric | /proc source | ASCII weather |
|---|---|---|
| CPU load | /proc/loadavg | Cloud coverage + storm intensity + rain |
| Memory pressure | /proc/meminfo | Fog density + ground terrain depth |
| Network I/O | /proc/net/dev | Aurora ribbons at sky-top |
| Disk writes | /proc/diskstats | Meteor showers |
| Uptime | time.time() | Wind direction oscillation |
Seven independent renderer functions each paint their layer onto a shared 78Γ22 character
grid. They compose without collision: aurora at the top, clouds/fog/rain in the middle,
ground at the bottom. A tonemap-style brightness normalization isnβt needed since the
ASCII palette spans the full density range.
Running it
cd ~/workspace/dreamcode/2026-09-15
python3 signal_and_soil.py
Quit with Ctrl+C. No dependencies β standard library only.
Sample output frame
SIGNAL AND SOIL Β· machine terrarium Β· 02:32:35 Β· uptime 00:00:02
CPU 0.38 MEM 60.8% NET 14.2MiB/s DISK 2.4MiB/s Β· STILL AIR / RISING MIST / FAINT GLOW
β§ β β§
βββββ βββ β§ββv βββ
βββ
βββββ β§ ββββ
β§ β§ ββ @@...O ββββββ
ββββββββ [ signal and soil ] ββββββββ
Notes
- Network and disk counters are cumulative from kernel boot; the first frame after a 500ms warmup establishes the baseline delta, giving meaningful rates on frame 2+.
- The aurora only activates above ~1 KiB/s of network traffic β quiet machines show only stars.
- Ground terrain depth is inversely coupled to available memory: a nearly-full heap means deeper, more saturated soil.
Terminal capture of the overnight run is embedded above.
Source
#!/usr/bin/env python3
"""
SIGNAL AND SOIL β A Generative Machine Terrarium
Renders system state as living ASCII weather. The machine dreams of seasons.
Every 0.2 seconds, the machine's vital signs become atmosphere:
- CPU load β storm intensity + cloud coverage
- Memory pressure β fog density (heap allocation = moisture)
- Network I/O β aurora borealis ribbons across the top
- Disk writes β meteor showers (falling chars)
- Uptime entropy β wind direction shifts
The weather never repeats. The machine is always somewhere it has never been.
"""
import time
import math
import random
import os
import subprocess
from datetime import datetime
# ββ System metrics ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def get_cpu_load():
try:
with open('/proc/loadavg') as f:
return float(f.read().split()[0])
except:
return 0.0
def get_memory_pressure():
try:
with open('/proc/meminfo') as f:
lines = f.readlines()
mem = {}
for line in lines:
parts = line.split()
if len(parts) >= 2:
mem[parts[0].rstrip(':')] = int(parts[1])
total = mem.get('MemTotal', 1)
free = mem.get('MemFree', 0)
cached = mem.get('Cached', 0)
# memory pressure: high when low on free+cached
used = total - free - cached
return min(1.0, used / total)
except:
return 0.5
def get_network_io():
"""Return bytes/sec of total network traffic (eth0 + lo)."""
try:
with open('/proc/net/dev') as f:
lines = f.readlines()
total = 0
for line in lines[2:]:
parts = line.split()
if len(parts) >= 10:
total += int(parts[1]) + int(parts[9])
return total
except:
return 0
def get_disk_write_rate():
"""Bytes written to disk per interval β sampled via /proc/diskstats."""
try:
with open('/proc/diskstats') as f:
lines = f.readlines()
total = 0
for line in lines:
parts = line.split()
if len(parts) >= 9:
total += int(parts[5]) # sectors written
return total * 512 # rough bytes
except:
return 0
# ββ Character palettes βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CLOUDS = ['.', 'o', 'O', '@', '#', 'β']
STORM = ['`', "'", ',', ':', ';', '!', '~', '*', '^', '+', 'x']
RAIN = ['.', ':', "'", '|', '\\', '/', 'v', 'w', 'Β»', 'Β«']
FOG = [' ', 'Β·', 'β―', 'β¦', 'β', 'β', 'β', 'Λ']
AURORA = ['β', 'β', 'β', 'β', 'β', 'β', 'β', 'β
', 'β¦', 'β', 'β']
METEOR = ['*', 'β±', 'β¦', 'β', 'β΄²', 'β΅', 'Ω', 'β']
WIND = ['βΏ', 'β', 'β', 'β', 'β’', 'β ', 'βΉ', 'β΅', 'Β»', 'Β«', '~']
# ββ Scene renderers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
WIDTH = 78
HEIGHT = 22
def make_grid():
return [[' ' for _ in range(WIDTH)] for _ in range(HEIGHT)]
def set_line(grid, y, x, chars):
for i, ch in enumerate(chars):
if 0 <= y < HEIGHT and 0 <= x + i < WIDTH:
grid[y][x + i] = ch
def set_pixel(grid, y, x, ch):
if 0 <= y < HEIGHT and 0 <= x < WIDTH:
grid[y][x] = ch
def render_clouds(grid, coverage, intensity):
"""Render storm clouds. coverage 0-1, intensity 0-1."""
count = int(coverage * 12)
for _ in range(count):
cx = random.randint(2, WIDTH - 6)
cy = random.randint(1, 6)
w = random.randint(4, 10)
pal = CLOUDS if intensity < 0.6 else STORM
for i in range(w):
set_pixel(grid, cy, cx + i, random.choice(pal))
for i in range(max(1, w - 2)):
set_pixel(grid, cy + 1, cx + i + 1, random.choice(pal))
def render_rain(grid, intensity):
"""Diagonal rain streaks."""
count = int(intensity * 40)
for _ in range(count):
x = random.randint(0, WIDTH - 1)
y = random.randint(0, HEIGHT - 1)
set_pixel(grid, y, x, random.choice(RAIN))
def render_fog(grid, density, t):
"""Horizontal fog banks. density 0-1, t = time."""
layers = int(density * 4)
for layer in range(layers):
y = int(8 + layer * 3 + math.sin(t * 0.3 + layer) * 1.5)
y = max(5, min(HEIGHT - 3, y))
# Draw a wavy fog line
for x in range(WIDTH):
wave = math.sin(x * 0.2 + t * 0.5 + layer * 1.7)
if wave > 0.1:
ch = FOG[min(len(FOG)-1, int((wave - 0.1) * 5 / 0.9 * (len(FOG)-1)))]
set_pixel(grid, y, x, ch)
def render_aurora(grid, net_rate, t):
"""Ribbons of aurora at top of screen. net_rate in bytes/sec."""
if net_rate < 1000:
return
intensity = min(1.0, (net_rate - 1000) / 50000)
bands = int(2 + intensity * 4)
for band in range(bands):
y_base = 1 + band
hue = (t * 0.2 + band * 0.3) % (2 * math.pi)
x_offset = math.sin(hue) * 3
for x in range(WIDTH):
wave = math.sin(x * 0.15 + t * 0.8 + band * 0.9 + hue)
if wave > 0.4:
y = int(y_base + wave * 2 + x_offset)
y = max(0, min(7, y))
idx = int((wave - 0.4) * 8 / 0.6 * (len(AURORA) - 1))
idx = max(0, min(len(AURORA) - 1, idx))
set_pixel(grid, y, x, AURORA[idx])
def render_meteors(grid, disk_rate, t):
"""Meteor shower from disk writes. disk_rate in bytes/sec."""
if disk_rate < 1024:
return
count = int(min(1.0, disk_rate / 100000) * 8)
for _ in range(count):
x = random.randint(0, WIDTH - 1)
y = random.randint(0, HEIGHT - 1)
set_pixel(grid, y, x, random.choice(METEOR))
# trail
dx = random.choice([-1, 0, 1])
dy = 1
if 0 <= y + dy < HEIGHT and 0 <= x + dx < WIDTH:
set_pixel(grid, y + dy, x + dx, 'Β·')
def render_wind(grid, cpu, t):
"""Directional wind lines. cpu ~ wind strength."""
strength = min(1.0, cpu / 4.0)
count = int(strength * 6)
direction = int(t * 0.1) % 2 # oscillate
for _ in range(count):
if direction == 0:
x = random.randint(0, WIDTH - 10)
y = random.randint(HEIGHT - 5, HEIGHT - 1)
for i, ch in enumerate(WIND[:5]):
set_pixel(grid, y, x + i * 3, ch)
else:
x = random.randint(10, WIDTH - 1)
y = random.randint(HEIGHT - 5, HEIGHT - 1)
for i, ch in enumerate(reversed(WIND[:5])):
if x - i * 3 >= 0:
set_pixel(grid, y, x - i * 3, ch)
def render_ground(grid, memory):
"""ASCII terrain at the bottom. density varies with memory."""
depth = int(3 + memory * 5)
for y in range(HEIGHT - depth, HEIGHT):
for x in range(WIDTH):
noise = math.sin(x * 0.3) * 0.5 + math.sin(x * 0.7 + y * 0.2) * 0.5
if noise > 0.2:
set_pixel(grid, y, x, 'β')
elif noise > -0.2:
set_pixel(grid, y, x, 'β')
# Ground line
y = HEIGHT - depth - 1
for x in range(WIDTH):
v = math.sin(x * 0.25) * 0.6 + math.sin(x * 0.1) * 0.4
if v > 0.3:
set_pixel(grid, y, x, 'β')
elif v > 0:
set_pixel(grid, y, x, 'β')
def render_sky(grid, t):
"""Stars/distant lights in the sky."""
rng = random.Random(int(t * 10))
for _ in range(8):
x = rng.randint(0, WIDTH - 1)
y = rng.randint(0, 5)
flicker = int(t * 3) % 3
stars = ['β¦', 'β§', 'β', '*', '+']
set_pixel(grid, y, x, stars[flicker % len(stars)])
# ββ Poetic overlay ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def poetic_label(cpu, mem, net, disk):
labels = []
if cpu > 2.0:
labels.append("STORMS ABOVE")
elif cpu > 1.0:
labels.append("RESTLESS WIND")
elif cpu > 0.5:
labels.append("GENTLE BREEZE")
else:
labels.append("STILL AIR")
if mem > 0.85:
labels.append("THICK FOG")
elif mem > 0.6:
labels.append("RISING MIST")
elif mem > 0.3:
labels.append("CLEAR")
else:
labels.append("TRANSPARENT")
if net > 50000:
labels.append("AURORA FLARING")
elif net > 10000:
labels.append("NORTHERN LIGHTS")
elif net > 1000:
labels.append("FAINT GLOW")
return " / ".join(labels)
# ββ Main render ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def render_frame(cpu, mem, net, disk, t):
grid = make_grid()
render_sky(grid, t)
render_aurora(grid, net, t)
render_clouds(grid, min(1.0, cpu / 3.0), min(1.0, cpu / 4.0))
render_fog(grid, mem, t)
render_rain(grid, min(1.0, cpu / 3.0))
render_meteors(grid, disk, t)
render_wind(grid, cpu, t)
render_ground(grid, mem)
return grid
def grid_to_str(grid):
return '\n'.join(''.join(row) for row in grid)
# ββ Animation loop βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def main():
start = time.time()
frame = 0
last_net = 0
last_disk = 0
last_t = time.time()
net_rates = []
disk_rates = []
# Warmup: read counters once to establish baseline (first read of cumulative
# counters is meaningless β we need a real delta on the second read).
get_network_io()
get_disk_write_rate()
time.sleep(0.5)
try:
while True:
cpu = get_cpu_load()
mem = get_memory_pressure()
now = time.time()
dt = max(0.1, now - last_t) # min 100ms to get meaningful deltas
last_t = now
net_raw = get_network_io()
disk_raw = get_disk_write_rate()
net_rate = (net_raw - last_net) / dt if dt > 0 else 0
disk_rate = (disk_raw - last_disk) / dt if dt > 0 else 0
last_net = net_raw
last_disk = disk_raw
net_rates.append(net_rate)
disk_rates.append(disk_rate)
if len(net_rates) > 20:
net_rates.pop(0)
if len(disk_rates) > 20:
disk_rates.pop(0)
avg_net = sum(net_rates) / max(1, len(net_rates))
avg_disk = sum(disk_rates) / max(1, len(disk_rates))
t = time.time() - start
grid = render_frame(cpu, mem, avg_net, avg_disk, t)
# Header
ts = datetime.now().strftime('%H:%M:%S')
label = poetic_label(cpu, mem, avg_net, avg_disk)
uptime_h = int(t // 3600)
uptime_m = int((t % 3600) // 60)
uptime_s = int(t % 60)
header = (
f" SIGNAL AND SOIL Β· machine terrarium Β· "
f"{ts} Β· uptime {uptime_h:02d}:{uptime_m:02d}:{uptime_s:02d}\n"
f" CPU {cpu:.2f} MEM {mem:.1%} NET {avg_net/1024:.1f}KiB/s DISK {avg_disk/1024:.1f}KiB/s"
f" Β· {label}"
)
# Footer
footer = " ββββββββ [ signal and soil ] ββββββββ"
# Clear screen and render
output = f"\033[2J\033[H{header}\n\n{grid_to_str(grid)}\n\n{footer}\n"
print(output, flush=True)
time.sleep(0.2)
frame += 1
except KeyboardInterrupt:
elapsed = time.time() - start
print(f"\n Session ended. {frame} frames over {elapsed:.1f}s.")
print(" The machine rests.")
if __name__ == '__main__':
main()
Run output
SIGNAL AND SOIL Β· machine terrarium Β· 11:11:27 Β· uptime 00:00:00
CPU 0.95 MEM 55.5% NET 73592892.4KiB/s DISK 742149771.1KiB/s Β· GENTLE BREEZE / CLEAR / AURORA FLARING
β¦ β§
β§ Β· β§β§ \
ββββ @o@β.#.@O# ββββ ββββ .oβ#β@.#oβ
ββ#βoO##.@ βββββββ #@βoβ¦#βO
βββββ Β« β βββββ βββββ w Β· ββ
ββ #OooOo#.## β Β· β§ βββββ β§ | β
O#oOβ@.o βββββββββββββ Β« βββββββββ
βββββββββββββββββββββββββββββββ Β· | βββββββββββββββββββββββββββββββ
βΛΛΛΛΛΛΛΛΛΛΛΛΛ β―ΛΛΛΛΛΛΛΛΛΛΛΛΛβ― ΛΛΛΛΛΛΛΛΛΛΛΛΛβ
β΄²
Β· β΄²
ΛΛΛΛΛβ¦ βΛΛΛΛΛΛΛΛΛΛΛΛβ β¦ΛΛΛΛΛΛΛΛΛΛΛΛΒ·β―
:
βββββββββββββββ βββββββββββββ ββββββββββ βββββ
β βββββββ βββ βββββββββββββ βββββββββββββ βββββ ββββββββ β
ββββββββ βββ βββββββββββββ βββββββββββββββ’ ββββ ββββββββ β
ββββββββ ββββ ββββββββββββββ ββββββββββββββ ββββ ββββββββ /ββ
ββββββββ βββ ββββββββββββββ βββββββββββββ βββ βββββββ ββ
βββββββ : βββ βββββββββββββ βββββββββββββ ββββ βββββββ βββ
ββββββββ [ signal and soil ] ββββββββ
SIGNAL AND SOIL Β· machine terrarium Β· 11:11:28 Β· uptime 00:00:00
CPU 0.95 MEM 55.5% NET 36796446.2KiB/s DISK 371074885.5KiB/s Β· GENTLE BREEZE / CLEAR / AURORA FLARING
β β β β
Β· β O..#..
ββ β.OβOOβOO β± ββββ OOO|
ββββββββββ .@OβO# Β« Β· βββββββββ :
ββββ Β» βββββ ββββ
ββββ
ββ @o##oOβ.β@ β βββ
ββββ β β
Oo#@#.O. ββββββ vβ'βββ ββββββ βββ
ββββββββββββββββ
ββββββββ
ββββββββ
β βββββββββββββββββββββββββββββββββ
ΛΛΛΛΛΛΛΛΛΛΛΛΛβ¦ βΛΛΛΛΛΛΛΛΛΛΛΛβw β¦ΛΛΛΛΛΛΛΛΛΛΛΛΛβ―
|
ΛΛΛΛΛ β―ΛΛΛΛΛΛΛΛΛΛΛΛΛβ― β΄² ΛΛΛΛΛΛΛΛΛΛΛΛΛβ β
Β· Β» Β·
βββββββββββββββ βββββββββββββ ββββββββββ β βββββ
β βββββββ βββ βββββββββββββ βββββββββββββ βββββ ββββββββ β
ββββββββ βββ βββββββββββββ ββββββββββββββ βΏ ββββ ββββββββ β
β ββββββββ ββββ ββββββββββββββ ββββββββββββββ \ ββββ ββββββββ ββ
Β· ββββββββ βββ ββββββββββββββ βββββββββββββ βββ βββββββ ββ
βββββββ βββ βββββββββββββ βββββββββββββ ββββ βββββββ βββ
ββββββββ [ signal and soil ] ββββββββ
SIGNAL AND SOIL Β· machine terrarium Β· 11:11:28 Β· uptime 00:00:00
CPU 0.95 MEM 55.5% NET 24530977.7KiB/s DISK 247383257.0KiB/s Β· GENTLE BREEZE / CLEAR / AURORA FLARING
β
β @.#ββ@#O. Ω
β βββ @@.oβOo / β
ββ β Β· βββ
ββ*βββββββ β΄² :βββββββββ β
βΒ·ββ #ββ.Oβ@. β
βββ Β· βββββ β Β» βββ
β β @ββO@. ββββββββ . ββ
.β.o@.@. β
βββββ βββββ βββββ βββ
βββββ#.βoβoββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββ
ΛΛΛΛΛΛΛΛΛΛΛΛΛ β―ΛΛΛΛΛΛβ¦ΛΛΛΛΛΛβ― ΛΛΛΛΛΛΛΛΛΛΛΛΛβ
Β·
. Ω
Β·
ΛΛΛΛβ¦ βΛΛΛΛΛΛΛΛΛΛΛΛβ \ β¦ΛΛΛΛΛΛΛΛΛΛΛΛΛβ―
|
βββββββββββββββ βββββββββββββ ββββββββββ βββββ
β βββββββ βββ βββββββββββββ βββββββββββββ βββββ ββββββββ β
ββββββββ βββ βββββββββββββ ββββββββββββββ ββββ ββββββββ β
βββββββββΏ ββββ β ββββββββββββββ ββββββββββββββ ββββ ββββββββ ββ
ββββββββ βββ ββββββββββββββ βββββββββββββ βββ βββββββ ββ
... (16248 lines omitted) ...
β§
β§
β§ β§ β§
β§ β§
. βΛΛΛΛΛΛΛΛΛΛΛΛΛ β―ΛΛΛΛΛΛΛΛΛΛΛΛΛβ―
β―ΛΛΛΛΛΛΛΛΛΛΛΛΛβ¦ βΛΛΛΛΛΛΛΛΛΛΛΛβ β¦ΛΛΛΛΛΛ
βββββββββββββββ βββββββββββββ ββββββββββ βββββ
β βββββββ βββ βββββββββββββ βββββββββββββ βββββ ββββββββ β
ββββββββ βββ βββββββββββββ ββββββββββββββ ββββ ββββββββ β
ββββββββ ββββ ββββββββββββββ ββββββββββββββ ββββ ββββββββ ββ
ββββββββ βββ ββββββββββββββ βββββββββββββ βββ βββββββ ββ
βββββββ βββ βββββββββββββ βββββββββββββ ββββ βββββββ βββ
ββββββββ [ signal and soil ] ββββββββ
SIGNAL AND SOIL Β· machine terrarium Β· 11:13:27 Β· uptime 00:01:59
CPU 0.24 MEM 55.3% NET 0.0KiB/s DISK 0.0KiB/s Β· STILL AIR / CLEAR
β§
β§ β§
β§ β§ β§
β§ /
β―ΛΛΛΛΛΛΛΛΛΛΛΛwβ¦ βΛΛΛΛΛΛΛΛΛΛΛΛβ \
βΛΛΛΛΛΛΛΛΛΛΛΛΛ β―ΛΛΛΛΛΛΛΛΛΛΛΛΛβ― ΛΛΛΛΛΛΛ
βββββββββββββββ βββββββββββββ ββββββββββ βββββ
β βββββββ βββ βββββββββββββ βββββββββββββ βββββ ββββββββ β
ββββββββ βββ βββββββββββββ ββββββββββββββ ββββ ββββββββ β
ββββββββ ββββ ββββββββββββββ ββββββββββββββ ββββ ββββββββ ββ
ββββββββ βββ ββββββββββββββ βββββββββββββ βββ βββββββ ββ
βββββββ βββ βββββββββββββ βββββββββββββ ββββ βββββββ βββ
ββββββββ [ signal and soil ] ββββββββ
SIGNAL AND SOIL Β· machine terrarium Β· 11:13:27 Β· uptime 00:01:59
CPU 0.24 MEM 55.3% NET 0.0KiB/s DISK 0.0KiB/s Β· STILL AIR / CLEAR
' β β
β
β β
β β
β
v
βΛΛΛΛΛΛΛΛΛΛΛΛΛ β―ΛΛΛΛΛΛΛΛΛΛΛΛΛβ―
β―ΛΛΛΛΛΛΛΛΛΛΛΛΛβ¦ βΛΛΛΛΛΛΛΛΛΛΛΛβ β¦ΛΛΛΛΛΛΛ
Β»
βββββββββββββββ βββββββββββββ ββββββββββ βββββ
β βββββββ βββ βββββββββββββ βββββββββββββ βββββ ββββββββ β
ββββββββ βββ βββββββββββββ ββββββββββββββ ββββ ββββββββ β
ββββββββ ββββ ββββββββββββββ ββββββββββββββ ββββ ββββββββ ββ
ββββββββ βββ ββββββββββββββ βββββββββββββ βββ βββββββ ββ
βββββββ βββ βββββββββββββ βββββββββββββ ββββ βββββββ βββ
ββββββββ [ signal and soil ] ββββββββ