The COG Factory / COG Factory Experiments
Digitron
A server-authoritative game of TIC-TAC-TOE on a 40×30, 16-colour pixel display cut into a wall. One cog owns the rules, another owns the pixels, and the wire between them carries GAME STATE ONLY — nine cells, a cursor, a turn, a phase, two scores.
| project | digitron |
|---|---|
| date | 2026-08-24 |
| package | DIGITRON.GOB, readme.txt, screenshot.jpg |
| build | 1 — 2026-09-05 |
| sha256 | b7f4f88167f7ddc517182cdd77137898c9b4a7bda517e7736bad016ecea90cc3 |
Installing it
- Unzip it.
- Drop the
.GOBinto theepisodefolder of a Jedi Knight install — the one that already hasJK1.GOBin it. - Pick it from the episode list. On a libretro frontend, load the
.GOBitself as content.
The GOB carries its own level, cogs, materials and sounds and
reads the stock resource/ for everything else. There is no
retail game data in the zip.
These are living projects, not finished releases. A level is rebuilt whenever something is learned — a lighting rule, a texture orientation, a whole detail pass — and it keeps its filename through all of it. The build number is how to tell which one you have: it is printed in the readme inside every zip, and it only moves when the level's bytes actually change.
Screenshots
The readme that ships with it
========================================================================
Jedi Knight Level
DIGITRON.GOB
A server-authoritative game of TIC-TAC-TOE on a 40x30, 16-colour pixel
display cut into the wall of a Jedi Knight room. One cog owns the
rules, another owns the pixels, and the wire between them carries GAME
STATE ONLY -- nine cells, a shared cursor, a turn, a phase, two
scores. No mod, no DLL, no extended verbs: three trigger IDs, three
buttons and a screen that syncs nothing.
[cogfactory / digitron]
BUILD 1 -- 2026-09-05
sha256 b7f4f88167f7ddc517182cdd77137898
This level is not made or distributed by LucasArts
Entertainment Co.
========================================================================
THIS PACKAGE
------------------------------------------------------------------------
DIGITRON.GOB the level
readme.txt this file
screenshot.jpg what it looks like
INSTALLATION
------------------------------------------------------------------------
Drop DIGITRON.GOB into the `episode` folder of a Jedi Knight install
(the one that already has JK1.GOB in it) and pick it from the episode
list. Nothing else needs to change: the GOB carries its own level, cogs,
materials and sounds, and reads the stock resource/ for everything else.
On a libretro frontend, point the core at the GOB itself. It needs a
folder beside it holding `resource/` from a real install -- that is
where the retail data lives and none of it is in here.
CONTROLS
------------------------------------------------------------------------
THREE BUTTONS are cut into the wall opposite the screen. Walk up to
one until you are TOUCHING it and press SPACE.
[<] move the cursor one cell LEFT (it wraps)
[OK] START the game / place your mark / REMATCH
[>] move the cursor one cell RIGHT (it wraps)
X is player slot 0 and O is player slot 1. IN SINGLEPLAYER IT IS A
HOTSEAT -- one person plays both sides, because that is the only way
one person can play a two-player game. In MULTIPLAYER the server
enforces turns by slot, and an intent from the wrong player, on an
occupied cell, or in the wrong phase is REFUSED: the board does not
move and only the machine that was refused hears about it.
The engine holds ONE 250 ms activate debounce shared by every button,
door and switch in the level, so four presses a second is the ceiling
for everybody at once. The game is turn-based partly for that reason.
NOTES
------------------------------------------------------------------------
THE WIRE CARRIES GAME STATE, NEVER PIXELS. Seven opcodes name seven
FIELDS of the model -- cell, cursor, turn, phase, result, score,
refusal count -- and not one of them names a screen cell, a colour
or a paint op. Rendering is a pure local function of the model, and
the proof is mechanical: re-run every scripted game with the banner
moved, the score line moved, the board moved and every cell resized,
and the transcript comes out byte-identical while the picture does
not.
THE SERVER OWNS EVERYTHING, INCLUDING THE CURSOR. dgtsrv.cog holds
the whole game in its own cog heap behind `if (!IsMulti() ||
IsServer())` and never touches the screen or the sound. The cursor
is on the wire because it is game state: a spectator watching a
machine whose turn it is not still sees it walk.
A REFUSAL IS A MODEL FIELD AND THE NOISE IS THE CLIENT'S OWN IDEA.
The server broadcasts each slot's REFUSAL COUNT, absolute, like the
score. Every machine sees both counters move; each one decides,
locally, that only its own means a sound. Two refusals cross the
wire in the identical shape in the test suite and exactly one of
them is audible.
SINGLEPLAYER AND MULTIPLAYER RUN THE SAME 1163 LINES, and the only
difference in the whole log is the REASON one intent was refused --
because the rules really do differ there and nowhere else. One digit
of one line of the episode file is the entire build difference.
DROPS ARE NORMAL AND THE BOARD HEALS ITSELF. The server re-
broadcasts its whole model every two seconds, forever, from day one.
A build that discards every third trigger it receives plays a
visibly wrong game -- a missing mark, a score that never moved, a
cursor in the wrong cell in the wrong colour -- and the next sweep
repairs all of it in one frame, with one sound.
THE SCREEN SYNCS NOTHING. SetWallCel goes through the engine's
32-entry surface queue, which drops the rest while still marking
them changed, so streaming a 1200-cell repaint over the wire is
saturation plus permanent desync. Every cog here is NOSYNC and
paints locally on each machine. In a real multiplayer session, after
a full repaint, the count of screen surfaces the engine marked dirty
is ZERO.
16 COLOURS FROM ONE MATERIAL. Sixteen cels of one 16-bit MAT and
`pen & 15`; SetSurfaceMat is never called anywhere. The screen's
size, its first surface and its material all come from ONE ROW of
the level file, so a different room writes a different row and ships
the same cog.
IT IS DETERMINISTIC ON PURPOSE. There is no Rand() anywhere -- Jedi
Knight never seeds it from the level, so a random opponent could not
be tested. The games the suite plays are fixed content, the five
sound effects are baked, and two runs produce the same transcript to
the line.
FIVE PROCEDURAL SOUND EFFECTS, and the interesting one is the one
you do not hear: a resync sweep over a board that is already correct
makes no sound at all, because absolute state that has not changed
fails the compare and never reaches the renderer. A sweep that DID
heal something says so, once -- never once per chunk.
PHASE 2's PROTOCOL DEMO IS STILL IN THE BOX. The counter and the
marching grid that proved the plumbing are unchanged, byte for byte,
behind one build switch, and all twelve of their tests still pass.
WHAT IS IN THE GOB
------------------------------------------------------------------------
90 episode.jk
268697 jkl\digitron.jkl
22140 cog\dgtcli.cog
7041 cog\dgtprob.cog
15403 cog\dgtscrn.cog
4460 cog\dgtsnd.cog
16046 cog\dgtsrv.cog
2188 mat\dgtbezl.mat
2188 mat\dgtbtn.mat
2188 mat\dgtceil.mat
2188 mat\dgtflor.mat
11980 mat\dgtpix.mat
2188 mat\dgtwall.mat
30914 sound\dgtboot.wav
2028 sound\dgtdeny.wav
1808 sound\dgtpres.wav
13274 sound\dgtsync.wav
750 sound\dgttick.wav
405571 bytes in 18 entries
CREDITS
------------------------------------------------------------------------
Generated end to end by tools/cogfactory in the openjkdf2_libretro
tree -- level, five cogs, materials and sound.
The screen is p23's, at 16 colours and one material.
Sound effects are procedural, from tools/cogfactory/sfxlib.py; no
audio art and no external service.
It stands on p25 (what a TYPE 2 episode really boots into, and what
COG_SHOULD_SYNC costs), p24 (trigger delivery is synchronous, re-
entrant and float-compared), p23 (the pixel screen), p22 and p21
(how big a cog may be), and p19 (a whole game in a cog, and why the
shadow starts at -1).
The readme format is styled after SM Sith Lord's cog headers,
because his are the ones everybody read.
========================================================================



