Reader

Blog

Welcome to the Blog.

Mode 13h in Turbo Pascal

Graphics programming without illusions

Turbo Pascal graphics programming is one of the cleanest ways to learn what a frame actually is. In modern stacks, rendering often passes through layers that hide timing, memory layout, and write costs. In DOS Mode 13h, almost nothing is hidden. You get 320x200, 256 colors, and a linear framebuffer at segment $A000. Every pixel you draw is your responsibility.

Mode 13h became a favorite because it removed complexity that earlier VGA modes imposed. No planar bit operations, no complicated bank switching for this resolution, and no mystery about where bytes go. Pixel (x, y) maps to offset y * 320 + x. That directness made it ideal for demos, games, and educational experiments. It rewarded people who could reason about memory as geometry.

A minimal setup in Turbo Pascal is refreshingly explicit: switch video mode via BIOS interrupt, get access to VGA memory, write bytes, wait for input, restore text mode. There is no rendering engine to configure. You control lifecycle directly. That means you also own failure states. Forget to restore mode and you leave the user in graphics. Corrupt memory and artifacts appear instantly.

Early experiments usually start with single-pixel writes and quickly hit performance limits. Calling a procedure per pixel is expressive but expensive. The first optimization lesson is batching and locality: draw contiguous spans, avoid repeated multiplies, precompute line offsets, and minimize branch-heavy inner loops. Mode 13h teaches a truth that still holds in GPU-heavy times: throughput loves predictable memory access.

Palette control is another powerful concept students often miss today. In 256-color mode, pixel values are indices, not direct RGB triples. By writing DAC registers, you can change global color mappings without touching framebuffer bytes. This enables palette cycling, day-night transitions, and cheap animation effects that look far richer than their computational cost. You are effectively animating interpretation, not data. ... continue

Maintenance Is a Creative Act

In software culture, novelty gets applause and maintenance gets scheduling leftovers. We celebrate launches, rewrites, and shiny architecture diagrams. We quietly postpone dependency cleanup, operational hardening, naming consistency, test stability, and documentation repair. Then we wonder why velocity decays.

This framing is wrong. Maintenance is not the opposite of creativity. Maintenance is applied creativity under constraints.

Creating something new from a blank page is one creative mode. Improving a living system without breaking commitments is another, often harder, mode. It demands understanding history, preserving intent, and evolving design with minimal collateral damage.

Good maintenance starts with respect for continuity. Existing systems encode decisions that may no longer be obvious but still matter. Some are outdated and should change. Some are hard-earned safeguards that protect production behavior. The maintainer’s job is to tell the difference.

That requires curiosity, not cynicism. “This code is ugly” is easy. “Why did this shape emerge, and what risks does it currently absorb?” is useful. ... continue

Latency Budgeting on Old Machines

When constrained hardware makes wait time a design variable

One gift of old machines is that they make latency visible. You do not need an observability platform to notice when an operation takes too long; your hands tell you immediately. Keyboard echo lags. Menu redraw stutters. Disk access interrupts flow. On constrained hardware, latency is not hidden behind animation. It is a first-class design variable.

Most retro users developed latency budgets without naming them that way. They did not begin with dashboards. They began with tolerance thresholds: if opening a directory takes longer than a second, it feels broken; if screen updates exceed a certain rhythm, confidence drops; if save operations block too long, people fear data loss. This was experiential ergonomics, built from repeated friction.

A practical budget often split work into classes. Input responsiveness had the strictest target. Visual feedback came second. Heavy background operations came third, but only if they could communicate progress honestly. Even simple tools benefited from this hierarchy. A file manager that reacts instantly to keys but defers expensive sorting feels usable. One that blocks on every key feels hostile.

Because CPUs and memory were limited, achieving these budgets required architectural choices, not just micro-optimizations. You cached directory metadata. You precomputed static UI regions. You used incremental redraw instead of repainting everything. You chose algorithms with predictable worst-case behavior over theoretically elegant options with pathological spikes. The goal was not maximum benchmark score; it was consistent interaction quality.

Disk I/O dominated many workloads, so scheduling mattered. Batching writes reduced seek churn. Sequential reads were preferred whenever possible. Temporary file design became a latency decision: poor temp strategy could double user-visible wait time. Even naming conventions influenced performance because directory traversal cost was real and structure affected lookup behavior on older filesystems. ... continue

IRQ Maps and the Politics of Slots

Negotiating IRQ, DMA, and I/O on DOS-era PCs

Anyone who built or maintained DOS-era PCs remembers that hardware conflicts were not rare edge cases; they were normal engineering terrain. IRQ lines, DMA channels, and I/O addresses had to be negotiated manually, and each new card could destabilize a previously stable system. This was less like plug-and-play and more like coalition politics in a fragile parliament.

