Analysis model: gpt-5.5 xhigh

Motion by Avalanche - Technical Dissection

Release year: 1993

Motion is Avalanche's MS-DOS/GUS 100K intro from Assembly 1993. The Assembly 1993 PC intro results place it fifth, after Eclipse, Debut, Tangle, and Locomotion. The first three and Locomotion are already covered in this series, so Motion is the next natural Assembly 1993 "best intro" target.

The production contains adult material; this writeup stays on the technical structure and does not reproduce the visual content.

Public references:

Offset notation:

This is not a source-level reconstruction. Names for visual parts are inferred from code shape, frame scheduling, and data flow.

Archive Identity

The Assembly party directory has both the original multipart archive and a later fixed archive:

eae0a34dfc366fa92fff155dada715e937bb3cd07af1d065188b224d35631713  motion.zip
e6cebbe2a9c92ee1ce1cf38ce62da3fcf2c3dddc9f0f2aa8df754a0d5f3ef564  motionf.zip
98595c41a1ffcd88b149de89f4dcc5b64dfc9f35c31952f532aaaabe0a6c701a  motion.diz
261eb889500c7a12d56f77e190fb2ddfa1aec295a50f945de9c5edbab70480a1  motionf.diz
b9fed2e36d7ee73cbc8b0fbd22f6b600cb1f11d73034b9c37518b390cec7940c  asm93-results.txt

The original motion.zip contains a tiny launcher, two MZ program parts, a MOD, and a batch file:

DRITT.001      3,211 bytes
INTRO.COM        399 bytes
INTRO.MOD     29,178 bytes
KLABB.002     42,972 bytes
RUNME.BAT         62 bytes

The batch file says the GUS must be at IRQ 7 and I/O 240h, then runs INTRO.COM. That launcher has strings for dritt.001 and klabb.002, so the party release is a small loader plus external part files and module.

The fixed archive contains a single packed intro executable and readme helpers:

FILE_ID.DIZ      204 bytes
MOTION.EXE    87,182 bytes
README.1ST       496 bytes
README.EXE     7,401 bytes
README.ZAX       543 bytes

The fixed text says the release fixes the Gravis UltraSound autodetect routine. That matches the binary: the final MOTION.EXE contains a direct GF1 base-port probe and no longer depends on the party batch file's fixed port/IRQ note.

Hashes for the fixed payload:

016b17087049a23c2e3495909893f7e25bd7881bd6335b8f3de9c369508e9764  MOTION.EXE
735551c164f16edf6987c8fa40bfe04673ec52e83e437cb75121f3df1c5fdec5  README.1ST
0df180cea2c3cdfe3dde5041b6761b4922300a049e1bbd587e2bab1c71360127  README.EXE
4d886949af11a05488ca1ae77eace748769569a2b044b5766ea335ac1f79004b  README.ZAX

PKLITE Expansion And MZ Layout

MOTION.EXE is PKLITE-packed. UNP 4.12 identifies it as:

DOS file size:   87182
file-structure:  executable (EXE)
EXE part sizes:  header 160 bytes, image 57844 bytes, overlay 29178 bytes
processed with:  PKLITE V1.12 <L>
new file size:   322204

The expanded executable hash:

c09ae5a2798d6599cd5ab9805b3a89d416dcb972a4f2819db983ef7bb40985ad  UNP-expanded MOTION.EXE

Expanded MZ layout:

file size:          322,204 bytes
computed MZ image:  293,026 bytes
header bytes:           512 bytes
load image bytes:   292,514 bytes
overlay bytes:       29,178 bytes
relocations:             46
initial CS:IP:      3a88:9c95
initial SS:SP:      0f65:0400
entry file offset:  280,341

The load image and overlay hashes:

9dec280590a4a935b4f42a775ab51a72e1ad0a6e99e985c70b201b2107d5f732  motion-load.bin
6c700ec62beb374cbd1ec57fc998f0524ee32ec80f68542d13adb7f8efae2119  motion-overlay.bin

