Analysis model: sol high, GPT-6 (reasoning level not recorded)

Last edited: 2026-09-17

Revision attribution: GPT-6, called Astra in this session, independently
checked and corrected the earlier sol high working analysis.

# Mini Intro by Trug - Technical Dissection

Release year: 1991.

Mini Intro combines a diagonal rainbow scroller with a field of single-pixel
sine dots. Its small visual vocabulary hides several useful implementation
details: two differently scaled sine tables, a thirteen-record controller,
selective old-point erasure, a skewed glyph buffer, and a scroller that runs
twice per dot update. The text and shape sequences have different periods.

[Pouet](https://www.pouet.net/prod.php?which=21400) dates the intro to July
1991 and files it under Future Crew and Image. The submitter explicitly
describes it as Trug's individual production. This article therefore uses
Trug as the author rather than inferring a collaboration from those catalogue
group tags. The preserved EXE member is dated 6 August 1991; that archive
timestamp and the catalogue month are distinct evidence.

The examined release is the
[Scene.org archive](https://files.scene.org/view/demos/groups/future_crew/intros/trug.zip).
Its three members are the 9,328-byte `trug.exe`, a 19-byte `file_id.diz`,
and a later 2,714-byte archive notice.

```text
trug.zip SHA-256
f9915d616f1125ec7f1eb7f9e5431fee9e4bed5bc35ab3ed4c32decf04a31b37
trug.exe SHA-256
7302fe78b5d4bb4bf1e0ecc9f418ec2698b421026f2136e16219abb1922062ca
```

## Evidence and address convention

The original packed program runs in DOSBox-X 2024.03.01 with S3 VGA and
10,000 fixed cycles. Host audio is disabled. The direct capture uses a
640x400 guest surface representing VGA mode 13h. Capture timestamps are
host recording timestamps; they are not instruction counts.

The 17 September 2026 recording lasts 245.000 seconds and contains 4,899
decoded FFV1 frames with no audio stream. The requested host sampling rate
was 20 fps. The recording starts before program launch; it is not aligned
to an emulated retrace. A scripted Space key near the end exits the intro;
the frame at 04:04.000 shows the DOS prompt. That exit frame and its
historical contact text are excluded from the site.

The five-second excerpt below is capture 01:10.000..01:15.000. It shows
the dense two-lobed ring formation and the moving diagonal text. The two
lobes are part of the rendered point field, visible in consecutive frames.
The excerpt preserves the complete 640x400 guest surface.

![Mini Intro direct runtime ring formation and diagonal rainbow scroller, 01:10 to 01:15](images/trug-mini-intro-runtime-rings.gif)

This full frame at 01:30.000 shows a later multi-lobed field. Its caption
does not assign a controller record using wall-clock estimates alone.

![Mini Intro direct runtime multi-lobed dot formation at 01:30](images/trug-mini-intro-runtime-90.png)

For the code study, two executable restoration stages were followed into
the Pascal program. Offsets in the following discussion are relative to
the recovered main code segment. In the controlled memory image that
segment is `1000h`, and DS is `113Ah`: a data address `DS:n` corresponds
to file offset `13A0h+n` in the 64 KiB memory snapshot.

The 64 KiB snapshot is a working memory dump, not a claimed original
executable length. Its SHA-256 is:

```text
4320a28067b958070293e024f0d71167991f8037f76bd612f2c6f2d7402b0428
```

The loop checks execute recovered x86 instructions in Unicorn. They stub
Pascal stack checking, BIOS video setup, retrace polling, palette I/O and
keyboard queries. They prove address sequences, state transitions and
operation counts under the stated initialization. They do not measure
DOSBox frame rate or prove physical VGA timing.

## The main loop has two scroller updates

Startup at `02FDh` initializes current count to 21, enables the growth
branch, clears the buffer, selects mode 13h, uploads the palette, and
loads controller record 1. The repeating code begins at `03EFh`:

```text
wait for next vertical retrace
update and display scroller
erase previous dot addresses
if growth enabled and current_count < target:
    current_count += 1
else if growth disabled and current_count > 2:
    current_count -= 1
if scroll_command == B5h:
    load next controller record; clear command
for i = 0 through current_count-2, inclusive:
    colour = i+1 if i+1 <= 254 else 1
    draw one dot; save its address; advance its phases
advance the four frame base phases; copy them to current phases
wait for next vertical retrace
update and display scroller
poll keyboard; repeat while no key
```

Retrace helper `0000h` first waits for port `03DAh` bit 3 to clear,
then waits for it to set. Thus there are two retrace waits for each dot
update. Nominal refresh-based estimates must still account for whether
the drawing work misses a retrace at the selected CPU speed.

Current count is an exclusive-style state variable: 21 means 20 dot
writes, and 257 means 256. The original colour branch does not implement
modulo 254: both dots beyond index 253 receive colour 1.

## Dot inner loop: two amplitudes, no multiply

`0094h..0133h` draws one dot. Its first two phase bytes index the
256-byte table at `DS:0184`; the other two index `DS:0084`.
Every entry matches the following rounded sine definitions:

```text
small[i] = round(25 + 25*sin(2*pi*i/256))   # 0..50
large[i] = round(70 + 70*sin(2*pi*i/256))   # 0..140
y = 50 + small[p0] + small[p1]            # 50..150
x = 20 + large[p2] + large[p3]            # 20..300
address = 320*y + x
A000[address] = colour
erase_list[i] = address
p0,p1,p2,p3 = (phase + corresponding_step) modulo 256
```

There is no coordinate multiply in this routine. `XCHG AL,AH` converts
y into `256*y`, two right shifts obtain `64*y`, and the additions form
`320*y+x`. The bounded table sums keep the byte-swap shortcut valid.
All dots remain within the visible 320x200 page; no clipping branch is
needed for these coordinate bounds.

The original instructions passed 1,280 independent input cases: all 256
equal-phase vectors and 1,024 reproducibly seeded mixed vectors. Each case
checked the emitted pixel, saved erase address, and all four phase updates.

The old-frame eraser at `0071h` walks `current_count-1` saved words and
writes zero to those addresses. It does not clear the page and does not
restore underlying artwork. Repeated or coincident dots still cost a write
and a history entry. At 256 dots, the field needs 256 old-pixel clears and
256 new-pixel stores per main update, plus 512 bytes read and 512 bytes
written in the address history.

## Controller records and the growth trap

Each ten-byte record holds a target word, four per-frame phase increments,
and four per-dot increments. Displaying increments as signed bytes makes
their direction readable; the actual additions wrap modulo 256.

| Record | Target state | Frame phase increments | Per-dot increments |
| --- | ---: | --- | --- |
| 1 | 20 | 0, 0, 0, 0 | 1, 0, 1, -128 |
| 2 | 20 | 0, 0, 1, 0 | 1, 0, 1, -128 |
| 3 | 257 | 0, 0, 1, 0 | 1, 0, 1, -128 |
| 4 | 257 | 0, 2, 2, 2 | 1, 0, 1, -128 |
| 5 | 257 | 0, 0, 1, 1 | 6, 10, 5, 3 |
| 6 | 257 | 2, 0, 2, 2 | 4, 1, 126, 1 |
| 7 | 257 | 0, 0, 2, 2 | 63, 0, 1, -128 |
| 8 | 257 | 0, 0, 2, 2 | 6, 10, 8, 3 |
| 9 | 257 | 0, 0, 1, 1 | 3, -2, 3, -126 |
| 10 | 257 | 0, 1, 2, 2 | -66, 4, -2, -2 |
| 11 | 257 | 1, 0, 1, 1 | 1, 2, 0, -127 |
| 12 | 257 | 0, 3, 0, 3 | 0, 2, 1, -127 |
| 13 | 157 | 1, 2, 1, 0 | 10, 2, 1, -128 |

`0195h` loads the selected record and advances the selector; after record
13 it returns to record 4. It does not reset all accumulated phase bases.
The early controller records introduce motion and then grow the field.

The target word is not an unconditional assignment or a bidirectional
easing target. With the growth flag set, a target below the current count
does nothing. An 8,000-update instruction trace enters record 13 on
update 4,816 with count state 257; the next record on update 5,055 still
has count 257. Reading the final record's 157 as a visible reduction
would incorrectly describe the program.

## Scroller: three different address walks

The far routine at main offset `0560h` runs twice per dot update.
Every nine calls it fetches another script byte and copies sixteen font
bytes into a temporary glyph. Character slots are 32 bytes apart, while
this renderer reads bytes 1..16 of the selected slot. Bytes outside the
accepted range produce an empty glyph and can still act as commands.

The glyph-column step rotates each byte through carry, choosing either
zero or a colour index. Its sixteen stores begin at `DS:3204h` and
advance by **319**, because `STOSB` adds one and `ADD DI,013Eh` adds
318. Relative to buffer `3150h`, row r receives a pixel at column
`180-r`. This is a skewed insertion edge.

There are nine column steps for an eight-bit glyph. On the tested CPU
model, `RCL AL,9` has effective rotation count zero; carry decides the
store. For an all-set test glyph, the first eight steps emit sixteen
nonzero samples. Step nine emits zero on rows 1..15, supplying spacing;
row zero can inherit carry from the earlier colour comparison.

Presentation starts from buffer row 2 and reads fifteen rows of 180
bytes each. Each `MOVSW` advances the destination by two, then
`ADD DI,0140h` advances it by another 320. After ninety words, the
subtraction `6FF4h` makes the next source row start one screen row lower:

```text
source(row, word) = DS:3150h + 320*(row+2) + 2*word
dest(row, word)   = (8980h + 320*row + 322*word) modulo 65536
row = 0..14; word = 0..89
```

Both bytes of each word are stored. The 2,700-byte destination sequence
was matched byte for byte against execution: 2,580 addresses are in the
visible screen and 120 in its offscreen tail. Ninety of the visible
addresses arise after segment wraparound. These counts describe writes,
including black pixels; they do not imply ninety visible stray dots.

Finally, the routine shifts 180 bytes left in each of buffer rows 0..14,
using forward overlapping `REP MOVSW`. Presentation and shifting therefore
cover different row ranges. Together with the sixteen injected bytes and
one explicit clear at `DS:350Fh`, each call issues 2,717 buffer-byte
writes and 2,700 VRAM-byte writes. The video transfer alone is 5,400
bytes per dot update, much larger than the dot field's 512 pixel writes.

## Colour changes are pixel indices

The palette routine at `0014h` is called once from `03DDh` during startup.
It rearranges its component arrays and writes DAC indices 1..254, with
254 index outputs and 762 component outputs. The steady frame loop does
not call it. Describing a live DAC colour conveyor would misattribute the
rainbow effect.

The scroller advances a colour counter each call, wrapping 240 to 1.
An illuminated glyph sample uses `counter+row`, so the skewed insertion
edge receives a graded set of colours. Subsequent RAM shifts and diagonal
presentation move those already coloured pixels across the screen.

## Text and shape timing do not share one loop

`B5h` requests the next dot controller record. `B4h` redirects the text
pointer to offset `0216h` (534) and immediately processes that byte.
It adds no extra blank character interval of its own.

The steady text region contains 1,094 consumed bytes and eighteen `B5h`
markers. At nine calls per byte, one pass takes 9,846 scroller calls or
4,923 main updates. The executed trace returns to script-next=535 on
updates 2,404 and 7,327, while the loaded shape record changes from 4 to
12. Eighteen markers advance a ten-record cycle by eight positions.
Text/controller alignment therefore repeats after five text passes.

That is an alignment period, not a proven framebuffer period. Accumulated
phase bases, the 240-step colour counter, glyph state and presentation
buffers must also agree before an identical pixel sequence can be claimed.

The command is tested only between the two scroller calls. A marker fetched
by the second call is handled in the following main update. This explains
why text fetch count and controller-entry count need not identify the same
half-frame.

## Corrections and limits

The independent revision corrected five errors in the earlier working
interpretation: using one sine amplitude for both axes, describing a live
palette conveyor, applying the scroller destination increment only once
per row, assuming the final target shrinks the dot count, and treating
colour overflow as modulo 254. The instruction checks above exercise the
corrected interpretations directly.

The complete recording establishes the opening, multiple later formations,
and keyboard exit. The controlled instruction trace establishes text-loop
and controller state transitions. Matching every recorded shape to an
exact controller entry would require a synchronized trace from the same
DOSBox run; visual resemblance alone is insufficient.

The two-stage executable restoration supplied readable code and data, but
the previous working notes' detailed unpacker instruction totals have not
been independently reproduced in this revision and are omitted. No claim
of a full framebuffer period or measured native refresh cadence is made.
