Index  /  Programming  ·  Sheet 7 of 11

Programming

VB.NET · VBA · Ladder · PASM · Tooling

GW-BASIC on a TI in 1986. VB.NET today. I am aware of how that sounds — four decades and I never once left the family. The tools ship, they work, and they are still maintainable, which is more than I can say for some things written in more fashionable languages.

Snake, 1986

The first real program I had anything to do with, I did not write. I typed it — out of a magazine, the way you did then. Over two thousand lines of BASIC, three months of evenings, character by character.

Then I ran it. It failed. And I could not find out why.

Two thousand lines, no idea which one was wrong, and nothing anywhere in it that said this part, at least, is correct. There was no way in. So I threw it out and started again from the first line — typing a stretch, running it, checking my work, and only then typing the next stretch. It took longer and it worked.

What that actually taught me

A system you cannot bisect is a system you cannot debug. Two thousand unverified lines is not a program with a bug in it — it is a wall. Every principle further down this page, and most of the ones on the AI sheet, are a fourteen-year-old's workaround that turned out to be the entire method: build a piece, prove it, then build the next piece against something you already trust.

What I build

Almost everything I write exists because I did a job twice and objected. Not products — tools. A viewer for ladder logic so it can be read outside the vendor's IDE. A hub that loads games as plug-ins. A memory system so an AI stops starting every conversation from nothing.

The common thread is a contract. Get the interface right and the things behind it become cheap to add. Get it wrong and every addition is a negotiation with the previous one.

HOST APPLICATION knows the interface only IGame — the contract ROULETTE DUNGEON CRAWL NOT WRITTEN YET

The test of a plug-in contract is brutally simple: if adding a new one takes more than one file and one registration, the contract is wrong. Not inconvenient. Wrong. Go back and fix the interface, because every future addition pays that tax again.

One long thread

BASIC to VB6 to VBScript to VB.NET is not four languages, it is one that kept growing. Every step added something without demanding I throw away what already worked — which is the same bargain I now insist on from architecture: no rewrites, every phase builds to the next.

I did not adopt that principle from a book. I lived it for forty years before I had a name for it, and I would be suspicious of anyone who arrived at it the other way round.

GameRoom — the contract, proved

That diagram is a claim. This is the evidence for it: one lobby, six tiles, and every one of them a plug-in the host knows nothing about beyond the interface.

Game Room lobby with tiles for Roulette, Blackjack, Othello, Minesweeper, Dungeon Crawl and Player Stats
01The host's entire view of the world. It can list them and launch them. It cannot tell you what any of them do.

The reason this is a real test rather than a flattering one is how little the six have in common. A rendered wheel with physics and a bet layout. A card game with a shoe and a dealer that has rules. A board game with an opponent that has to think. A grid puzzle with chording. A stat page that reads history instead of playing anything. And a full AD&D crawler with turns, light radius and an inventory.

If one interface holds all of that, the interface is the right shape. If it had needed a special case for any one of them, it would have been wrong and every future game would have paid for it.

Game Room roulette table with wheel, betting layout and session statistics
02American wheel, both zeros. Session stats and hot numbers underneath — because the interesting part of roulette is watching a fair wheel refuse to look fair.
Game Room blackjack hand showing dealer 17 against player 18
03Six-deck shoe, blackjack pays 3:2, dealer stands on all 17s. The table rules are stated on screen because a blackjack game that hides its rules is doing something else.
Game Room Othello board mid-game against the AI
04Reversi with a selectable AI and legal moves hinted on the board. Teaching the rules through the interface beats explaining them.
Game Room Minesweeper on expert difficulty
05Expert 30×16, proper number colors, flags and chording. Getting the details right on the one everybody has played is harder than inventing something new.
Dungeon Crawl

The largest plug-in, and the one that stopped being a game and became a systems problem. AD&D 2nd edition rules — THAC0 and all — with light radius burning down in real minutes, encounter distance in feet, and surprise that actually matters.

Dungeon Crawl encounter screen with combat variables and monster knowledge panel
06Two wild boars at 70 feet, downwind and unaware. Every number the decision depends on is on screen — range, state, light remaining, what a hunter's shot is worth. No hidden modifiers.
The bit I'm proudest of

Look at the monster panel. AC and THAC0 are filled in; hit dice, damage, speed and XP are question marks. You only know what you have earned — the entry fills in as you fight them. The bestiary is the character's, not the player's, and it means the first of anything is genuinely dangerous.

