Analysis model: gpt-5.5 xhigh
StarPort BBS Intro II by Future Crew - Technical Dissection
StarPort BBS Intro II is a 21 November 1993 MS-DOS VGA/AdLib BBStro by
Future Crew for the group's StarPort board. It is small enough to be mistaken
for a simple advertisement, but the released source shows a dense 2k-style
effect: a planar VGA starfield, a rotating 3D dot-letter scroller, palette
cycling, a 70 Hz timer hook, and a compact multi-channel AdLib music player.
This page covers the second StarPort intro. Future Crew's own FCINFO12.TXT
lists the earlier STARPRT2.EXE as the 13 September 1992 StarPort BBS intro
and STARPORT.ZIP as the 21 November 1993 StarPort BBS intro II. The 1993
source package is the useful target here because it preserves SP2.ASM,
SP2.COM, and the authors' comments.

The contact sheet above is runtime evidence from the original 1993 SP2.COM.
It shows the plain starfield, the first large projected dot letters, mid-run
rotation/scroll states, and the late title sweep. The source-derived analysis
below explains how those frames are made.
Sources
- Pouet,
StarPort BBS (2): https://www.pouet.net/prod.php?which=12217 - Demozoo,
StarPort BBS (2): https://demozoo.org/productions/99552/ - GitHub mirror of the released source: https://github.com/sceners/starport-bbstro-future-crew
- Defacto2 file page for
fcsp2src.zip: https://defacto2.net/f/b11aa7e - Metropoli mirror containing
FCSP2SRC.ZIP: https://files.mpoli.fi/software/PROGRAMM/ASM/FCSP2SRC.ZIP
Archive
The examined source archive is FCSP2SRC.ZIP from the Metropoli mirror:
325c928245d90685a71fccecf7312e2c68a50c9f2386e998846ed12dbe1b6954 FCSP2SRC.ZIP
It contains the source, an updated Future Crew information file, and helper
material, but not the final SP2.COM binary:
FCINFO12.TXT 66094 bytes
FILE_ID.DIZ 99 bytes
MAKE.BAT 86 bytes
MMZLM.WRZ 751 bytes
README 281 bytes
SLOBBS.EXE 6931 bytes
SP2.ASM 20856 bytes
UNDER.COM 1058 bytes
The SP2.COM used for runtime capture comes from the GitHub source mirror:
f999fcd7aa2e2c4b24a71164b4d3d74ee67441e58d76b353d49726fafa9dda72 SP2.COM
d27511e811f49ce41cf2d418441ad405737f774add6e9e89c451e297caa3c17d SP2.ASM
bfefdb6fa8c9e2d5040fbe0577bb1c0dc960a39685e60c23149809d04f6d51b4 FCINFO12.TXT
The source header identifies the production and credits directly:
StarPort Intro II V1.0
Copyright (C) 1993 Future Crew
code: Psi
music: Skaven
The comments also explain the size target. The released COM was first built normally, then postprocessed by removing zero data from the end. That is why the source declares large zeroed buffers while the final file is still only 1993 bytes.
Runtime Capture
On 4 July 2026 I ran SP2.COM under DOSBox-X 2026.01.02, Linux SDL2 64-bit,
with machine=svga_s3, normal CPU core, cycles=fixed 12000, dummy SDL
video/audio, and capture format=mpegts-h264. Timing zero is the start of
dx-capture /v SP2.COM. The captured stream is MPEG-TS H.264/AAC at 640x400,
duration 24.441233 seconds. The stills below are extracted at 320x200.
| Timestamp | Frame | Notes |
|---|---|---|
00:00.500 |
![]() |
The intro is already in VGA mode and drawing the initial star/dot field before readable dot letters enter the camera. |
00:05.000 |
![]() |
The dot-letter scroller has begun entering from the right. This is the text0 stream being converted into 8x8 font dots and inserted into the rotating point list. |
00:08.000 |
![]() |
Large letters pass close to the viewer, proving the scroller is not a flat bitmap scroll. Size changes come from the projected Z value and the pset2 dot-size branch. |
00:11.000 |
![]() |
The letter stream is now fragmented by perspective and rotation. The same frame loop also advances palette order and the AdLib row player. |
00:18.000 |
![]() |
A gap in readable letters leaves the starfield visible. The background points are normal pset1 dots sharing the same rotation/projection machinery. |
00:23.000 |
![]() |
The late run wraps back to the board-name phrase and shows the looped text source rather than a one-shot splash card. |

