Read the game client, not the wiki
This one covers five days, September 25 to 29. Most of it went into rules for the tools I build with: a CLI that reports before it acts, and a workflow kit that split every file over 500 lines. The rest went into a new s
This one covers five days, September 25 to 29. Most of it went into rules for the tools I build with: a CLI that reports before it acts, and a workflow kit that split every file over 500 lines. The rest went into a new side project that started on Sunday, a game database that reads its data straight out of the game.
Lootlore: a game database built from the client's own files
Lootlore is a new site for players: items, monsters, NPCs, quests, maps, skills, shops, crafting, and music, plus a character builder. It is private and has no public URL yet, so no link. The first game is MapleStory Classic World, and the site went from an empty scaffold to about 4,400 records in three days.
Most game databases are built by hand, or scraped from a wiki that someone else built by hand. Lootlore starts one step earlier. The game client ships its data as WZ archives, 298 files and about 420 MB for this client, and the data pipeline reads them directly.
The reader is plain Node, written from scratch:
- Each
.wzfile opens with aPKG1header. Reads go through a file handle by byte range, so nothing loads a whole archive into memory. - Strings are XOR'd against a key stream, and directory offsets are scrambled with a hash of the game version. The reader derives both.
- Images are stored as zlib-compressed pixels in one of six GPU formats, from 16-bit BGRA to DXT5 blocks. The reader decodes each one to RGBA and writes a PNG.
One detail took longer than it should have. The client ends its zlib streams on a sync flush, with no final block and no checksum. Node's inflateSync treats that as a truncated file and throws. Passing finishFlush: Z_SYNC_FLUSH makes it return what it has, which is every pixel.
The client is the ground truth, but it does not carry everything. Two community sites fill the gaps (damage formulas, DPS tables), and each source lands untouched in a raw/ folder first. A build turns raw/ into one shared record shape per kind, and the site renders every game with the same page code. A rebuild with no source change writes nothing and removes nothing.
The site side grew fast on top of that data. Every name on a record page links to its record, both ways. A world map places markers with minimap hover cards. Monster icons are drawn from the idle animation frame, and the character builder draws its avatar from the client's own art. On Tuesday every tooltip on the site folded into one hover card system, so hovering any record name anywhere shows a preview of it.
It runs on OMEGA, the framework in the next section. The game data is a custom target beside the web target, so the pipeline and the site live in one repo and share one config.
OMEGA: report first, act on --execute
OMEGA 0.53.0 shipped on September 26. The bigger change is still on its way to the next release: every verb in the CLI now follows one rule about when it acts.
There are two switches with two names. A verb that acts by default takes --dry-run to show its plan instead. A verb that reports by default takes --execute to do the work. The old --apply and --check flags are gone, so there is one word to learn for "yes, really". omega migrate is the clearest case: a bare run lists every step still due and exits 1, and --execute runs them.
The same pass gave the CLI one verb table. Each row names a verb and the frameworks that own it, and every target's scripts are generated from that table. omega dev is one verb on all four frameworks now, and every target's start script is omega dev.
Two smaller changes in the same batch:
- The test runner installs a connect trap before any suite runs. A connect or DNS lookup to a host past this machine throws, while loopback stays open for the local emulator. A unit test cannot quietly depend on the internet.
- The two admin-key checks in the MCP server now compare with a constant-time
safeCompare. A plain===stops at the first wrong character, which leaks how much of the key matched through timing.
workkit: every file over 500 lines, split
workkit is my workflow kit for Claude Code: GitHub issues as the work list, labels as the pipeline, and hooks that enforce the rules. It shipped nine releases in four days, 0.59.0 to 0.62.0.
Most of that was one rule. A lint hook in my setup bounces any write that grows a code file past 500 lines of code. Comments and blank lines do not count. A file already over the bar can shrink but never grow. Then the kit applied the rule to itself: about 30 files over the bar were split by concern. The biggest was a 5,004-line test suite for the dashboard app, which is now a folder of suites, each under 500 lines, with a shared helpers file.
The hooks that guard the pipeline read the shell commands the agent runs. One of them blocks a second full test run on a tree that already passed. Another reruns the touched test files when an issue moves to QA, and refuses the move while any is red. Reading bash text is harder than it sounds. A redirect like 2>&1 kept being misread as a file argument, and a test command that only appeared inside a commit message had to count as a mention, not a run. The fix strips heredocs and quoted strings before looking for the command.
The next push also adds a Windows runner to CI, so every push tests on Windows beside Linux. Eight tests that failed on a Windows machine are already fixed.
Small but satisfying
- My dotfiles clean a machine-specific setting out of
settings.jsonwith a git filter. After Claude Code rewrote the file to a different size, git showed it as modified with an empty diff. Git trusts the file size it recorded before it runs the filter. A no-opgit addrefreshes that record, so setup now ends with one whenever the filtered file already matches. - The cover on this post uses a new theme in my cover renderer: thick ink borders, hard offset shadows, one flat accent. It is the same neobrutal look my video explainers use, now as data one config key away.
Next: Lootlore gets its first public page, and the CLI rule ships in an OMEGA release. If you have built a data pipeline off a game client, I would like to hear what format fought you the hardest. Comments open.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.