The overlay is the module-sized tail. It differs from the original INTRO.MOD in only 112 bytes. The changes are mostly the MOD title and sample name text being blanked or partly overwritten; the pattern/sample structure remains the party module. For example, the original starts with Hockylir, while the fixed overlay starts with zeroes but keeps the same sample length fields.

README.EXE is also PKLITE-packed:

DOS file size:   7401
processed with:  PKLITE V1.12 <S>
new file size:   34179

It is a readme viewer and not part of the intro runtime.

Runtime Check

The fixed executable was run briefly under DOSBox-X with:

gus=true
gusbase=240
gusirq=7
gusdma=3

The save state says:

Program_Name: MOTION
Machine_Type: MCH_VGA
Memory_Size:  4096
Time_Stamp:   2026-06-14 23:55

Several helper byte sequences from motion-load.bin appear in guest memory at a common physical base around 0x82f8, so the expanded image and static offsets line up with the running program after relocation.

Main Schedule

The program entry is at expanded+0x44515. It first preserves the incoming ES, sets up a pointer near the load image end, probes the GUS, calls the music interface, registers a callback table, then executes a fixed chain of visual parts.

44515  mov  cs:[1a87],es
4451a  mov  ax,476bh
4451d  inc  ax
4451e  mov  cs:[1a89],ax
44522  call 44465h          ; GUS base-port autodetect
44525  jb   445ceh          ; continue silent/partial if probe failed
44529  mov  byte ptr cs:[8ae7],1
4452f  mov  ax,cs:[1a9a]
44533  mov  dx,cs:[1a98]
44538  xor  bx,bx
4453b  xor  cx,cx
4453d  xor  si,si
4453f  xor  di,di
44541  call dword ptr cs:[1042h]  ; sound library call gate
4454d  mov  cs:[1a9a],si
44552  mov  cs:[1a98],di

The callback registration block stores four small records at cs:0028:

44572  mov  word ptr cs:[0028],0000h
44579  mov  ax,cs:[0040]
4457d  mov  cs:[002a],ax
44581  mov  word ptr cs:[002c],1491h
44588  mov  word ptr cs:[002e],cs
4458d  mov  word ptr cs:[0030],165fh
44594  mov  word ptr cs:[0032],cs
44599  mov  word ptr cs:[0034],16dfh
445a0  mov  word ptr cs:[0036],cs
445a5  mov  word ptr cs:[0038],0006h
445ac  mov  word ptr cs:[003a],0005h
445b3  mov  word ptr cs:[003e],0000h
445ba  mov  word ptr cs:[003c],0000h
445c1  mov  cx,cs
445c3  mov  si,0028h
445c6  mov  bx,0004h
445c9  call dword ptr cs:[1042h]

After that, the intro is just a straight-line part list:

432e0  keyboard IRQ hook
42320  bitplane dot/line path
421cd  logo/fade path
410fb  vector/object path using the shared span engine
3e09b  picture/palette path
41b8e  further vector/picture part
3d021  text/bit-mask path
3feef  further part
42b15  further part
402ca  further part
430d0  further part
42c90  final part
4439e  restore text screen
43303  restore keyboard vector

Finally it sets BIOS text mode, restores a saved text page to B800, stops the GUS path if it was started, restores the keyboard vector, and exits through DOS int 21h, AH=4Ch.

GUS Autodetect

The fixed archive's important bugfix is visible at expanded+0x44465. The code tries several common GF1 base-port encodings by writing to a pair of GF1 registers, reading back a test byte, and deciding whether the mirror behaves like a GUS.

The base candidates are:

44465  mov  word ptr cs:[1a98],0323h
4446c  call 4440dh
44473  mov  word ptr cs:[1a98],0343h
4447a  call 4440dh
44481  mov  word ptr cs:[1a98],0313h
44488  call 4440dh
4448f  mov  word ptr cs:[1a98],0333h
44496  call 4440dh
4449d  mov  word ptr cs:[1a98],0353h
444a4  call 4440dh
444ab  mov  word ptr cs:[1a98],0363h
444b2  call 4440dh
444b9  sub  word ptr cs:[1a98],0103h
444c0  stc
444c1  ret
444c2  sub  word ptr cs:[1a98],0103h
444c9  clc
444ca  ret