The core constraint was scarcity. Popular sound cards wanted IRQ 5 or 7. Network cards often preferred 10 or 11 on later boards but collided with other devices on mixed systems. Serial ports claimed fixed ranges by convention. Printer ports occupied addresses and IRQs that software still expected. These were not abstract settings. They were finite shared resources, and two devices claiming the same line could produce failures that looked random until you mapped the whole system.

That mapping step separated casual tinkering from reliable operation. Good builders kept a notebook: slot position, card model, jumper settings, base address, IRQ, DMA low/high, BIOS toggles, and driver load order. Without this, every change became archaeology. With it, you could reason about conflicts before booting and recover quickly after experiments.

Slot placement itself mattered more than many people remember. Motherboards often wired specific slots to shared interrupt paths or delivered different electrical behavior under load. Moving a card one slot over could stabilize an entire system. This felt superstitious until you understood board traces, chipset quirks, and timing sensitivities. “Try another slot” was not a meme; it was an informed diagnostic move.

Software configuration had to align with hardware reality. A sound card set to IRQ 5 physically but configured as IRQ 7 in a game setup utility produced symptoms that were confusing but consistent: missing effects, lockups during sample playback, or intermittent crackle. The fix was not mystical. It was alignment across all layers: jumper, driver, environment variable, and application profile. ... continue

Interrupts as User Interface

INT 10h, 16h, and 21h as DOS-era interaction surfaces

In modern systems, user interface usually means windows, widgets, and event loops. In classic DOS environments, the interface boundary often looked very different: software interrupts. INT calls were not only low-level plumbing; they were stable contracts that programs used as operating surfaces for display, input, disk services, time, and devices.

Thinking about interrupts as a user interface reveals why DOS programming felt both constrained and elegant. You were not calling giant frameworks. You were speaking a compact protocol: registers in, registers out, carry flag for status, documented side effects.

Take INT 21h, the core DOS service API. It offered file IO, process management, memory functions, and console interaction. A text tool could feel interactive and polished while relying entirely on these calls and a handful of conventions. The interface was narrow but predictable.

INT 10h for video and INT 16h for keyboard provided another layer. Combined, they formed a practical interaction stack:

That is a full UI model, just encoded in BIOS and DOS vectors instead of GUI widget trees. ... continue

Incident Response with a Notebook

A timestamped timeline when dashboards lag

Modern incident response tooling is powerful, but under pressure, people still fail in very analog ways: they lose sequence, they forget assumptions, they repeat commands without recording output, and they argue from memory instead of evidence. A simple notebook, used with discipline, prevents all four.

This is not anti-automation advice. It is operator reliability advice. When systems are failing fast and dashboards are lagging, your most valuable artifact is a timeline you can trust.

I keep a strict notebook format for incidents:

That structure sounds verbose until minute twenty, when context fragmentation starts. By minute forty, it is the difference between controlled recovery and expensive chaos.

The “expected result” field is especially important. Teams often run commands reactively, then treat any output as signal. That is backwards. State your hypothesis first, then test it. If expected and actual differ, you learn something real. If you skip expectation, every log line becomes confirmation bias. ... continue

Ground Is a Design Interface

Many circuit failures are not caused by “bad signals.” They are caused by bad assumptions about ground. Designers often treat ground as a neutral reference that exists automatically once a symbol is placed. In reality, ground is a physical network with resistance, inductance, and shared current paths. If we ignore that, measurements lie, interfaces become unstable, and debugging turns into superstition.

The mental shift is simple but profound: ground is not the absence of design. Ground is part of the design interface. Every subsystem communicates through it, injects noise into it, and depends on its stability. Once you frame ground this way, layout and topology decisions stop feeling cosmetic and start feeling architectural.

A common early mistake is routing sensitive analog return currents through the same narrow paths used by switching loads. The board may pass basic tests, then fail under realistic activity when motor drivers, DC-DC converters, or digital bursts modulate the local reference. The symptom appears as random ADC jitter or intermittent threshold misfires. The root cause is shared impedance, not firmware.

Star-ground strategies can help in some low-frequency or mixed-signal contexts, but they are often misapplied as a universal rule. Solid planes usually win for modern digital work because they minimize return path impedance and give high-frequency currents predictable local loops under signal traces. The key is intentional current-path thinking, not slogan-driven layout.

Measurement technique also determines whether you see truth or artifacts. Using long oscilloscope ground clips on fast edges can invent ringing that is mostly probe loop inductance. Engineers then “fix” a problem that exists in the measurement setup. Short ground springs, proper probe compensation, and awareness of reference path are not optional details; they are prerequisites for trustworthy diagnosis. ... continue

