## Phosphor buffer with CRT physics: persistence decay, beam bloom, ## and half-block rendering with intensity-based shading. import illwill, math const PhosphorDecay* = 0.60 # per-frame persistence (P31 green phosphor) BeamIntensity* = 0.7 # brightness at beam impact BloomH* = 0.15 # horizontal glow spread MinBright* = 0.02 # below this, phosphor is considered off type Intensity* = enum iHot ## beam core — white-hot iBright ## fluoro green phosphor iMedium ## green glow iDim ## faint persistence trail PhosphorBuffer* = object w*, h*: int # terminal columns/rows pixH*: int # pixel height (2× rows via half-blocks) data*: seq[float] # brightness per pixel [w × pixH] proc initPhosphor*(w, h: int): PhosphorBuffer = let pixH = h * 2 PhosphorBuffer(w: w, h: h, pixH: pixH, data: newSeq[float](w * pixH)) proc idx(pb: PhosphorBuffer, x, y: int): int {.inline.} = y * pb.w + x proc add(pb: var PhosphorBuffer, x, y: int, intensity: float) {.inline.} = if x >= 0 and x < pb.w and y >= 0 and y < pb.pixH: pb.data[pb.idx(x, y)] = min(pb.data[pb.idx(x, y)] + intensity, 1.0) proc decay*(pb: var PhosphorBuffer) = for i in 0..= pb.w or y < 0 or y >= pb.pixH: return # Beam impact — no vertical bloom, keeps traces thin pb.add(x, y, BeamIntensity) pb.add(x - 1, y, BloomH) pb.add(x + 1, y, BloomH) proc plotLine*(pb: var PhosphorBuffer, x0, y0, x1, y1: float) = ## Interpolated line of phosphor dots between two points. let steps = max(int(max(abs(x1 - x0), abs(y1 - y0))), 1) for i in 0..steps: let t = i.float / steps.float pb.plotDot(x0 + (x1 - x0) * t, y0 + (y1 - y0) * t) # ── Half-block rendering ──────────────────────────────────────────── proc toIntensity*(b: float): Intensity = if b > 0.7: iHot elif b > 0.4: iBright elif b > 0.15: iMedium else: iDim proc writePhosphor*(tb: var TerminalBuffer, x, y: int, ch: string, intensity: Intensity) = case intensity of iHot: tb.write(x, y, fgWhite, styleBright, ch) of iBright: tb.write(x, y, fgGreen, styleBright, ch) of iMedium: tb.write(x, y, fgGreen, ch) of iDim: tb.write(x, y, fgGreen, styleDim, ch) proc render*(pb: PhosphorBuffer, tb: var TerminalBuffer) = ## Blit the phosphor buffer to the terminal using half-block characters. for ty in 0.. MinBright or botB > MinBright: let tOn = topB > MinBright let bOn = botB > MinBright if tOn and bOn: tb.writePhosphor(x, ty, "█", toIntensity(max(topB, botB))) elif tOn: tb.writePhosphor(x, ty, "▀", toIntensity(topB)) else: tb.writePhosphor(x, ty, "▄", toIntensity(botB))