========================================================================
 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.

========================================================================
