Froot Loops and a graphics card

*Published on US Mother's Day, 2026.*

The email arrived on the 4th of December 2006, forwarded by my dad from Bill, an engineer at the company my dad was contracting at. I was 14. Bill had attached a zip file containing an HTML page with PHP scriptlets embedded inline, written by someone at the company four years earlier, plus a sample text dump from a Siemens 9005 digital PBX. The file was named `parsebev3.html`, after Bev, the user who actually ran the parser every day. There were already three versions of a parser-for-Bev. The job was to make a fourth, in a Windows desktop application.

![Main parser window: Input file textbox, "Go!" button, status bar showing "Status: File Loaded OK"](/posts/froot-loops/main.png)

> If your son can take a look at this and see how easy it might be to do, I would appreciate it. I think rather than rewrite it as an ASP.Net app it might be better to just create a Windows based .net application. Either VB or C##. I'm sure we could figure out some way to compensate him for his work, talk to Candi.

Candi, Bill's wife, was also in the email; she would handle compensation. With Bev, the three of them were a small-shop cast of characters this project belonged to more than it ever belonged to me.

The Siemens 9005 was a digital PBX, the kind that handled switching for offices that still ran their own phone systems. Each extension on the switch had a stack of feature programming attached: class of service, hunt-group memberships, forward-on-busy targets, forward-on-no-answer targets, ACD assignments. The switch dumped this all as a structured-but-fiddly text format, and downstream of that someone needed to pull the dump into Excel to actually look at the configuration. That was the job. Read the text dump, parse the per-extension records, output TSV.

I downloaded the zip, looked at the PHP for a bit, and started writing. I picked VB6.

Bill's email had recommended VB.NET or C#. I'd already been writing VB6 for a couple of years by then, mostly on web-automation tooling for browser games. My copy was cracked, because I was a kid and had no money. I'd tried VB.NET a year or so earlier with *Sams Teach Yourself VB.NET 2003 in 21 Days*. The language itself was fine. The tooling shift from VB98 to the .NET ecosystem, and the DLL hell that came with it, was where I got stuck. So I went back to VB6. It wasn't a naive choice; it was what got the parser shipped.

There was no Siemens 9005 dump-format spec on the internet that I could find. The format had to come from reading the PHP, which itself was probably someone's empirical reading of a few sample dumps. So I did, opened the sample dump in Notepad, and wrote down what I saw at the top of my own module. The first chunk looked like this:

```
' Field Posn Length
'
' Extension 1 7
' Type 9 4
' COS 14 3
' Target 1 18 8
' ...
' Forward DND External 64 1
```

The full spec, all of it written into the comments at the top of the module before any executable code, was about 100 lines of "find this anchor, then offset by N for a field of length L." Records were bracketed by `RINGDOWN` (start) and `FORWARD` (end). Inside each record, the first `DS` anchored the basic fields. The second `DS` anchored ACD and NAME. Optional sections were anchored by `G R O U P` and `T E R M I N A L`, with literal single-space gaps between every letter. A 1990s PBX serializing its terminal-info section header as letter-spaced ASCII is the kind of detail you don't get to pick up unless you've stared at the actual output.

The code that followed the spec was a faithful translation of the comments:

```vb
intRecStart = InStr(intRecFinish, strBuffer, "RINGDOWN") + 2
intRecFinish = InStr(intRecStart, strBuffer, "FORWARD")
strRecord = Mid$(strBuffer, intRecStart, intRecFinish - intRecStart)

intFirstDS = InStr(1, strRecord, "DS") + 2
strEXT(intRecordCount) = Mid$(strRecord, intFirstDS + 1, 7)
strTYPE(intRecordCount) = Mid$(strRecord, intFirstDS + 9, 4)
strCOS(intRecordCount) = Mid$(strRecord, intFirstDS + 14, 3)
strTARGET1(intRecordCount) = Mid$(strRecord, intFirstDS + 18, 8)
```

`InStr` to find an anchor, `Mid$` to take a substring of fixed length at a hardcoded offset. That is the entire parsing technique. The PHP had done the same thing with its substring functions; I did it four years later with `Mid$`. The format determines the technique more than the language does.

The terminal-info section had the most fiddly parsing. Up to twelve terminal entries per record, four banks of three terminals each, with each bank anchored by its own `DS`. Empty slots were marked with `*` characters in specific positions, so the code had a chain of `If InStr(strCSTYPE5, "*") > 0` checks, each clearing the relevant slot and setting an `intStopFlag` to short-circuit further bank reads. The whole thing reads like every fixed-width parser ever written: a stack of conditional fall-throughs, half of them defending against quirks in the upstream output that a real spec would have ruled out.

---

The parser was the job. The rest of the application is where the kid version of me showed what he thought real Windows applications were supposed to look like.