The GIF covers the 00:05..00:13 window. It is the important evidence because
the effect is temporal: the text advances in world coordinates, rotates through
two axes, changes dot size with depth, and shares the field with independently
seeded background dots. Still frames alone make it look closer to a static
bitmap than it really is.
Program Shape
The source is a tiny COM program with code, data, scratch buffers, and zeroed
state in one segment. At startup it sets DS=CS, ES=A000h, clears the zero
area, probes CPU/VGA capability, builds its font and geometry tables, starts
the AdLib player and timer hook, then loops until a key is pressed.
COM entry
zero declared scratch/state area
ES = A000h
reject pre-386 CPUs
reject non-VGA displays
copy BIOS font glyphs into private font buffer
switch to planar VGA mode 0Dh
build row/column/mask tables
seed dot list with random background stars
initialize AdLib instruments
install INT 8 timer at roughly 70 Hz
per-frame loop: palette, copy/clear, simulate, project/draw
restore timer, silence AdLib, print final text line
The capability checks are compact but explicit. The CPU test uses push sp / pop dx / cmp dx,sp to reject 8086/80186 behavior, then sgdt to separate 286
from 386-or-better. The VGA test calls INT 10h AX=1A00h and requires a VGA
display-combination result.
VGA Mode And Plane Layout
The runtime picture is not mode 13h. The intro briefly enters mode 13h only
to ask BIOS for the A..Z glyph bitmaps, then switches to BIOS mode 0Dh, the
320x200 16-color planar graphics mode.
mov ax,13h
int 10h ; temporary glyph-rendering mode
...
mov ax,0dh
int 10h ; final 320x200x16 planar VGA mode
Mode 0Dh matters because one byte in video memory represents eight horizontal
pixels in one selected plane. The source precomputes tables so the frame loop
does not have to recompute planar addresses and bit masks for every projected
dot:
rows[screen_y] byte offset for the scanline
cols[screen_x] byte offset within the scanline
colb[screen_x] one-pixel bit mask
colbv/colbw/colbww wider masks for larger projected dots
The visible buffer is private, not direct-to-VGA. The declared vbuf is
44*200 bytes, surrounded by eight rows of overflow padding on both sides.
That 44-byte row pitch is larger than the 40 bytes strictly needed for
320/8 pixels. The padding lets the wide-dot writers touch nearby rows without
constant boundary work.
clearcopy copies this private buffer to A000h and clears it in one pass:
mov si,OFFSET vbuf
mov bx,4
mov cx,200
mov di,-4
...
mov eax,ds:[si]
mov ds:[si],edx
mov es:[di],eax
The loop walks four bytes at a time and copies 40 bytes per screen row to VGA. The private buffer keeps 44 bytes per row, so the copy naturally skips the extra guard bytes.
Palette And Plane Cycling
Each visible frame starts by waiting for vertical retrace through port 3DAh,
then uploading a palette order through 3C8h/3C9h.
mov dx,3dah
wait display not in retrace
wait display in retrace
call setpal
The source defines five base colors from black through pale blue-white, then four palette-index tables:
col0..col4 background to brightest dot color
index1..index4 palette order plus target write plane
The ninth byte in each index record selects the VGA sequencer map mask for
the frame:
mov al,2
mov ah,ds:[si+8]
mov dx,3c4h
out dx,ax
That means the copy/draw rhythm is both palette-animated and plane-aware. The comments beside the index tables name the plane order:
index1 ... ,1 ; 1248
index2 ... ,8 ; 8124
index3 ... ,4 ; 4812
index4 ... ,2 ; 2481
The visible result is subtle in stills but clear in motion: the dot field has depth and shimmer while still using only 16-color planar VGA.
Timer And Simulation
The intro hooks interrupt 8 and reprograms the PIT divisor to 17000, noted in
the source as 70hz:
mov eax,fs:[8*4]
mov ds:oldint8,eax
mov ax,cs
shl eax,16
mov ax,OFFSET intti8
mov dx,17000
...
out 43h,al
out 40h,al
out 40h,al
The interrupt handler is intentionally tiny:
inc cs:framecounter
iret
The drawing loop consumes framecounter and calls doit70 once for each
elapsed tick. That is why the source says this is done for frame
synchronization on slower machines. Rendering can miss time, but the scroller,
waves, depth, and music row advancement are still stepped by elapsed 70 Hz
ticks rather than by raw host speed.
Dot Field And 3D Scroller
The full dot list has 444 entries:
DOTNUM1 = 256 text dots
DOTNUM = 444 text dots plus background dots
dots = x, y, z, draw-routine for each dot
Startup fills the list with random background points using a small linear
congruential generator. These use pset1, the small-dot writer. Letter dots
use pset2, the wide-dot writer that grows or shrinks vertically depending on
the projected Z threshold.
The scroller text is not stored as a bitmap. It is a byte stream with small
delay values embedded among characters. doit70 subtracts SCROLLSPEED, reads
the next byte when the spacing counter wraps, and calls letter3d for
printable characters.
SCROLLSPEED 90
LETTERDOTSPACING 128
text0 delay bytes + message bytes + zero terminator
letter3d uses the copied BIOS font as an 8x8 dot source. For every set font
pixel it writes a dot record:
mov ds:dots[di],si ; x
mov ds:dots[di+2],bp ; y
mov ds:dots[di+4],ax ; z sinus offset
mov word ptr ds:dots[di+6],OFFSET pset2
The dot index wraps inside DOTNUM1, so the 3D letters are a ring buffer. New
letters overwrite older letter dots while the background stars remain in the
rest of the array.
Rotation And Projection
The motion is built from two 2x2 rotations. set3drot creates sine/cosine
pairs from the 64-entry sine table, one matrix for Y/Z and another for X/Z.
doit70 changes the driving waves every 70 Hz tick:
sinus1 += 70
sinus2 += 177
wwave += cosine-derived wave
zadder += cosine-derived depth offset + 8888
udwave/lrwave drive the two rotation matrices
The per-dot draw path applies the rotations, adds the global Z offset, rejects near/behind points, then performs perspective projection:
bp = Z + zadder
if bp < 1024: skip
screen_y = 256 * rotated_y / bp + 100
screen_x = 307 * rotated_x / bp + 160
Those constants match the runtime look. Letters swell when they approach the viewer, shrink and fragment when they move away, and curve through the field instead of moving as a flat sine scroller.
Dot Writers
The small writer is one OR operation into the private buffer:
pset1:
mov al,ds:colb[si]
or ds:[di],al
The large writer uses precomputed masks across several rows:
pset2:
or ds:[di+0],ax
or ds:[di+44],ax
cmp bp,8300
jl pset3
or ds:[di-44],ax
or ds:[di+88],ax
or ds:[di-88],ax
or ds:[di+132],ax
The writer does not compute circles or shaded sprites. It relies on a few horizontal masks, a 44-byte row pitch, and OR-composition. That is exactly the kind of compromise that makes the intro small: dots look chunky and luminous, but the drawing code stays tiny.
AdLib Player
The audio code is a size-optimized AdLib player, not a raw register dump. The
comments call it Simplex Adlib Player, and the source says the tune was by
Skaven and converted with ST3->SIMPLEXADLIB.
music_channels = 8
music_speed = 8
instrument data for five instrument definitions
pattern streams with delay/note bytes
The output routine writes to ports 388h and 389h, waiting after both the
register select and data writes. a_init initializes the OPL state, loads
instruments for nine AdLib melodic channels, and silences notes. a_dorow
advances channels from pattern pointers, handles embedded delay bytes, converts
note nibbles through a_note_table, and writes key-on/key-off data through
a_playnote.
The important structural point is that music is integrated into the same 70 Hz simulation step as the visuals. It is not a separate DOS player and not a sample stream; it is part of the 1993-byte COM.
Exit Path
The main loop polls BIOS keyboard status:
again:
call doit
mov ah,1
int 16h
jz again
mov ah,0
int 16h
On exit the program restores the timer, reinitializes AdLib to silence it, returns to text mode, and writes a one-line final ANSI-style board card. That end line contains historical BBS contact material; the analysis does not transcribe it because the technical evidence is in the source and runtime effect, not in republishing phone numbers.
Runtime-To-Code Concordance
The media set is a direct DOSBox-X run of the original SP2.COM: six
timestamped frames, one contact sheet, and one short scroller GIF. This pass
does not recapture, crop, pad, resize, regenerate, or alter those assets. The
bridge is unusually strong because the released SP2.ASM source names the
data structures and routines behind the visible effect.
| Visual evidence | Source/code evidence | What it means |
|---|---|---|
00:00.500 starfield-only frame |
Startup seeds the 444-entry dots list, with background stars using pset1; private vbuf is copied through clearcopy |
The intro is already running the same dot projection/copy pipeline before readable text enters. |
00:05.000 first projected letters |
text0 byte stream, SCROLLSPEED, LETTERDOTSPACING, and letter3d converting BIOS 8x8 font bits into dot records |
The board text is generated from font geometry, not stored as a flat bitmap scroller. |
00:08.000 large close letters and the scroller GIF |
Projection path uses rotated X/Y/Z, rejects near points, then computes screen_y = 256*y/z + 100 and screen_x = 307*x/z + 160; pset2 grows dots by Z threshold |
The swelling letters in the GIF are perspective-sized 3D point letters sharing the starfield renderer. |
00:11.000 fragmented mid-run letters |
set3drot builds two rotation matrices from sine/cosine pairs; doit70 advances sinus1, sinus2, wwave, zadder, udwave, and lrwave |
The broken/curving letter shapes are rotation and depth motion, not random corruption or palette-only animation. |
00:18.000 field between readable text passes |
DOTNUM1 = 256 reserves text dots while the remaining entries persist as background stars |
Text and background points are one list with different writer routines; gaps reveal the persistent starfield. |
00:23.000 late board-name sweep |
text0 wraps via zero terminator and ring-buffer replacement inside the first DOTNUM1 dot records |
The late title sweep is the looped scroller source coming back through the same dot pipeline. |
| Contact sheet shimmer across frames | setpal uploads palette-index tables index1..index4; the ninth byte selects sequencer map mask plane order |
The visual shimmer comes from palette order plus VGA plane cycling, not just point coordinates. |
| Smooth timing and integrated audio | INT 8 hook, PIT divisor 17000, framecounter, doit70, and a_dorow AdLib row advancement |
Simulation and the compact Skaven AdLib tune advance from the same 70 Hz timing model. |
| 1993-byte COM size despite the complete effect | BIOS font reuse, 44-byte-pitch private buffer, precomputed row/column/mask tables, small OR-based dot writers, and Simplex AdLib pattern data | The small file works because text geometry, rendering, palette, timing, and music are all specialized to one effect. |
The runtime GIF is therefore a faithful summary of the source: a planar VGA/AdLib BBS ad where text becomes 3D point geometry, the same dot list holds letters and background stars, palette/index records animate colour and plane order, and a 70 Hz timer drives both simulation and music.
Why It Works
StarPort BBS Intro II is impressive because almost every byte does double
duty:
- The BIOS font is reused as source geometry for 3D dot letters.
- A planar VGA mode keeps the framebuffer small enough for a private buffer.
- Precomputed row, column, and mask tables make per-dot drawing cheap.
- Four palette/index records animate color and select write planes.
- A 70 Hz timer decouples simulation from render cost.
- Background stars and scrolling text share the same projection pipeline.
- The AdLib tune is pattern/instrument data, not a bulky external module.
The runtime GIF is therefore not just decoration. It shows the central trick: StarPort II is a tiny BBS ad built like a real demo effect, with text treated as 3D point geometry and then pushed through the same VGA/timer/music loop as the starfield.