AD&D 2nd edition character sheet showing abilities, saving throws, combat values and inventory
07The character sheet, and the depth of the rules underneath it. Open doors 4-in-20. Bend bars 0%. System shock 85%. Learn spell 35%. Henchmen 7, loyalty +3, reaction +3. Those are the real 2e derived tables, not a simplification that keeps the flavor and drops the arithmetic.
ASCII town map of Millbrook with named shops and the player character
08Millbrook. Thorn & Daughter, Provisioners. Harrow's Plate & Mail. The Wandering Boar. Chapel of the Kindly Light. Gorse the Butcher. Doors you stand on and press E.

Three details in those two screens say more about how I build than anything I could claim.

Encumbrance is honest. The purse reads 99 pp, 9 sp, 6 cp (2.3 lb) — the coins have weight, because in 2e they do. Light is tracked in real minutes remaining, not in torches. If you're going to implement a rules system, implement the parts that were load-bearing.

The prose does the work the graphics can't. "You pass under the gate of Millbrook. Woodsmoke, wet stone, and somebody shouting about turnips." That is the entire art budget of an ASCII game, spent well.

And it tells you what isn't finished. The class panel reads "Spells arrive with the Phase 6 mining." There's a Kill Character (test) button still sitting on the sheet. The build phases are visible from inside the game — which is either a lack of polish or the most honest possible status report, and I'd argue it's the second one right up until someone else has to play it.

Rick's Life — an automaton that watches itself

A second cellular automaton, and a considerably stranger one. The simulation is the easy half; the tabs across the top are the actual project — Universe, Observer, Predictions, Narratives.

Cellular automaton engine showing a seeded pattern
09A seeded pattern running. 39 cells, and nothing yet to say about them.
The observer bounding an unrecognized structure and labeling it Unknown
10The observer, working. It has bounded a structure it does not recognize and labeled it Unknown (1 Unique) — not identifying a known thing, but noticing that something coherent is there and admitting it has no name for it yet.

That distinction is the whole reason it exists. A classifier that only recognizes gliders and blinkers tells you what you already programmed. One that can say "there is a thing here and I don't know what it is" can find something you didn't anticipate — which is the only interesting outcome in a system whose behavior you can't predict from the rules.

And it's the third time on this sheet: the DSP processor watching its own state transitions, the memory substrate on the AI page watching what recurs across sessions, and now an automaton naming its own emergent structures. I did not set out to build the same thing three times. Apparently it's just what I find interesting.

The Mandelbrot set rendered in blue and black
11The Mandelbrot set — the original argument that a rule two lines long can contain more structure than anyone could design deliberately. Everything on this part of the sheet is a footnote to it.
Forge Color-Life Engine
Forge Color-Life Engine rendering a cellular automaton field with parameter controls
12A color cellular automaton — majority weighting, drift strength and polarity, selectable neighborhood, six palettes. Simple local rules, no global plan, and it still produces coastlines.

Every parameter is exposed and live, which is the only honest way to build something whose behavior you cannot predict from the rules. You are not designing the output. You are designing the conditions and then finding out.

Tools that build tools

Every one of these started as a job I did twice and objected to. None of them are products. They exist because the alternative was doing the tedious part again.

Visual directory structure editor with an Engineering template
13Directory Template Builder. Job folder structures are a standard that only helps if applying it is easier than not applying it — so the standard became a button.
Database generator with a templates menu of common SQL operations
14The same app's database side — canned SQL for the operations I kept retyping. Joins, group-bys, index and constraint work, straight off a menu.
SQLAccess SQL tab with schema tree and statement execution log
15SQLAccess. Opens read-only by default, shows the schema as a tree with live row counts, and logs every statement with its outcome — including the failures, which are the ones worth keeping.
A small thing I'm pleased with

SQLAccess opens read-only and you have to deliberately leave that state. The default protects you; the capability is one click away. That's the same instinct as a maintained-contact selector switch — safe unless someone means it.

DSP Processor

The largest of them, and the one where the two halves of this site meet. Real-time audio capture and processing, written to scratch the electronics itch with software instead of solder.

DSP Processor spectrum view comparing input and output
16Pre-DSP and post-DSP spectra side by side. Showing the input next to the output is the whole design principle — you can't tune what you can't compare.
DSP signal flow with filter stages and metering controls
17Signal flow. Gain, pan, high-pass, low-pass, output mixer — each stage labeled with what it's actually for, because "removes DC, rumble, stage noise" is more useful to a future me than "HPF".
DSP Processor waveform display with a list of recorded takes
18Waveform and take management. Loop recording runs unattended for a hundred takes, which is a lot of audio to generate and exactly the point.

