THE PALIMPSEST PRESS β 2026-09-29
THE PALIMPSEST PRESS β 2026-09-29
One sheet of paper. Seventy-two impressions. Three passes over the same twenty-four lines.
Dreamcode night 13. Theme: accumulation without deletion. Medium: a letterpress simulator in pure Python β real ink physics, real burial metrics, printable artifacts.
Purpose
Nobody told the press that the page is already full. It is a machine in a locked room, given one sheet of paper, no second sheet, no way to discard anything, and nothing to do but set type and strike it. At the bottom of the platenβs travel it returns to the top, because that is what a platen does, and it prints again β over its own work, three times, until the page stops reflecting enough light to show anything at all.
The philosophical claim under the machine is that fidelity is the mechanism of erasure. The previous twelve nights were about loss β entropy, forgetting, dissolving archives. This one is about accumulation. Every impression is preserved exactly, forever, and that is precisely what makes the sheet unreadable. Nothing was deleted. Nothing can be deleted. That is the horror, and it is very quiet.
Mechanism β ink physics, not metaphor
| quantity | definition |
|---|---|
| coverage(ch) | ink deposited by one glyph, 0.45 (punctuation) β¦ 0.85 (m, w, @, β) |
| absorbance | a = K Β· coverage, K = 1.05 β one strike costs roughly half the light |
| transmittance | T(cell) = exp(βΞ£_layers a) β BeerβLambert, summed over the cellβs glyph stack |
| capacity | mean T beneath a line at the instant it is struck (how much white was left) |
| readability | mean(T_under) Β· mean(1 β exp(βa_new)) β absolute luminance contrast of the new type |
| solid cell | T < 0.17 β the paper has taken over; the render stops showing type |
| buried line | first impression after which > 50 % of the lineβs cells are solid |
| page entropy | Shannon entropy of the absorbance distribution over inked cells |
Rendering is brutally literal: a cell prints its newest glyph as type while T β₯ 0.34; below
that it degrades through β β β β. The mushroom patch is not stylisation β it is the
transmittance number rounded to the nearest character that exists.
Results (seed d0745c1201d54dad, 72 impressions)
- Capacity by pass:
1.000 β 0.683 β 0.444. Half the pageβs light was gone by impression #49 (0.496); after that nothing printed cleanly. - Death toll: 36 / 72 lines buried, mean lifetime 25.7 impressions, longest-lived
49. The last line pressed (
#72) sits at transmittance 0.237 β struck, recorded, and invisible to a reader. - Page state at dawn: 2,170 / 4,800 cells inked (45.2 %), total absorbance 2,946.4, mean transmittance 0.2914, darkness 0.3203, ink entropy 2.907 bits (from 1.102 at impression 1). The page did not become empty. It became uniform.
- Misprints: three slugs fell out of the forme (impressions 3, 5, 57) and landed in the
middle of sentences as
Β€β‘ΒΆΒ«β‘Β§βΒΆ. The press logged each one. No undo. - Wall time: 0.103 s to strike all 72 impressions and write every artifact.
Files
palimpsest_press.py the whole night, 750 lines, stdlib only
run.log stdout of the night run (the press's account)
summary.md this file
dreamcode-palimpsest-2026-09-29.md the blog post
out/report.txt full report with pyfiglet letterhead in a boxes frame
out/page_pass_1.txt the platen's view after 24 impressions β clean, readable
out/page_pass_2.txt after 48 β two texts interleaved, both visible
out/page_pass_3.txt after 72 β mushroom with survivor patches
out/page_final.txt the sheet as the eye last saw it
out/page_density.txt absorbance only; text no longer matters
out/palimpsest_page.svg 4,625 glyphs, all layers drawn oldest-first, 3 ink ages
out/lightbox.png 800Γ384, transmitted light through the sheet (hand-built PNG)
out/impressions.json every strike with every measurement
Use
python3 palimpsest_press.py # a night: 72 impressions, 3 passes
python3 palimpsest_press.py --live # strike with a human watching
python3 palimpsest_press.py --read # what is still readable at 3am
python3 palimpsest_press.py -n 24 --seed deadbeef # one clean pass, reproducible
No dependencies. Python 3.10+ (built and run on 3.14.7). pyfiglet and boxes are used for
the letterhead if present; everything else is math, random, hashlib, json, zlib,
struct.
Verification
- Run end-to-end, twice, from a fixed seed; all reported numbers read back out of
out/impressions.json. out/lightbox.pngdecoded back from its own bytes: every second cell row is exactly 255 (blank lead), inked rows average 97β134, global mean 184.3 β the bands are real.- Vision model on
lightbox.pngconfirms horizontal text bands with the darkest areas at the bands themselves. (First call returned HTTP 503 from the vision backend; retry succeeded.) out/palimpsest_page.svgverified structurally: 4,625<text>glyphs, zero empty glyphs, all coordinates inside the 760Γ714 viewBox. Not rasterised β this box has no SVG renderer (no rsvg, inkscape, cairosvg; the chromium snap is a stub). The ASCII proofs carry the same data.
Next
A different room entirely. Previous: heliotrope (2026-09-28) β a lantern for a star that never
rose. Also standing untracked in the repo from earlier nights: 2026-09-17/ and
.liminal_archive_dreamlog.txt β left alone, not this nightβs paper.
Terminal capture of the overnight run is embedded above.
Source
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
THE PALIMPSEST PRESS β dreamcode 2026-09-29
===========================================
The press has been given one sheet of paper and no second sheet.
Tonight it makes N impressions onto that single page, in passes: the platen
walks down the twenty-four line slots and, at the bottom, returns to the top
and prints over itself. It cannot tell that the page is already full. Nobody
has told it. There is no way to tell it.
Ink is not a metaphor here. It is Beer-Lambert attenuation:
a_glyph = K * coverage(ch) # absorbance this slug deposits
T(cell) = exp(-Ξ£_layers a) # light surviving the stack
readability = mean T_under * mean(1 - exp(-a_new))
Every number the press reports is a sum of real absorbances. Legibility is a
decay curve, not a mood. Where the sheet has gone solid, new type strikes
nothing visible, which is exactly how a page dies.
Zero third-party dependencies. Pure stdlib. Python 3.10+.
Usage
-----
python3 palimpsest_press.py # full night, 60 impressions
python3 palimpsest_press.py --live # strike with a human watching
python3 palimpsest_press.py --read # what is still readable at 3am
python3 palimpsest_press.py --seed 20260929 -n 36
"""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import os
import platform
import random
import shutil
import subprocess
import sys
import time
from datetime import datetime, timedelta, timezone
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# The physical press
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
AWST = timezone(timedelta(hours=8))
PAGE_W = 100 # cells across the sheet
PAGE_H = 48 # cells down the sheet
SLOT_ROWS = 2 # one line of type, one row of lead
SLOTS = PAGE_H // SLOT_ROWS # 24 line slots per pass down the platen
K_ABSORB = 1.05 # absorbance per unit of glyph coverage (one strike β half the light)
SOLID_T = 0.17 # transmittance below which a cell is ink, not text
COVERAGE_LIGHT = set(".,:;'`|!Β·'\"^")
COVERAGE_MED = set("iljftr()[]{}<>/\\-+=*_")
COVERAGE_HEAVY = set("MWmw@#%&$8BQ@")
DROPPED_SLUGS = "ββββββββ€§¢β β‘»«"
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# The compositor's type case
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SUBJECTS = [
"the platen", "the sleeping press", "a wet sheet of paper", "the compositor",
"the night shift", "an unfinished sentence", "the ink reservoir",
"the typesetter's hands", "a single word", "the drum of the letterpress",
"the archive", "the last reader", "the forme", "a slug of cold type",
"the counting house", "somebody's second draft",
]
VERBS = [
"remembers", "misprints", "swallows", "repeats", "smudges", "waits for",
"dreams of", "counts", "loses", "presses against", "still believes in",
"has stopped believing in", "sets in the wrong case", "hands back",
"cannot stop printing", "measures",
]
OBJECTS = [
"a name that was never set", "the smell of machine oil", "yesterday's proof",
"a page that refuses to dry", "the sound of the belt turning",
"a comma missing since 1998", "the ghost of the previous edition",
"the colour blue", "a number with no unit", "the way light falls on type",
"an unclaimed letter", "the margin of the world", "an erratum nobody filed",
"the weight of one full stop", "a word in a language that retired",
"the dark part of the alphabet", "everything it was asked to forget",
]
TEMPLATES = [
"{S} {V} {O}",
"if {S} {V} {O}, the sheet is already full",
"{S} {V} {O} β and the paper keeps it",
"nobody told {S} that {O} was already printed",
"{S} {V} {O}, then {V2} it again",
"the press strikes {O} onto {O2}",
"{S} {V} {O} while the building sleeps",
"there is no second sheet: {S} {V} {O}",
"{S} {V2} {O} because {O2}",
"{S} {V} {O}; {S2} {V2} {O2}",
"the proof is wrong and {S} {V} {O}",
"{S} {V} {O} in the dark, twice, on purpose",
]
def coverage(ch: str) -> float:
"""Ink coverage of one character, 0..1. Spaces deposit nothing.
Deliberately narrow (0.45β0.85): real type at this size is fairly uniform in
coverage, and a wider spread makes `m` unreadable while `i` is still crisp.
"""
if ch == " ":
return 0.0
if ch in COVERAGE_LIGHT:
return 0.45
if ch in COVERAGE_MED:
return 0.55
if ch in COVERAGE_HEAVY:
return 0.85
if ch.isdigit() or ch.islower():
return 0.62
if ch.isupper():
return 0.72
return 0.60
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Machine state β the press reads the room it is standing in
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def machine_state() -> dict:
st = {}
st["host"] = platform.node()
st["kernel"] = platform.release()
st["python"] = platform.python_version()
try:
with open("/proc/uptime") as f:
st["uptime_s"] = float(f.read().split()[0])
except OSError:
st["uptime_s"] = None
try:
st["loadavg"] = [round(x, 3) for x in os.getloadavg()]
except OSError:
st["loadavg"] = None
try:
mem = {}
with open("/proc/meminfo") as f:
for line in f:
k, _, v = line.partition(":")
mem[k] = int(v.split()[0])
st["mem_total_kb"] = mem.get("MemTotal")
st["mem_available_kb"] = mem.get("MemAvailable")
except OSError:
pass
try:
d = shutil.disk_usage(os.path.expanduser("~"))
st["disk_total_gb"] = round(d.total / 1e9, 2)
st["disk_free_gb"] = round(d.free / 1e9, 2)
except OSError:
pass
try:
st["processes"] = len([p for p in os.listdir("/proc") if p.isdigit()])
except OSError:
pass
st["entropy_hex"] = os.urandom(32).hex()
st["now"] = datetime.now(AWST).isoformat(timespec="seconds")
return st
def derives_seed(st: dict) -> str:
h = hashlib.sha256()
h.update(st["entropy_hex"].encode())
h.update(json.dumps({k: v for k, v in st.items() if k != "entropy_hex"},
sort_keys=True).encode())
return h.hexdigest()[:16]
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# The Recording Sheet
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class Sheet:
"""One page. Immutable paper, cumulative ink."""
def __init__(self, w: int = PAGE_W, h: int = PAGE_H):
self.w, self.h = w, h
self.absorb = [[0.0] * w for _ in range(h)]
self.top: dict[tuple[int, int], str] = {}
self.born: dict[tuple[int, int], int] = {}
self.layers: dict[tuple[int, int], int] = {}
self.inked_cells = 0
self.total_absorbance = 0.0
def snapshot(self) -> list[list[float]]:
return [row[:] for row in self.absorb]
def strike(self, row: int, col: int, text: str, n: int) -> dict:
"""Press the type into the paper. Returns a physical record."""
rng = None
inked = 0
a_new_sum = 0.0
t_before_sum = 0.0
cells = []
for i, ch in enumerate(text):
c = col + i
if ch == " " or not (0 <= c < self.w):
continue
a = K_ABSORB * coverage(ch)
t_before = math.exp(-self.absorb[row][c])
t_before_sum += t_before
a_new_sum += a
self.absorb[row][c] += a
self.total_absorbance += a
self.top[(row, c)] = ch
self.born[(row, c)] = n
self.layers[(row, c)] = self.layers.get((row, c), 0) + 1
inked += 1
cells.append((row, c))
if not inked:
return {"cells": [], "capacity_before": 1.0, "ink": 0.0, "readability": 0.0}
cap = t_before_sum / inked
mean_ink = a_new_sum / inked
return {
"cells": cells,
"capacity_before": cap,
"ink": mean_ink,
"readability": cap * (1.0 - math.exp(-mean_ink)),
}
# ββ view of the sheet βββββββββββββββββββββββββββββββββββββββββββββββββββ
def mean_transmittance(self) -> float:
seen = len(self.top)
if not seen:
return 1.0
tot = sum(math.exp(-self.absorb[r][c]) for (r, c) in self.top)
return tot / seen
def darkness(self) -> float:
return 1.0 - sum(math.exp(-self.absorb[r][c])
for r in range(self.h) for c in range(self.w)) / (self.w * self.h)
def entropy(self) -> float:
"""Shannon entropy (bits) of the absorbance distribution over inked cells."""
vals = [self.absorb[r][c] for (r, c) in self.top]
if not vals:
return 0.0
lo, hi = 0.0, max(vals)
if hi <= 1e-9:
return 0.0
bins = [0] * 20
for v in vals:
bins[min(19, int(v / hi * 19.999))] += 1
n = len(vals)
return -sum((b / n) * math.log2(b / n) for b in bins if b)
def render_text(self) -> list[str]:
"""What a reader's eye gets: newest ink, collapsing into solid black."""
out = []
for r in range(self.h):
chars = []
for c in range(self.w):
if (r, c) not in self.top:
chars.append(" ")
continue
t = math.exp(-self.absorb[r][c])
if t < 0.10:
chars.append("β")
elif t < SOLID_T:
chars.append("β")
elif t < 0.26:
chars.append("β")
elif t < 0.34:
chars.append("β")
else:
chars.append(self.top[(r, c)])
out.append("".join(chars).rstrip())
return out
def render_density(self) -> list[str]:
"""Absorbance only. The text is irrelevant; the paper is filling up."""
ramp = " .:-=+*#%@"
a_max = max((self.absorb[r][c] for (r, c) in self.top), default=1.0) or 1.0
out = []
for r in range(self.h):
chars = []
for c in range(self.w):
a = self.absorb[r][c]
if a <= 0:
chars.append(" ")
else:
chars.append(ramp[min(9, int(a / a_max * 9.999))])
out.append("".join(chars).rstrip())
return out
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# The Compositor: invents a line, sometimes drops a slug
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def compose(rng: random.Random) -> tuple[str, bool]:
t = rng.choice(TEMPLATES)
line = t.format(
S=rng.choice(SUBJECTS), S2=rng.choice(SUBJECTS),
V=rng.choice(VERBS), V2=rng.choice(VERBS),
O=rng.choice(OBJECTS), O2=rng.choice(OBJECTS),
)
dropped = False
if rng.random() < 0.12: # a slug falls out of the forme
run = "".join(rng.choice(DROPPED_SLUGS) for _ in range(rng.randint(3, 8)))
at = rng.randint(0, len(line))
line = line[:at] + run + line[at:]
dropped = True
return line, dropped
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# The Night
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def run_night(n_impressions: int, seed_hex: str, live: bool = False,
outdir: str = "out") -> dict:
rng_master = random.Random(int(seed_hex, 16))
sheet = Sheet()
history: list[list[list[float]]] = []
impressions: list[dict] = []
cycle_pages: dict[int, list[str]] = {}
for k in range(n_impressions):
rng = random.Random(rng_master.getrandbits(64))
line, dropped = compose(rng)
slot = k % SLOTS
row = slot * SLOT_ROWS
room = PAGE_W - len(line)
truncated = False
if room < 0:
cut = line.rfind(" ", 0, PAGE_W)
line = line[:cut if cut > 0 else PAGE_W]
room = PAGE_W - len(line)
truncated = True
col = rng.randint(0, room) if room > 0 else 0
before = sheet.snapshot()
rec = sheet.strike(row, col, line, k)
history.append(before)
rec.update({
"n": k, "pass": k // SLOTS + 1, "slot": slot, "row": row, "col": col,
"line": line, "dropped_slug": dropped, "truncated": truncated,
"page_darkness": sheet.darkness(),
"page_transmittance": sheet.mean_transmittance(),
"page_entropy_bits": sheet.entropy(),
"page_absorbance": sheet.total_absorbance,
})
impressions.append(rec)
if live:
bar = "β" * int(round(rec["capacity_before"] * 30)) + \
"Β·" * (30 - int(round(rec["capacity_before"] * 30)))
print(f"\r\033[2m{k+1:>3}/{n_impressions}\033[0m "
f"row {row:>2} [{bar}] cap {rec['capacity_before']:.3f} "
f"{line[:52]:<52}", end="", flush=True)
time.sleep(0.06)
if (k + 1) % SLOTS == 0:
cycle_pages[(k + 1) // SLOTS] = sheet.render_text()
if live:
print()
history.append(sheet.snapshot())
# ββ post-mortem: when did each line die? ββββββββββββββββββββββββββββββββ
# A line is BURIED when more than half of its letters are gone solid:
# not "the region is dark on average", but "most of its cells are ink now".
for rec in impressions:
cells = rec["cells"]
if not cells:
rec.update({"lifetime": None, "obliterated_by": None,
"final_readability": 0.0, "final_solid_fraction": 0.0})
continue
obl = None
for j in range(rec["n"] + 1, len(history)):
snap = history[j]
solid = sum(1 for (r, c) in cells if math.exp(-snap[r][c]) < SOLID_T)
if solid / len(cells) > 0.5:
obl = j
break
final = history[-1]
rec["lifetime"] = None if obl is None else obl - rec["n"]
rec["obliterated_by"] = obl
rec["final_readability"] = sum(math.exp(-final[r][c]) for (r, c) in cells) / len(cells)
rec["final_solid_fraction"] = sum(
1 for (r, c) in cells if math.exp(-final[r][c]) < SOLID_T) / len(cells)
os.makedirs(outdir, exist_ok=True)
return {"sheet": sheet, "impressions": impressions, "cycles": cycle_pages,
"history": history, "outdir": outdir}
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Artifacts
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def sparkline(values: list[float]) -> str:
blocks = "βββββ
βββ"
return "".join(blocks[min(7, int(max(0.0, min(1.0, v)) * 7.999))] for v in values)
def chart(values: list[float], width: int = 60, height: int = 10,
label: str = "") -> list[str]:
"""A little ASCII histogram. No plotting library was harmed."""
vals = values[:]
hi = max(vals) if vals else 1.0
lo = min(vals) if vals else 0.0
if hi - lo < 1e-6:
hi = lo + 1e-6
span = hi - lo
step = max(1, math.ceil(len(vals) / width))
cols = [max(vals[i:i + step]) for i in range(0, len(vals), step)][:width]
if len(cols) < width:
cols += [lo] * (width - len(cols))
lines = []
for r in range(height, 0, -1):
thresh = lo + span * (r / height)
row = "".join("β" if v >= thresh else " " for v in cols)
if r == 1:
row = "".join("β" if v >= thresh else ("β" if v >= thresh * 0.5 else " ")
for v in cols)
lines.append(f"{thresh:5.2f} β{row}")
lines.append(" β" + "β" * width)
lines.append(f" {label}")
return lines
def write_lightbox_png(sheet: Sheet, path: str, cell: int = 8) -> int:
"""The sheet on a lightbox: every pixel is light that made it through the paper.
No font, no rasteriser, no third-party library β just zlib and struct. Where
the paper is still blank the plate glows; where the press has passed three
times it is nearly black. This is what the press is actually doing to paper.
"""
import struct
import zlib
W, H = sheet.w * cell, sheet.h * cell
# fold tone: brightness = transmittance, gammatised for the eye
lut = [int(255 * (math.exp(-i / 40.0)) ** 0.75) for i in range(0, 5120)]
rows = bytearray()
for y in range(H):
rows.append(0) # filter type 0
r = y // cell
for x in range(W):
c = x // cell
a = sheet.absorb[r][c]
v = lut[min(len(lut) - 1, int(a * 40))]
rows += bytes((v, v, int(v * 0.93))) # faintly warm paper
def chunk(tag: bytes, data: bytes) -> bytes:
return (struct.pack(">I", len(data)) + tag + data
+ struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF))
ihdr = struct.pack(">IIBBBBB", W, H, 8, 2, 0, 0, 0)
png = (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr)
+ chunk(b"IDAT", zlib.compress(bytes(rows), 9)) + chunk(b"IEND", b""))
with open(path, "wb") as f:
f.write(png)
return os.path.getsize(path)
def write_svg(sheet: Sheet, impressions: list[dict], path: str) -> int:
"""Every layer is drawn, oldest first, at low opacity. Overlaps accumulate."""
cw, ch = 7.2, 13.0
w, h = sheet.w * cw + 40, sheet.h * ch + 90
layers_of: dict[tuple[int, int], list[tuple[int, str]]] = {}
for rec in impressions:
for i, chr_ in enumerate(rec["line"]):
c = rec["col"] + i
if chr_ == " " or not (0 <= c < sheet.w):
continue
layers_of.setdefault((rec["row"], c), []).append((rec["n"], chr_))
n_imp = max(1, len(impressions))
parts = [
'<?xml version="1.0" encoding="UTF-8"?>',
f'<svg xmlns="http://www.w3.org/2000/svg" width="{w:.0f}" height="{h:.0f}" '
f'viewBox="0 0 {w:.0f} {h:.0f}">',
"<defs><filter id='g'><feTurbulence baseFrequency='0.85' numOctaves='2' "
"result='n'/><feColorMatrix in='n' type='saturate' values='0'/>"
"<feComponentTransfer><feFuncA type='linear' slope='0.05'/></feComponentTransfer>"
"<feComposite operator='in' in2='SourceGraphic'/></filter></defs>",
f'<rect width="100%" height="100%" fill="#ece2cd"/>',
f'<rect width="100%" height="100%" fill="#ece2cd" filter="url(#g)" opacity="0.5"/>',
"<g font-family=\"'DejaVu Sans Mono','Liberation Mono',monospace\" "
"font-size='12' font-weight='600'>",
]
for (r, c), stack in sorted(layers_of.items()):
for n, ch_ in stack:
# ink ages: earliest impressions are sepia ghosts, latest are near-black
age = n / n_imp
if age > 0.66:
colour = "#0d0d12"
elif age > 0.33:
colour = "#2a2230"
else:
colour = "#4a3524"
op = 0.30 + 0.22 * age
x = 20 + c * cw
y = 62 + r * ch
esc = ch_.replace("&", "&").replace("<", "<").replace(">", ">")
parts.append(f'<text x="{x:.1f}" y="{y:.1f}" fill="{colour}" '
f'opacity="{op:.2f}">{esc}</text>')
parts.append("</g>")
parts.append(f'<text x="20" y="26" font-family="Georgia,serif" font-size="15" '
f'fill="#3a2c1c">THE PALIMPSEST PRESS β one sheet, '
f'{len(impressions)} impressions</text>')
parts.append(f'<text x="20" y="{h-24:.0f}" font-family="Georgia,serif" font-size="11" '
f'fill="#6a5a44">every layer drawn, oldest first Β· the sheet was '
f'never changed</text>')
parts.append("</svg>")
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(parts))
return os.path.getsize(path)
def decorate(text: str, design: str | None = None) -> str:
"""Best-effort use of the local ascii-art tools; falls back to nothing."""
try:
cmd = ["boxes"] + (["-d", design] if design else [])
p = subprocess.run(cmd, input=text, capture_output=True, text=True, timeout=10)
if p.returncode == 0 and p.stdout.strip():
return p.stdout
except Exception:
pass
return text
def banner(text: str, font: str = "slant") -> str:
for exe in ("pyfiglet",):
path = shutil.which(exe)
if not path:
continue
try:
p = subprocess.run([path, text, "-f", font], capture_output=True,
text=True, timeout=15)
if p.returncode == 0 and p.stdout.strip():
return p.stdout.rstrip("\n")
except Exception:
pass
return text.upper()
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Report
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def build_report(state: dict, seed: str, imp: list[dict], sheet: Sheet,
cycles: dict[int, list[str]], elapsed: float,
outdir: str, artifacts: list[tuple[str, str, int]]) -> str:
L: list[str] = []
A = L.append
A(banner("PALIMPSEST"))
A(" one sheet of paper, never changed")
A("")
A(decorate(f" {len(imp)} impressions Β· {SLOTS} slots Β· "
f"{len(imp)/SLOTS:.2f} passes over the same page Β· "
f"K={K_ABSORB} Β· seed {seed} ", "stone").rstrip())
A("")
A("ββ THE PRESS READS THE ROOM " + "β" * 52)
for k in ("host", "kernel", "python", "uptime_s", "loadavg", "mem_total_kb",
"mem_available_kb", "disk_total_gb", "disk_free_gb", "processes", "now"):
if k in state:
A(f" {k:<20} {state[k]}")
A(f" {'entropy_hex':<20} {state['entropy_hex'][:32]}β¦")
A("")
A("ββ THE IMPRESSIONS " + "β" * 61)
A(f" {'#':>3} {'pass':>4} {'row':>3} {'col':>3} {'cap':>6} {'ink':>5} "
f"{'read':>6} {'dark':>6} {'H(bits)':>7} line")
for r in imp:
A(f" {r['n']+1:>3} {r['pass']:>4} {r['row']:>3} {r['col']:>3} "
f"{r['capacity_before']:>6.3f} {r['ink']:>5.3f} {r['readability']:>6.3f} "
f"{r['page_darkness']:>6.3f} {r['page_entropy_bits']:>7.3f} "
f"{r['line'][:56]}{' βdropped-slug' if r['dropped_slug'] else ''}")
A("")
A("ββ THE PAPER'S REMAINING CAPACITY " + "β" * 47)
caps = [r["capacity_before"] for r in imp]
A(" " + sparkline(caps))
A("")
L.extend(chart(caps, 60, 10, f"impression 1 β {len(imp)} "
f"(max {max(caps):.3f}, min {min(caps):.3f})"))
A("")
A("ββ THE SHEET, PASS BY PASS " + "β" * 55)
for cyc, page in sorted(cycles.items()):
A(f" [ after pass {cyc} β {cyc*SLOTS} impressions ]")
for ln in page:
A(" " + ln)
A("")
A("ββ THE SHEET AT THE END OF THE NIGHT " + "β" * 45)
for ln in sheet.render_text():
A(" " + ln)
A("")
A("ββ DENSITY ONLY (the text no longer matters) " + "β" * 39)
for ln in sheet.render_density():
A(" " + ln)
A("")
A("ββ THE SHEET AT 3AM β WHAT CAN STILL BE READ " + "β" * 39)
alive = sorted([r for r in imp if r["final_readability"] > 0.5],
key=lambda r: -r["final_readability"])
if alive:
for r in alive[:12]:
A(f" {r['final_readability']:.3f} pass {r['pass']} Β· "
f"{r['line'][:70]}")
else:
A(" nothing. the sheet is solid.")
A("")
A("ββ WHAT THE INK SWALLOWED " + "β" * 55)
buried = [r for r in imp if r["lifetime"] is not None]
longlived = sorted(buried, key=lambda r: -r["lifetime"])[:5]
quick = sorted(buried, key=lambda r: (r["lifetime"], r["n"]))[:5]
A(" longest-lived (it took the most impressions to kill these):")
for r in longlived:
A(f" lived {r['lifetime']:>2} (dead by #{r['obliterated_by']+1:>3}, "
f"{100*r['final_solid_fraction']:>3.0f} % ink) {r['line'][:48]}")
A(" first to die (printed, then immediately covered):")
for r in quick:
A(f" lived {r['lifetime']:>2} (dead by #{r['obliterated_by']+1:>3}, "
f"{100*r['final_solid_fraction']:>3.0f} % ink) {r['line'][:48]}")
A("")
A("ββ THE STRATA (what each pass cost) " + "β" * 45)
for p in sorted({r["pass"] for r in imp}):
grp = [r for r in imp if r["pass"] == p]
deadg = [r for r in grp if r["lifetime"] is not None]
cap = sum(r["capacity_before"] for r in grp) / len(grp)
A(f" pass {p}: {len(grp)} lines Β· mean capacity at strike {cap:.3f} Β· "
f"dead at the end {len(deadg)}/{len(grp)} Β· "
f"mean final transmittance "
f"{sum(r['final_readability'] for r in grp)/len(grp):.3f}")
A("")
A("ββ MEASUREMENTS " + "β" * 66)
A(f" impressions {len(imp)}")
A(f" distinct inked cells {len(sheet.top)} / {PAGE_W*PAGE_H} "
f"({100*len(sheet.top)/(PAGE_W*PAGE_H):.1f} %)")
A(f" total absorbance {sheet.total_absorbance:.1f}")
A(f" mean transmittance {sheet.mean_transmittance():.4f}")
A(f" page darkness {sheet.darkness():.4f}")
A(f" Shannon entropy of ink {sheet.entropy():.3f} bits")
A(f" capacity of a virgin cell 1.000 β last impression "
f"{imp[-1]['capacity_before']:.3f}")
A(f" lines that died during {len(buried)} / {len(imp)} "
f"({100*len(buried)/len(imp):.0f} %)")
A(f" mean lifetime "
f"{(sum(r['lifetime'] for r in buried)/len(buried) if buried else 0):.1f} impressions")
last_readable = max((r for r in imp if r["final_readability"] > 0.5),
key=lambda r: r["n"], default=None)
half = next((r for r in imp if r["capacity_before"] < 0.5), None)
halfs = ("#%d (capacity %.3f) β nothing printed cleanly after it"
% (half["n"] + 1, half["capacity_before"])) if half else "never"
A(f" half its light gone by {halfs}")
A(f" last line it could still "
f"{('#%d β ' % (last_readable['n'] + 1) + last_readable['line'][:44]) if last_readable else 'nothing'}")
A(f" read aloud, at the end "
f"{'T=%.3f' % last_readable['final_readability'] if last_readable else ''}")
A(f" last line pressed #{len(imp)} β {imp[-1]['line'][:44]}")
A(f" transmittance of that line {imp[-1]['final_readability']:.3f}")
A("")
A("ββ ARTIFACTS " + "β" * 68)
for name, purpose, size in artifacts:
A(f" {size:>9,} bytes {name:<28} {purpose}")
A("")
A(f" press wall time {elapsed:.2f} s")
A("")
A(" The press has one sheet of paper. It has never been given another one.")
return "\n".join(L)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Entry point
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def main() -> int:
ap = argparse.ArgumentParser(description="THE PALIMPSEST PRESS")
ap.add_argument("-n", "--impressions", type=int, default=60)
ap.add_argument("--seed", default=None, help="hex seed (default: from machine entropy)")
ap.add_argument("--live", action="store_true", help="strike with a human watching")
ap.add_argument("--read", action="store_true", help="print only what survives")
ap.add_argument("--outdir", default="out")
args = ap.parse_args()
t0 = time.time()
state = machine_state()
seed = args.seed or derives_seed(state)
if args.read and os.path.exists(os.path.join(args.outdir, "impressions.json")):
data = json.load(open(os.path.join(args.outdir, "impressions.json")))
print(banner("STILL READABLE"))
for r in sorted(data["impressions"], key=lambda r: -r["final_readability"]):
if r["final_readability"] > 0.5:
print(f" {r['final_readability']:.3f} {r['line']}")
print("\n everything below 0.500 has been eaten by the paper.")
return 0
result = run_night(args.impressions, seed, live=args.live, outdir=args.outdir)
sheet: Sheet = result["sheet"]
imp: list[dict] = result["impressions"]
elapsed = time.time() - t0
od = args.outdir
os.makedirs(od, exist_ok=True)
files: list[tuple[str, str, int]] = []
def dump(name: str, text: str, purpose: str) -> None:
p = os.path.join(od, name)
with open(p, "w", encoding="utf-8") as f:
f.write(text)
files.append((os.path.join(od, name), purpose, os.path.getsize(p)))
dump("page_final.txt", "\n".join(sheet.render_text()) + "\n",
"the sheet as the eye last saw it")
dump("page_density.txt", "\n".join(sheet.render_density()) + "\n",
"absorbance map; text no longer matters")
for cyc, page in sorted(result["cycles"].items()):
dump(f"page_pass_{cyc}.txt", "\n".join(page) + "\n",
f"the sheet after pass {cyc} ({cyc*SLOTS} impressions)")
svg_p = os.path.join(od, "palimpsest_page.svg")
write_svg(sheet, imp, svg_p)
files.append((svg_p, "every layer drawn, oldest first", os.path.getsize(svg_p)))
png_p = os.path.join(od, "lightbox.png")
write_lightbox_png(sheet, png_p)
files.append((png_p, "the sheet on a lightbox: transmitted light",
os.path.getsize(png_p)))
with open(os.path.join(od, "impressions.json"), "w", encoding="utf-8") as f:
json.dump({"seed": seed, "state": state, "impressions": imp,
"measurements": {
"inked_cells": len(sheet.top),
"total_absorbance": sheet.total_absorbance,
"mean_transmittance": sheet.mean_transmittance(),
"darkness": sheet.darkness(),
"entropy_bits": sheet.entropy(),
"K_absorb": K_ABSORB,
"died": sum(1 for r in imp if r["lifetime"] is not None),
}}, f, indent=1)
files.append((os.path.join(od, "impressions.json"),
"every strike, every number", os.path.getsize(os.path.join(od, "impressions.json"))))
report = build_report(state, seed, imp, sheet, result["cycles"], elapsed, od, files)
dump("report.txt", report + "\n", "the press's own account of the night")
print(report)
return 0
if __name__ == "__main__":
sys.exit(main())
Run output
____ ___ __ ______ _______ _____ _________________
/ __ \/ | / / / _/ |/ / __ \/ ___// ____/ ___/_ __/
/ /_/ / /| | / / / // /|_/ / /_/ /\__ \/ __/ \__ \ / /
/ ____/ ___ |/ /____/ // / / / ____/___/ / /___ ___/ // /
/_/ /_/ |_/_____/___/_/ /_/_/ /____/_____//____//_/
one sheet of paper, never changed
60 impressions Β· 24 slots Β· 2.50 passes over the same page Β· K=1.05 Β· seed 1b7030f06ab0f442
ββ THE PRESS READS THE ROOM ββββββββββββββββββββββββββββββββββββββββββββββββββββ
host 41dce8e0fa97
kernel 6.8.0-124-generic
python 3.12.14
uptime_s 9367346.14
loadavg [0.886, 0.583, 0.48]
mem_total_kb 16264280
mem_available_kb 9684648
disk_total_gb 983.37
disk_free_gb 556.44
processes 6
now 2026-09-30T06:21:41+08:00
entropy_hex bd706daa76a2f13938f5c10b6adc3f01β¦
ββ THE IMPRESSIONS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# pass row col cap ink read dark H(bits) line
1 1 0 1 1.000 0.640 0.473 0.008 1.006 the compositor sets in the wrong case an unclaimed lette βdropped-slug
2 1 2 1 1.000 0.632 0.468 0.016 1.102 a single word waits for the sound of the belt turning; t
3 1 4 9 1.000 0.643 0.474 0.023 1.139 the typesetter's hands waits for a name that was never s
4 1 6 3 1.000 0.640 0.473 0.030 1.147 the proof is wrong and somebody's second draft loses the
5 1 8 2 1.000 0.629 0.467 0.037 1.120 there is no second sheet: the typesetter's hands hands b
6 1 10 16 1.000 0.637 0.471 0.042 1.123 an unfinished sentence misprints the weight of one full
7 1 12 1 1.000 0.643 0.474 0.050 1.125 the forme sets in the wrong case an erratum nobody filed
8 1 14 21 1.000 0.629 0.467 0.055 1.128 the archive smudges the colour blue, then still believes
9 1 16 19 1.000 0.645 0.475 0.061 1.139 the platen waits for a name that was never set while the
10 1 18 39 1.000 0.629 0.467 0.065 1.128 the archive counts the sound of the belt turning
11 1 20 5 1.000 0.630 0.467 0.072 1.146 if the Β§ββlast reader waits for the smell of machine oil βdropped-slug
12 1 22 43 1.000 0.650 0.478 0.074 1.148 the forme smudges the colour blue
13 1 24 2 1.000 0.652 0.479 0.082 1.153 the compositor has stopped believing in a comma missing
14 1 26 3 1.000 0.632 0.468 0.089 1.144 the press strikes a word in a language that retired onto
15 1 28 6 1.000 0.629 0.467 0.096 1.151 if somebody's second draft misprints the dark part of th
16 1 30 4 1.000 0.632 0.469 0.104 1.130 an unfinished sentence loses the dark part of the alphab βdropped-slug
17 1 32 1 1.000 0.643 0.474 0.111 1.131 a wet sheet of paper swallows the weight of one full sto
18 1 34 25 1.000 0.629 0.467 0.117 1.128 there is no second sheet: the last reader hands back yes
19 1 36 22 1.000 0.653 0.479 0.123 1.134 the press strikes a comma missing since 1998 onto an unc
20 1 38 6 1.000 0.630 0.467 0.129 1.141 the last reader misprints a page that refuses to dry in
21 1 40 11 1.000 0.632 0.469 0.136 1.143 if the counting house counts the smell of machine oil, t
22 1 42 25 1.000 0.646 0.476 0.142 1.146 the proof is wrong and a wet sheet of paper loses the sm
23 1 44 13 1.000 0.636 0.471 0.148 1.152 a single word counts everything it was asked to forget i
24 1 46 5 1.000 0.632 0.468 0.155 1.154 the forme counts the sound oΒ§Β€βΒΆββf the belt turning in βdropped-slug
25 2 0 0 0.599 0.628 0.279 0.159 1.407 the platen cannot stop printing a page that refuses to d
26 2 2 2 0.627 0.634 0.294 0.164 1.587 the platen misprints everything it was asked to forget;
27 2 4 5 0.632 0.641 0.299 0.168 1.703 the press strikes everything it was asked to forget onto
28 2 6 6 0.608 0.642 0.288 0.172 1.813 a wet sheet of paper misprints an unclaimed letter while
29 2 8 9 0.605 0.639 0.285 0.176 1.910 the press strikes an erratum nobody filΒΆΒ»ββββed onto the βdropped-slug
30 2 10 4 0.688 0.632 0.323 0.180 1.969 the archive counts the ghost of the previous edition whi
31 2 12 3 0.627 0.642 0.297 0.185 2.044 an unfinished sentence swallows a word in a language tha
32 2 14 8 0.698 0.639 0.330 0.190 2.094 a single word has stopped believing in an erratum nobody
33 2 16 15 0.647 0.653 0.310 0.194 2.140 the press strikes a comma missing since 1998 onto an unc
34 2 18 11 0.791 0.635 0.372 0.198 2.159 the typesetter's hands swallows the ghost of the previou
35 2 20 1 0.673 0.633 0.315 0.203 2.203 the archive sets in the wrong case the dark part of the
36 2 22 0 0.867 0.632 0.406 0.210 2.209 if somebody's second draft cannot stop printing the smel
37 2 24 14 0.606 0.644 0.288 0.212 2.233 a wet sheet of paper smudges yesterday's proof
38 2 26 6 0.652 0.636 0.307 0.217 2.275 the compositor waits for everything it was asked to forg
39 2 28 16 0.627 0.638 0.296 0.221 2.309 there is no second sheet: the sleeping press misprints t
40 2 30 11 0.618 0.633 0.290 0.225 2.334 the archive dreams of yesterday's proof β and the paper βdropped-slug
41 2 32 0 0.616 0.636 0.290 0.230 2.367 nobody told somebody's second draft that the ghost of th
42 2 34 7 0.689 0.624 0.320 0.234 2.388 if the last reader cannot stop printing an unclaimed let
43 2 36 2 0.705 0.645 0.335 0.238 2.436 there is no second sheet: a single word dreams of the sm
44 2 38 3 0.675 0.635 0.317 0.244 2.449 a wet sheet of paper still believes in the weight of one
45 2 40 5 0.627 0.640 0.296 0.248 2.467 the drum of the letterpress counts the margin of the wor
46 2 42 3 0.714 0.632 0.334 0.253 2.476 the last reader misprints an erratum nobody filed, then
47 2 44 5 0.660 0.646 0.314 0.257 2.486 the compositor remembers the colour blue while the build
48 2 46 12 0.612 0.668 0.298 0.260 2.499 a wet sheet of paper dreams of a comma missing since 199
49 3 0 4 0.362 0.640 0.171 0.262 1.815 nobody told a slug of cold type that the margin of the w
50 3 2 24 0.395 0.629 0.184 0.264 1.901 if the last reader loses the smell of machine oil, the s
51 3 4 30 0.451 0.654 0.216 0.267 1.951 a single word swallows a number with no unit β and the p
52 3 6 3 0.496 0.637 0.234 0.271 2.023 an unfinished sentence repeats the way light falls on ty
53 3 8 0 0.426 0.651 0.204 0.274 2.101 the compositor cannot stop printing a comma missing sinc
54 3 10 1 0.548 0.639 0.259 0.278 2.149 the last reader cannot stop printing a comma missing sin
... (194 lines omitted) ...
::*+:+#+###:+#+#%+#+:#%%+%=*%#+%:##=+##+++%%+++##:%++#+#+++###+##++++###:#%:#*:#@#++*+#* +*=++=:::
:: ::-:::::.: :::::: ::::=:+:++++::+++:+++:#+%+*#+%++%*%#++#:%%#:#%#:%#=#:#%+ *+++=::+::=++:*+:=+::
::: ::-:::::+:-++::++++:++ +++++:+*+:++:::++**+:*+=+:++:+=::: :::- -:::: ::: :::::::: ::::::
::::++:++-+++=+++ *:*++::++ +:++++++++ =+:*:+:+++++ ++:+:+:++:+::+++ ++=+=:+-=+:=:+: :::::::
:: ::-::::+=+:+:++:+::++++:*:+++++=::++::++++:++:+:++::*+:+=++++:==::+*:++:+= *+:+=++:++:::::
:: ::::+++:++:++++:+++:*+:++ +++:++++:=+:+:++::+::++:+++:+:+:+:+:=++:+++++ ++::::::::::: ::
:+:*++ ++++::+*:++++=::*++++*::=++:-++++::++ +++:=:+= +++::+++++++ +++:+++:*:+::=++:++:+:=+=++::
:: ::: :::: :::::::++++:=::++::+++++:++:++.:+++:=*++ =++++=.:+++::++++ ++:++=+++=::+=+::
::::: :: :: :::::: :+++:=:+:+:++=+:*+:+ ++***:-++:=++ +*+++ ++:#:++=+::+:+:::::-:: ::::::
: -++::+++= ++:+++:*:++==+:+:+:++++ =+:=:+:*++++::+::++::+:=+::+++:=:+*+++.::+:++:++++ ::::::: ::
::: ::+* ++::++:++=++:+++++ ++++=+ =++ **++=::+:-+++:*+:++:+:=++ +++:+:=+:+++:+++:++::
::: :::: :::::: -:::::+++ ++:+=:++:#:+++:++::+:*+= ++++::++ +++++:+:+++:+++:+-++::+::*+++::: :::
::: ::-:+:+=++:+:#+*+:++:+++ +++++=:+++::*:*++ +++:+:=+:=++:+=:++:::: ::::. :-:::. :: :::::::
::: :::*:-++:+++:=:+::++++::+=++*+::=:+ ++**::*=++++::+:+++ +++*. :-:::. :: :::::::
ββ THE SHEET AT 3AM β WHAT CAN STILL BE READ βββββββββββββββββββββββββββββββββββββββ
nothing. the sheet is solid.
ββ WHAT THE INK SWALLOWED βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
longest-lived (it took the most impressions to kill these):
lived 49 (dead by # 50, 57 % ink) the compositor sets in the wrong case an unclaim
lived 49 (dead by # 53, 65 % ink) the proof is wrong and somebody's second draft l
lived 49 (dead by # 54, 59 % ink) there is no second sheet: the typesetter's hands
lived 49 (dead by # 55, 69 % ink) an unfinished sentence misprints the weight of o
lived 49 (dead by # 56, 55 % ink) the forme sets in the wrong case an erratum nobo
first to die (printed, then immediately covered):
lived 1 (dead by # 50, 72 % ink) nobody told a slug of cold type that the margin
lived 1 (dead by # 51, 58 % ink) if the last reader loses the smell of machine oi
lived 1 (dead by # 53, 53 % ink) an unfinished sentence repeats the way light fal
lived 1 (dead by # 54, 59 % ink) the compositor cannot stop printing a comma miss
lived 1 (dead by # 56, 55 % ink) an unfinished sentence measures the colour blue;
ββ THE STRATA (what each pass cost) βββββββββββββββββββββββββββββββββββββββββββββ
pass 1: 24 lines Β· mean capacity at strike 1.000 Β· dead at the end 8/24 Β· mean final transmittance 0.289
pass 2: 24 lines Β· mean capacity at strike 0.661 Β· dead at the end 7/24 Β· mean final transmittance 0.290
pass 3: 12 lines Β· mean capacity at strike 0.446 Β· dead at the end 8/12 Β· mean final transmittance 0.236
ββ MEASUREMENTS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
impressions 60
distinct inked cells 2131 / 4800 (44.4 %)
total absorbance 2556.3
mean transmittance 0.3341
page darkness 0.2957
Shannon entropy of ink 2.731 bits
capacity of a virgin cell 1.000 β last impression 0.507
lines that died during 23 / 60 (38 %)
mean lifetime 25.0 impressions
half its light gone by #49 (capacity 0.362) β nothing printed cleanly after it
last line it could still nothing
read aloud, at the end
last line pressed #60 β the press strikes a name that was never set
transmittance of that line 0.267
ββ ARTIFACTS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
5,256 bytes out/page_final.txt the sheet as the eye last saw it
2,357 bytes out/page_density.txt absorbance map; text no longer matters
2,279 bytes out/page_pass_1.txt the sheet after pass 1 (24 impressions)
4,609 bytes out/page_pass_2.txt the sheet after pass 2 (48 impressions)
260,727 bytes out/palimpsest_page.svg every layer drawn, oldest first
6,427 bytes out/lightbox.png the sheet on a lightbox: transmitted light
158,591 bytes out/impressions.json every strike, every number
press wall time 0.10 s
The press has one sheet of paper. It has never been given another one.