The values are stored as GF1 register port bases with 0103h later subtracted, so a stored 0343h resolves to a card base of 0240h. That matches the original party BAT warning.

The core probe routine enables register access, writes AAh, writes 55h, reads back, clears the test byte, and checks whether the first value stuck:

4440d  mov  dx,cs:[1a98]
44412  mov  al,4ch
44414  out  dx,al
44415  add  dl,02h
44418  xor  al,al
4441a  out  dx,al
4441e  call 443bah
44421  call 443bah
44424  mov  al,4ch
44426  out  dx,al
44427  add  dl,02h
4442a  mov  al,01h
4442c  out  dx,al
44430  xor  cl,cl
44432  xor  bx,bx
44434  mov  al,0aah
44436  call 443ech
44439  inc  cl
4443b  mov  al,55h
4443d  call 443ech
44440  dec  cl
44442  call 443cfh
44445  push ax
44446  xor  al,al
44448  call 443ech
4445c  pop  ax
4445d  cmp  al,0aah
44463  stc
44464  ret

The read/write helpers are tiny and direct:

443cf  mov  dx,cs:[1a98]
443d4  mov  al,43h
443d6  out  dx,al
443d7  inc  dl
443d9  mov  ax,bx
443db  out  dx,ax
443dc  dec  dl
443de  mov  al,44h
443e0  out  dx,al
443e1  add  dl,02h
443e4  mov  al,cl
443e6  out  dx,al
443e7  add  dl,02h
443ea  in   al,dx
443eb  ret
443ec  mov  dx,cs:[1a98]
443f1  mov  ch,al
443f3  mov  al,43h
443f5  out  dx,al
443f6  inc  dl
443f8  mov  ax,bx
443fa  out  dx,ax
443fb  dec  dl
443fd  mov  al,44h
443ff  out  dx,al
44400  add  dl,02h
44403  mov  al,cl
44405  out  dx,al
44406  add  dl,02h
44409  mov  al,ch
4440b  out  dx,al
4440c  ret

This is not a generic environment-variable parse. It pokes the hardware, therefore the fixed archive really did move the GUS dependency from a manual BAT-file assumption into binary autodetect.

Keyboard Exit Hook

expanded+0x432e0 hooks interrupt 9, saving the old vector from the real-mode IVT:

432e0  cli
432e1  xor  ax,ax
432e3  mov  es,ax
432e5  mov  ax,es:[0024h]
432e9  mov  cs:[8962h],ax
432ed  mov  ax,es:[0026h]
432f1  mov  cs:[8964h],ax
432f5  mov  ax,8a02h
432f8  mov  es:[0024h],ax
432fc  mov  word ptr es:[0026h],cs
43301  sti
43302  ret

The interrupt handler sends EOI to the PIC, sets an exit flag on the first activation path, and jumps into the program exit tail:

432c2  mov  al,20h
432c4  out  20h,al
432c6  pop  ax
432c7  cmp  byte ptr cs:[8a01h],0
432cd  jne  432dah
432d1  mov  byte ptr cs:[8a01h],1
432d7  jmp  44624h
432da  mov  al,20h
432dc  out  20h,al
432de  pop  ax
432df  iret

The restore routine puts the saved vector back:

43303  cli
43304  xor  ax,ax
43306  mov  es,ax
43308  mov  ax,cs:[8962h]
4330c  mov  bx,cs:[8964h]
43311  mov  es:[0024h],ax
43315  mov  es:[0026h],bx
4331a  sti
4331b  ret

VGA Timing And Page Flip Helper

The central frame helper is expanded+0x3d161. It combines a retrace wait, a CRTC start-offset update, and a software swap of two page offset pairs.