Then there's the tab I did not expect to build. Brains — a cognitive layer watching the application: state machine transitions as they happen, a habit-loop report looking for sequences that repeat often enough to count, and an attention spotlight tracking where activity actually went.

DSP Processor Brains tab showing state transitions, habit loop analysis and attention reporting
19An audio tool observing its own behavior. Working memory of recent state transitions, patterns that recur, and where attention has been — currently reporting honestly that it has seen nothing yet.

Which is the same idea as the memory substrate on the AI sheet, arrived at from the opposite direction. There I wanted a machine to stop forgetting across sessions; here I wanted an application to notice what it kept doing. Both are the instinct that a system which cannot observe itself cannot be improved deliberately — you're left changing things and hoping.

I don't think I noticed they were the same idea until I built the second one.

Bit-banging I²C on a Propeller

Sometimes the right move is to write the protocol yourself. This is a hand-rolled I²C master in Propeller assembly, driving an eight-channel device — address byte, then eight register-and-data pairs, every clock edge and every data transition placed by hand.

No peripheral, no library. Two pins, a counter, and the bus specification.

' start condition, then clock out one byte, MSB first

starti2c    waitcnt time,     off_time
            xor     OUTA,     i2cSDA        ' data low while clock high
            waitcnt time,     off_time
            xor     OUTA,     i2cSDL

AddressB    mov     OutHolder,address
loop        shl     OutHolder,#1            ' walk the bit up to position 9
            test    OutHolder,#%1_0000_0000 wc
            muxc    OUTA,     i2cSDA        ' pin follows carry — one instruction
clkloop1    waitcnt time,     off_time
            xor     OUTA,     i2cSDL        ' toggle clock
            xor     CkState,  #1
            waitcnt time,     off_time
            cmp     CkState,  #1      wz  ' data may only change while clock is low
    if_z    jmp     #clkloop1
            djnz    idx,      #loop         ' repeat for each bit in the byte

The test / muxc pair is the nice bit. test drops the selected bit straight into the carry flag, then muxc writes carry onto the pin — one instruction, no branch, no temporary. Shifting the bit up to a fixed position rather than shifting the mask down means the test operand is a constant, which keeps it a single long.

The whole driver is unrolled — seventeen near-identical blocks rather than a loop over a pointer. That reads as repetition and on most platforms it would be. A cog has 496 longs of code space and no stack, so a call costs you self-modifying code; when the routine fits, unrolling buys deterministic timing for space you weren't otherwise going to use.

Why a Propeller feels familiar
HUB RAM shared · round-robin COG 0COG 1 COG 2COG 3 COG 4COG 5 COG 6I2C no interrupts · no scheduler · no jitter

Eight cores, each with its own memory and its own copy of the code, sharing a hub in strict round-robin. There are no interrupts. If you need something to happen concurrently you don't schedule it, you hand it a core.

Which is why this architecture reads as obvious to anyone who came up on PLCs. A PLC guarantees you a deterministic scan; a cog guarantees you deterministic instruction timing, and waitcnt lets you place an edge to the clock cycle. Both are answering the same question — when exactly does this happen? — and both answer it by refusing to let anything preempt you.

It is the same instinct that says a machine which stops randomly is not random. Determinism isn't a performance feature. It's what makes the thing debuggable.

Rules I actually follow
# the ones that survived contact with production

no rewrites        every phase builds to the next. if the next stage
                  requires tearing out the last one, the last one
                  had the wrong shape.

explicit > implicit  verbosity is not a defect. the thing you left
                  unsaid because it was obvious is obvious only
                  to you, and only today.

no parse walls     never put queryable data behind a parse. keep the
                  raw blob as a receipt, shred the rest into columns
                  you can actually ask questions of.

one job each       everything is a tool that supports other tools.
                  build one thing with a clean edge, then build
                  the next thing against that edge.
HMI development. The screen an operator will use, being built on a laptop by someone imagining them.
20HMI development. The screen an operator will use, being built on a laptop by someone imagining them.
Robot programming at the bench — the other dialect I write in.
21Robot programming at the bench — the other dialect I write in.
On ladder logic

Yes it's a real programming language. It's a domain-specific language with a graphical syntax, hard real-time guarantees and a debugger that shows live state on the running machine. Show me the web framework that does that.

← RoboticsAI & Philosophy →