The custom button user-control on the Go button supported nine styles: Flat, Java, OfficeXP, WindowsXP, WindowsTheme, Plastik, Galaxy, Keramik, MacOSX. Two of those (Plastik and Keramik) are KDE themes. There is no reason a single-user Windows VB6 application needed nine button styles. I put them all in because the user-control I'd downloaded supported them, and I let Bev pick from a Settings dialog because I'd built a Settings dialog that did INI-backed persistence and I might as well give it something to remember.

![Six button styles rendered by the custom user-control](/posts/froot-loops/buttons.png)

A debug-mode menu, when toggled, streamed the parser's narration into a textbox on a separate form. `Debugtxt "Parsing record " & intRecordCount`, called from inside the parser. I built a logging system because I'd decided real Windows applications had logging.

The folder-output picker used a Win32 `SHBrowseForFolder` integration so it would be a native Explorer-style dialog. I didn't use it for the file picker, which was a `CommonDialog`. Why two? Because the docs I'd read for browsing a folder said `SHBrowseForFolder`, and the docs for opening a file said `CommonDialog`, and I did not yet know to question why.

![Settings dialog: record-type toggles, output options, and the Button Styles dropdown](/posts/froot-loops/settings.png)

On the About screen, clicking the IHS logo brings up a Froot Loops image, which I had forgotten was there.

![About dialog. Clicking the IHS logo reveals a hidden Froot Loops image.](/posts/froot-loops/about.gif)

What shipped did the job. Open a file, hit Go, get a TSV that opened in Excel. None of it was in source control; I'm lucky still to have the source twenty years later, copied from drive to drive. Each release went past my dad as code reviewer first. The application went into Bev's daily workflow and replaced the PHP/HTML thing.

I got paid. The compensation, sorted out by Candi, was a case of Froot Loops (I'd been to Florida earlier that year and gotten weirdly into them at breakfast; this was a known fact in the family, deployed) and an Nvidia GeForce 7950 GT. Generous, by 14-year-old PC-gamer standards. A graphics card was the right shape of payment. The Froot Loops were the joke. Both arrived.

![XFX-branded Nvidia GeForce 7950 GT, the graphics card in question.](/posts/froot-loops/7950gt.jpg)

What stays with me is Candi at her kitchen table, wondering what would keep a passionate young boy interested in technology.

---

Twenty years of writing software since this parser, what I notice is how much of what I've been paid to do was a more sophisticated version of the same job: take this stream of bytes from over here, derive what's interesting in it, deliver it in a shape over there. The Siemens dump in 2006, an FX trade ticker in 2011, a fleet of Whole Foods in-store handhelds in 2019, an Amazon promotion-planner in 2022, a VALORANT build event in 2024. Structurally the same problem, dressed up in the conventions of their domain. The companies paying for the work cared about the dressing-up, which is fair, because that's where the business value was. The shape of the work itself was almost always point-to-point ETL with a UI on top.

That category of work makes up a huge fraction of paid software, and a lot of what I've shipped since 2006 is still shaped that way. An LLM with a sample dump and a sample output can produce most of the syntactic legwork of fourteen-year-old me's parser in an afternoon today. The `InStr`/`Mid$` scaffolding, the output formatters, the glue between the settings dialog and the parser, all of that collapses.

The hours that don't collapse are the judgment ones. The Siemens dump didn't mark "no more terminals here" with a clean sentinel; it put a `*` character in some positions where data would otherwise be, and you had to know which field within each bank was the reliable diagnostic. You read sample dumps until you saw the pattern, then baked your intuition into a check. An LLM can produce the check once you've told it where to look; it can't tell you where to look without you having looked.

The personality of a thing built by a specific person who cared about it also doesn't come out of an LLM. Nine button styles because the user-control supported KDE and Mac OS X and Java themes; a status bar reading "Status: Idle" because something at the bottom of the window had to; a whole debug-mode menu because real Windows applications had logging. None of those are LLM-generated defaults. They're the marks of someone who cared what their software looked like, even when only Bev was going to see it.

I would not wish a Siemens 9005 dump format on a 14-year-old. But the era did produce engineers, and the kind of engineer it produced was comfortable with messy real input, with a one-button UI for an audience of one, with shipping the thing and finding out what to defend against once it was in production. My best LLM-assisted work has rested on instincts I learned writing software like this: static types catching my mistakes for me, behavioral tests for the parts of a system I don't fully hold in my head, the simple knowing-where-to-look that comes from staring at enough sample dumps. What an engineer whose first thousand hours doesn't include any of that ends up with, I don't know.

The parser is still on my NAS. The Froot Loops are long eaten. As for the graphics card, it lived in three different machines before dying, sometime around 2010, in a case I built in the garage. I miss the era a little, not for the tools but for the shape of the work.

---

Bill is retired now, works as a photographer in Colorado, and my dad has visited him there several times. I'm grateful for the chance he took on me. Candi, his wife and a mother, passed from breast cancer in 2014. If this post left you thinking of someone who took a chance on you, consider [Susan G. Komen](https://www.komen.org/) or [Cancer Research UK](https://www.cancerresearchuk.org/), in her memory.