3d161  mov  bx,cs:[2797h]
3d166  add  bx,cs:[279dh]
3d16b  mov  dx,03dah
3d16e  in   al,dx
3d16f  test al,01h
3d171  jne  3d16eh
3d173  call 3d1bah
3d176  mov  dx,03dah
3d179  cli
3d17a  in   al,dx
3d17b  and  al,08h
3d17d  jne  3d17ah
3d17f  in   al,dx
3d180  and  al,08h
3d182  je   3d17fh
3d184  sti

The display start is written through CRTC registers 0Ch/0Dh:

3d1ba  cli
3d1bb  mov  dx,03d4h
3d1be  mov  ah,bh
3d1c0  mov  al,0ch
3d1c2  out  dx,ax
3d1c3  mov  ah,bl
3d1c5  inc  al
3d1c7  out  dx,ax
3d1c8  sti
3d1c9  ret

After the retrace wait, it swaps two VRAM page offsets and two backing segment values, then derives the active drawing segment from the page offset:

3d185  mov  bx,cs:[2797h]
3d18a  mov  ax,cs:[2795h]
3d18e  mov  cs:[2797h],ax
3d192  mov  cs:[2795h],bx
3d197  mov  bx,cs:[279bh]
3d19c  mov  ax,cs:[2799h]
3d1a0  mov  cs:[279bh],ax
3d1a4  mov  cs:[2799h],bx
3d1a9  mov  cs:[2793h],ax
3d1ad  mov  ax,cs:[279dh]
3d1b1  shr  ax,04h
3d1b4  add  cs:[2793h],ax
3d1b9  ret

This helper is called repeatedly by the visual parts. It is the timing spine of the intro.

DAC Upload And Fades

The basic retrace-only wait used by slow fades is expanded+0x3c31e:

3c31e  mov  dx,03dah
3c321  in   al,dx
3c322  and  al,08h
3c324  jne  3c321h
3c326  cli
3c327  in   al,dx
3c328  and  al,08h
3c32a  je   3c327h
3c32c  sti
3c32d  ret

There are several palette upload paths. The small full-DAC path at expanded+0x3df38 waits for vertical retrace, writes DAC index 0, then streams 0x180 bytes to 3C9h:

3df38  mov  dx,03dah
3df3b  in   al,dx
3df3c  and  al,08h
3df3e  je   3df3bh
3df40  mov  dx,03c8h
3df43  xor  al,al
3df45  out  dx,al
3df46  inc  dx
3df47  mov  cx,0180h
3df4a  rep  outs dx,byte ptr ds:[si]
3df4c  mov  dx,03dah
3df4f  in   al,dx
3df50  and  al,08h
3df52  je   3df4fh
3df54  mov  dx,03c9h
3df57  mov  cx,0180h
3df5a  rep  outs dx,byte ptr ds:[si]
3df5c  ret

The fade routine at expanded+0x4218f clamps a 768-byte source palette toward black by subtracting an increasing byte at 78d0h, stores the result in 75d0h, then uploads all 768 RGB bytes:

4218f  mov  cx,0300h
42192  xor  bx,bx
42195  mov  al,3fh
42197  sub  al,cs:[78d0h]
4219c  cmp  al,cs:[bx+9daeh]
421a1  ja   421aah
421a5  mov  al,cs:[bx+9daeh]
421aa  mov  cs:[bx+75d0h],al
421af  inc  bx
421b0  loop 42195h
421b2  add  byte ptr cs:[78d0h],02h
421b8  mov  dx,3a88h
421bb  mov  ds,dx
421bd  mov  si,75d0h
421c0  mov  dx,03c8h
421c3  xor  al,al
421c5  out  dx,al
421c6  inc  dx
421c7  mov  cx,0300h
421ca  rep  outs dx,byte ptr ds:[si]
421cc  ret

