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:
- Scene.org party archive: https://archive.scene.org/pub/parties/1993/assembly93/100k/motion.zip
- Scene.org fixed archive: https://archive.scene.org/pub/parties/1993/assembly93/100k/motionf.zip
- Scene.org Assembly 1993 results: https://archive.scene.org/pub/parties/1993/assembly93/results.txt
- Demozoo production: https://demozoo.org/productions/6708/
- Pouet production: https://www.pouet.net/prod.php?which=5513
Offset notation:
packed+0x...means an offset in the original packedMOTION.EXE.expanded+0x...means an offset in the UNP-expanded final executable load image, after subtracting the 512-byte MZ header.- Runtime far pointers are discussed as segment:offset pairs only when the segment matters.
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 fixed executable folds a multipart party release into one PKLITE-packed MZ with the MOD as overlay data.
- The bugfix is real: the code probes several GF1 base candidates directly.
- The display core is built around retrace waits and CRTC start-offset flips.
- Palette effects stream DAC bytes directly, often during retrace.
- The dot/morph part uses integer quotient/remainder stepping per point.
- The vector path uses fixed-point sine/cosine tables and perspective divides.
- The polygon filler has real clipping, edge-table construction, and Mode X span splitting with leading/full/trailing plane masks.
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.