Giant Log Lenses

Layout stress test for wide CLI output and log lines

When dashboards hide detail, I still go back to raw logs and text-first tools.
This short note is intentionally built as a rendering stress test: some code lines are much wider than the article window to verify horizontal scrolling behavior. The examples are realistic enough to copy, but the primary goal is visual QA for long literals, long command chains, and dense tabular output.

1-liner (intentionally very long)

1
rg --no-heading --line-number --color=never "timeout|connection reset|tls handshake|upstream prematurely closed" ./logs/production/edge/*.log | jq -R 'split(":") | {file:.[0], line:(.[1]|tonumber), message:(.[2:]|join(":"))}' | awk 'BEGIN{FS="|"} {printf "%-42s | L%-6s | %s\n",$1,$2,$3}' | sort -k1,1 -k2,2n

2-liner (wide structured print)

1
2
rows=[{"ts":"2026-02-22T04:31:55Z","service":"api-gateway-eu-central-1-prod-blue","endpoint":"/v1/orders/checkout/recalculate-shipping-and-tax","latency_ms":912,"trace":"9f58b69b2d7d4a21a3f17d5e4f7a0112"}]
print("\n".join(f"{r['ts']} | {r['service']:<36} | {r['latency_ms']:>4}ms | {r['endpoint']} | trace={r['trace']}" for r in rows))

4-liner (wide payload path)

1
2
3
4
const payload = {tenant:"northwind-enterprise-platform",env:"production-eu-central-1",featureFlags:["long-session-replay-streaming","websocket-fallback-polling","incremental-checkpoint-serializer-v2"],meta:{requestId:"4b1d3be8fd7e4ad6a9f8c71e2bbf9a44",userAgent:"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36"}};
const digest = btoa(JSON.stringify(payload)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");
const url = `https://collector.example.internal/v2/telemetry/ingest/really/long/path/that/keeps/going?tenant=${payload.tenant}&env=${payload.env}&digest=${digest}`;
fetch(url,{method:"POST",headers:{"content-type":"application/json","x-trace-id":"4b1d3be8fd7e4ad6a9f8c71e2bbf9a44"},body:JSON.stringify(payload)});

Wide table sample

If this article behaves correctly, code blocks and tables stay on one logical line and can be scrolled horizontally without breaking the text grid style.

Related reading:

Fuzzing to Exploitability

Triage, minimization, and harness quality between crash and finding

Fuzzing finds crashes quickly. Turning crashes into reliable security findings is slower, less glamorous work. Many teams stall in the gap between “it crashed” and “this is exploitable under defined conditions.” Bridging that gap requires discipline in triage, reduction, root-cause analysis, and harness quality. Without this discipline, fuzzing campaigns generate noise instead of security value.

The first mistake is overvaluing raw crash counts. Hundreds of unique stack traces can still map to a handful of root causes. Counting crashes as progress creates perverse incentives: bigger corpus churn, less deduplication, shallow analysis. Useful metrics are different: number of distinct root causes, percentage with minimized reproducers, time to fix confirmation, and recurrence rate after patches.

Crash triage begins with deterministic reproduction. If you cannot replay reliably, you cannot reason reliably. Save exact binaries, runtime flags, environment variables, and input artifacts. Capture hashes of test executables. Tiny environmental drift can turn a real vulnerability into a ghost. Reproducibility is not bureaucracy; it is scientific control.

Input minimization is the next force multiplier. Large fuzz artifacts obscure causality and slow debugger cycles. Use minimizers aggressively to isolate the smallest trigger that preserves behavior. A minimized artifact clarifies parser states, boundary transitions, and corruption points. It also produces cleaner reports and faster regression tests.

Sanitizers provide critical signal, but they are not the end of analysis. AddressSanitizer might report a heap overflow; you still need to determine reachable control influence, overwrite constraints, and realistic attacker preconditions. UndefinedBehaviorSanitizer may flag dangerous operations that are currently non-exploitable yet indicate brittle code likely to fail differently under compiler or platform changes. Triage should classify both immediate risk and latent risk. ... continue

Exploit Reliability over Cleverness

Exploit writeups often reward elegance: shortest payload, sharpest primitive chain, most surprising bypass. In real engagements, the winning attribute is usually reliability. A moderately clever exploit that works repeatedly beats a brilliant exploit that succeeds once and fails under slight environmental variation.

Reliability is engineering, not luck.

The first step is to define what reliable means for your context:

If reliability is not measured, it is mostly imagined.

A practical reliability-first workflow: ... continue