The long part at 421cd uses this to do a staged logo reveal and fade. It waits 70 retraces, sets a palette entry, clears a work screen, blits through 3cfc2, repeatedly calls the retrace helper, and finally decrements palette components in-place before each DAC upload:

422b2  push cx
422b3  mov  si,a6deh
422b6  mov  di,a6deh
422b9  add  si,0003h
422bc  add  di,0003h
422bf  mov  cx,001fh
422c2  lods al
422c3  cmp  al,02h
422c5  jbe  422cbh
422c9  dec  al
422cb  stos al
...
422e5  mov  si,a6deh
422e8  mov  dx,03c8h
422eb  xor  al,al
422ed  out  dx,al
422ee  inc  dx
422ef  mov  cx,0060h
422f2  rep  outs dx,byte ptr ds:[si]
422f4  pop  cx
422f5  loop 422b2h

This is typical early PC demo timing: the CPU waits for display timing and streams DAC bytes directly rather than abstracting the palette through any library layer.

Planar Image Blit

The helper at expanded+0x3cfc2 copies a chunky-ish source rectangle into a planar Mode X destination. It selects one plane at a time with Sequencer map-mask register 02h, walks 80-byte VGA rows, and moves one byte per source pixel column group.

3cfc2  mov  dx,03c4h
3cfc5  mov  ax,0102h
3cfc8  mov  cx,0004h        ; four planes
3cfcb  push cx
3cfcc  out  dx,ax           ; map mask in AH
3cfcd  mov  bx,0140h
3cfd0  mov  cx,0050h        ; 80 byte columns
3cfd3  mov  si,cs:[273eh]
3cfd8  mov  di,cs:[273ch]
3cfdd  push cx
3cfde  mov  cx,cs:[2740h]   ; rows
3cfe3  movsb
3cfe4  add  si,013fh
3cfe8  add  di,004fh
3cfeb  loop 3cfe3h
3cfed  pop  cx
3cfee  add  word ptr cs:[273eh],4
3cff4  inc  word ptr cs:[273ch]
3cff9  loop 3cfd3h
3cffb  sub  word ptr cs:[273ch],0050h
3d001  dec  bx
3d002  sub  word ptr cs:[273eh],bx
3d007  shl  ah,1
3d009  pop  cx
3d00a  loop 3cfcbh
3d00c  ret

The important trick is that the source pointer advances by 4 between plane passes, while the destination advances one byte at a time within each plane. That converts interleaved source columns into VGA planes without a per-pixel plane test.

Bit-Mask Text / Strip Path

The part at expanded+0x3d021 is a small bit-mask renderer. It sets a low palette, writes a CRTC start value, clears a 40-dword band, waits for retrace, and uses byte masks from a table at 289bh to emit packed bits to A000.

3d0d3  mov  cx,0014h
3d0d6  xor  di,di
3d0d8  xor  ax,ax
3d0da  stosw
3d0db  push di
3d0dc  dec  di
3d0dd  dec  di
3d0de  push cx
3d0df  mov  al,[bx+289bh]
3d0e3  xor  ah,ah
3d0e5  mov  bp,ax
3d0e7  inc  bx
3d0f1  mov  dx,03c4h
3d0f4  mov  ax,0102h
3d0f7  out  dx,ax
3d0f8  xor  al,al
3d0fa  mov  ah,80h
3d0fc  or   bp,bp
3d0fe  je   3d10ch
3d102  or   al,ah
3d104  dec  bp
3d105  shr  ah,1
3d107  jae  3d0fch
3d109  stosb
3d10a  jmp  3d0f8h
3d10c  stosb
3d10d  pop  cx
3d10e  pop  di
3d10f  loop 3d0d8h

It is a run-to-bitmask converter: each source byte is a run length, AH is the current output bit, and bytes are emitted whenever the bit cursor falls through carry. For this sort of mask-text display, it is smaller than storing a ready 320-pixel bitmap for each row.

Dot Interpolator

