+ ; precalculate spawn points
+ mov ax, es
+ mov ds, ax ; decompressed image -> ds:bx
+ xor bx, bx
+ mov ax, spawn_pos
+ shr ax, 4
+ mov es, ax ; spawn_pos table segment -> es:dx
+ xor edx, edx
+
+ mov cx, 64000
+.calcspawn_loop:
+ mov al, [bx]
+ test al, 0x80
+ jz .notspawn
+ mov [es:edx * 2], bx
+ inc edx
+.notspawn:
+ inc bx
+ dec cx
+ jnz .calcspawn_loop
+ ; update num_spawn_pos
+ xor ax, ax
+ mov ds, ax
+ mov [num_spawn_pos], edx
+
+ mov ax, framebuf >> 4
+ mov fs, ax ; fs will point to the off-screen framebuffer
+
+ ; effect main loop
+.mainloop:
+ mov cx, spawn_rate
+.spawn: call rand
+ xor edx, edx
+ div dword [num_spawn_pos] ; edx <- rand % num_spawn_pos
+ mov bx, [es:edx * 2] ; grab one of the spawn positions
+
+ ; animate the spawn position
+ xor ax, ax
+ mov al, [frameno]
+ mov bp, ax
+ movsx ax, byte [bp + sintab]
+ sar ax, 3
+ add bx, ax
+
+ mov byte [fs:bx], 0xff ; plot a pixel there
+ dec cx
+ jnz .spawn
+
+ ; blur the screen upwards
+ mov ax, fs
+ mov ds, ax ; let's use ds for this to avoid long instructions
+ xor bx, bx ; use: pointer
+ xor ax, ax ; use: pixel accum
+ xor dx, dx ; use: second pixel
+.blurloop:
+ xor ax, ax
+ mov al, [bx]
+ mov dl, [bx + 320]
+ add ax, dx
+ mov dl, [bx + 319]
+ add ax, dx
+ mov dl, [bx + 321]
+ add ax, dx
+ mov dl, [bx + 640]
+ add ax, dx
+ xor dx, dx
+ mov cx, 5
+ div cx
+ mov [bx], al
+
+ inc bx
+ cmp bx, 64000 - 640
+ jnz .blurloop
+
+
+ ; wait until the start of vblank
+.waitvblank:
+ mov dx, 3dah
+ in al, dx
+ and al, 8
+ jz .waitvblank
+
+ ; copy to screen
+ push es
+ mov ax, 0a000h
+ mov es, ax
+ xor di, di
+ mov ax, fs
+ mov ds, ax
+ xor si, si
+ mov ecx, 16000
+ rep movsd
+ pop es
+ xor ax, ax
+ mov ds, ax
+
+ inc word [frameno]