The part starting at expanded+0x42320 uses mode 0Dh/planar 320x200-style output and a point interpolator. expanded+0x42533 builds per-point deltas from two coordinate streams; expanded+0x424ac advances them and plots each point.

Setup:

42320  mov  word ptr cs:[279dh],0000h
42327  mov  ax,000dh
4232a  int  10h
42331  call 42533h          ; build point deltas
42334  mov  cx,cs:[7a78h]
42339  call 3d161h          ; flip/wait
...
4234e  call 424ach          ; draw all points
42351  call 3d161h
42355  loop 4233ch

Delta setup for one point:

42539  mov  ax,[di+0000h]       ; current x
4253d  mov  [si-54b0h],ax       ; x accumulator
42541  mov  ax,[bx+2588h]       ; target x
4254d  mov  ax,[bx+2588h]
42551  sub  ax,[di+0000h]
42555  mov  cx,cs:[7a78h]       ; frame count
4255a  xor  dx,dx
4255c  idiv cx
4255e  mov  [si+7310h],dx       ; x fractional remainder
42562  mov  [si-6770h],ax       ; x integer step
42566  mov  word ptr [si-2f30h],1

The y path is similar, but multiplies by 40 because the mode-0Dh/bitplane row is 40 bytes wide:

42592  mov  ax,[di+12c4h]
42596  imul ax,ax,0028h
42599  mov  [si-41f0h],ax
4259d  mov  ax,[bx+398ch]
425ad  sub  ax,[di+12c4h]
425b1  mov  cx,cs:[7a78h]
425b6  xor  dx,dx
425b8  idiv cx
425ba  mov  [si-7a30h],dx
425be  imul ax,ax,0028h
425c1  add  [si-6770h],ax
425c5  mov  word ptr [si-1c70h],0028h

The per-frame updater adds integer and remainder components. When a remainder crosses the frame count, it emits one extra step:

424b5  mov  ax,[si-6770h]
424b9  add  [si-54b0h],ax
424bd  mov  ax,[si+7310h]
424c1  add  [si+4d90h],ax
424c5  cmp  [si+4d90h],cx
424c9  jb   424d9h
424cd  sub  [si+4d90h],cx
424d1  mov  ax,[si-2f30h]
424d5  add  [si-54b0h],ax

The plotted pixel path clips to 320x200 and then turns x into byte offset plus plane bit:

424f5  mov  di,[si-41f0h]       ; y*40 accumulator
424f9  mov  dx,[si-54b0h]       ; x accumulator
424fd  cmp  dx,013fh
42501  ja   42524h
42505  cmp  di,1f18h
42509  ja   42524h
4250d  mov  bl,dl
4250f  shr  dx,03h              ; byte x
42512  add  di,dx
42514  and  bl,07h              ; bit in byte
42517  mov  al,cs:[bx+7a7ch]    ; bit mask
4251c  add  di,cs:[2797h]
42521  or   es:[di],al

This is a classic integer morph/plot loop: integer step plus remainder, no floating point, and the final write is a single OR into a bitplane byte.

Vector Projection

expanded+0x3f78d is a 16-bit/32-bit mixed fixed-point rotation pipeline. It uses sine/cosine tables at 43cdh and 496dh and angle counters at 3869h, 386bh, and 386dh. The code uses imul, forms a 32-bit result from DX:AX, and shifts right by 14.

One rotation pair:

3f78d  mov  si,cs:[3869h]
3f792  add  si,si
3f794  mov  ax,cs:[si+496dh]
3f799  imul cx
3f79b  mov  di,dx
3f79d  shl  edi,10h
3f7a1  mov  di,ax
3f7a3  mov  ax,cs:[si+43cdh]
3f7a8  imul bp
3f7aa  shl  edx,10h
3f7ae  mov  dx,ax
3f7b0  sub  edi,edx
3f7b3  sar  edi,0eh
3f7b7  mov  es,di

The paired expression computes:

rot_x = (cos(angle) * x - sin(angle) * y) >> 14
rot_y = (sin(angle) * x + cos(angle) * y) >> 14

It repeats this for three axes. The use of ES as a temporary storage register is odd-looking but valid in real mode: it gives the code one more 16-bit slot without a memory spill.

Perspective projection is handled at expanded+0x3f8c8:

3f8c8  mov  ax,cs:[3867h]
3f8cc  sub  ax,bp
3f8ce  mov  ds,ax
3f8d0  mov  ax,cs:[3867h]
3f8d4  imul bx
3f8d8  idiv bx
3f8da  mov  bx,ax
3f8dc  add  bx,cs:[278dh]
3f8e1  mov  ax,cs:[3867h]
3f8e5  imul cx
3f8e9  idiv cx
3f8eb  mov  cx,ax
3f8ed  add  cx,cs:[278fh]

Conceptually:

denom = camera_distance - z
screen_x = center_x + camera_distance * x / denom
screen_y = center_y + camera_distance * y / denom

The surrounding vector parts update the angle counters modulo 0x168, so the table length is 360 decimal entries.

Polygon Clipping

The polygon clipper starts at expanded+0x3d86f. It takes a vertex list at 2d89h, clips against left, right, top, and bottom edges, and rewrites the surviving polygon into alternating buffers at 2d89h and 2de9h.

Left-edge clip excerpt:

3d883  mov  cx,[si]
3d885  mov  dx,[si+02h]
3d888  add  si,0004h
3d88b  cmp  ax,0000h
3d88e  jl   3d89fh
3d890  cmp  cx,0000h
3d893  jl   3d8c4h
3d895  mov  [di],cx
3d897  mov  [di+02h],dx
3d89a  add  di,0004h

Entering the clipping edge computes an intersection by multiplying the edge delta and dividing by the x delta:

3d8a4  push cx
3d8a5  push dx
3d8a6  sub  dx,bx
3d8a8  sub  cx,ax
3d8aa  imul dx
3d8ac  idiv cx
3d8ae  sub  bx,ax
3d8b0  sub  ax,ax
3d8b4  mov  [di],ax
3d8b6  mov  [di+02h],bx
3d8b9  mov  [di+04h],cx
3d8bc  mov  [di+06h],dx
3d8bf  add  di,0008h

Right, top, and bottom edges use the same pattern with bounds stored at 2fedh and 2febh. If the polygon is fully clipped away, the vertex count at 2e69h becomes zero and the fill step is skipped.

Edge Table And Span Fill

The rasterizer converts clipped polygon edges into a per-y edge table at 29c9h. expanded+0x3dae7 initializes the y bounds and then walks all polygon edges.

The low-slope branch is Bresenham-style. It keeps an error term in SI, a doubled minor-axis delta in DX, and advances the edge table pointer by 2e7bh when the y position changes:

3db83  mov  bx,cx
3db85  inc  cx
3db86  shl  dx,1
3db88  mov  si,dx
3db8a  sub  si,bx
3db8c  neg  bx
3db8e  add  bx,si
3db90  cmp  ax,[di]
3db92  jae  3db96h
3db94  mov  [di],ax
3db96  inc  ax
3db97  or   si,si
3db99  jns  3dba1h
3db9b  add  si,dx
3db9d  loop 3db90h
3dba1  add  di,cs:[2e7bh]
3dba6  add  si,bx
3dba8  loop 3db90h

The high-slope branch flips the loop so y is the major axis:

3dbe0  mov  bx,cx
3dbe2  mov  cx,dx
3dbe4  inc  cx
3dbe5  shl  bx,1
3dbe7  mov  si,bx
3dbe9  sub  si,dx
3dbeb  neg  dx
3dbed  add  dx,si
3dbef  mov  [di],ax
3dbf1  add  di,cs:[2e7bh]
3dbf6  or   si,si
3dbf8  jns  3dc00h
3dbfa  add  si,bx
3dbfc  loop 3dbefh
3dc00  inc  ax
3dc01  add  si,dx
3dc03  loop 3dbefh

expanded+0x3d717 then fills spans in 4-plane Mode X. It has three cases: same byte, neighboring bytes, and multi-byte span.

The multi-byte case:

3d755  mov  ax,di
3d757  shr  ax,02h
3d75a  inc  ax
3d75b  mov  cx,dx
3d75d  shr  cx,02h
3d760  sub  cx,ax
3d766  mov  bx,di
3d768  and  bx,0003h
3d76b  mov  ah,cs:[bx+29c1h]   ; leading plane mask
3d770  mov  al,02h
3d772  mov  bx,dx
3d774  and  bx,0003h
3d777  shr  di,02h
3d77a  add  di,cs:[2e74h]
3d77f  mov  dx,03c4h
3d782  out  dx,ax
3d783  mov  al,cs:[2e6bh]
3d787  stosb
3d788  mov  ax,0f02h           ; full planes for middle bytes
3d78b  out  dx,ax
3d78c  mov  al,cs:[2e6bh]
3d790  mov  ah,al
3d792  test di,0001h
3d796  je   3d79ah
3d798  stosb
3d799  dec  cx
3d79a  shr  cx,1
3d79e  rep  stosw
3d7a3  mov  ah,cs:[bx+29c5h]   ; trailing plane mask
3d7a8  mov  al,02h
3d7aa  out  dx,ax
3d7ab  mov  al,cs:[2e6bh]
3d7af  stosb

The same-byte case ANDs the leading and trailing masks:

3d7b2  mov  bx,di
3d7b4  and  bx,0003h
3d7b7  mov  ah,cs:[bx+29c1h]
3d7be  mov  bx,dx
3d7c0  and  bx,0003h
3d7c3  and  ah,cs:[bx+29c5h]
3d7c8  shr  di,02h
3d7cb  add  di,cs:[2e74h]
3d7d0  mov  dx,03c4h
3d7d3  out  dx,ax
3d7d4  mov  al,cs:[2e6bh]
3d7d8  mov  es:[di],al

This is the core inner loop: clip to screen, build x extents per y, split the span into plane masks, use rep stosw for the middle, and touch the Sequencer only at the span boundaries.

Dirty Rectangle Clears

The span engine has paired clear routines. expanded+0x3dd2f computes a dirty rectangle for 4-plane 320x240-ish drawing and clears the minimum bounding range of bytes:

3dd33  mov  ax,013fh
3dd37  mov  cs:[2e6ch],ax
3dd3b  mov  word ptr cs:[2e6eh],0
3dd42  mov  ax,00efh
3dd45  mov  cs:[2e70h],ax
...
3ddd4  mov  si,cs:[34adh]    ; row pitch
3ddd9  mov  ax,cs:[2e70h]
3dddf  mul  si
3dde1  mov  cx,cs:[2e6ch]
3dde6  shr  cx,02h
3dde9  add  ax,cx
3ddeb  mov  di,ax
...
3de05  mov  al,cs:[2e6bh]
3de09  mov  ah,al
3de0b  mov  cx,bx
3de0d  push di
3de0e  test di,0001h
3de12  je   3de16h
3de14  stosb
3de15  dec  cx
3de16  shr  cx,1
3de1a  rep  stosw
3de1f  pop  di
3de20  add  di,si
3de22  dec  dx
3de23  jne  3de0bh

expanded+0x3de29 is the bitplane sibling for 40-byte rows; it uses shr x,3 and a row pitch of 28h.

That is why the vector parts do not need to clear an entire 64 KB page each frame. They clear just the old bounding area, render the current polygon spans, then flip.

Why It Is Interesting

Motion is not as polished as Eclipse or Debut, but its code is a good snapshot of a 1993 PC intro trying to be more than a static picture:

The most important inner loop is the span filler. It shows the exact PC/VGA tradeoff of the time: spend a little code on plane masks and row extents so the middle of each span becomes a tight rep stosw, and keep whole-screen copying out of the frame budget.