KAZIK/tools/tests/hell_fps_test.inc
Игорь Федосеев ce5121debd feat: add native hell expedition
Add a painting entrance, Freedoom combat and Doom-style HUD.

Persist the one-time clear reward in signed saves without changing file sizes. Include native regression checks and synchronized game/launcher builds.
2026-11-01 22:29:09 +03:00

509 lines
13 KiB
PHP

.data
hfTestStage dd 1
hfTestIo dd 0
hfTestFrame db 'build\scratch\hell-entry.bgra',0
hfTestCourt db 'build\scratch\hell-court.bgra',0
hfTestHall db 'build\scratch\hell-portal.bgra',0
hfTestWin db 'build\scratch\hell-victory.bgra',0
hfTestPreview db 'build\scratch\hf-prev.dat',0
hfTestProfileDir db 'build\scratch',0
hfTestProfileName db 'hell-profile.dat',0
hfTestBoss dd 0
hfTestStrafe dd 5000
.code
HFTestAimBoss PROC uses esi edi ebx
local vx:DWORD,vy:DWORD,best:DWORD
mov esi,hfTestBoss
mov eax,[esi].HFEnemy.x
sub eax,playerX
sar eax,8
mov vx,eax
mov eax,[esi].HFEnemy.y
sub eax,playerY
sar eax,8
mov vy,eax
mov best,80000000h
xor edi,edi
hf_test_aim_angle:
mov eax,tableCos[edi*4]
sar eax,8
imul eax,vx
mov ebx,eax
mov eax,tableSin[edi*4]
sar eax,8
imul eax,vy
add eax,ebx
cmp eax,best
jle @F
mov best,eax
mov playerAngle,edi
@@:
inc edi
cmp edi,512
jb hf_test_aim_angle
ret
HFTestAimBoss ENDP
HFTestCapture PROC uses ebx path:DWORD
invoke CreateFileA,path,40000000h,0,0,2,80h,0
mov ebx,eax
invoke WriteFile,ebx,OFFSET screenBuffer,SCREEN_SIZE*4,OFFSET hfTestIo,0
invoke CloseHandle,ebx
ret
HFTestCapture ENDP
RunHellFPSTest PROC uses esi edi ebx
call InitHall3D
call LoadTextures3D
call LoadHallAssets3D
call InitializeCasinoSprites3D
mov hfTestStage,2
call HFLoad
test eax,eax
jz hf_test_fail
mov playerX,HF_PORTAL_X
mov playerY,29*FP_1
mov playerAngle,128
mov engineMode,MODE_WALK
call UpdateCamera3D
call CheckHallInteractions3D
; Approach must be within the standard interaction distance.
mov playerY,FP_1*294/10
call CheckHallInteractions3D
cmp interactTargetId,INTERACT_HELL_FPS
jne hf_test_fail
invoke CanWalk3D,HF_PORTAL_X,FP_1*294/10
cmp eax,1
jne hf_test_fail
; Old standing frame no longer blocks the west aisle.
invoke CanWalk3D,FP_1*25/10,11*FP_1
cmp eax,1
jne hf_test_fail
mov playerX,4*FP_1
mov playerY,12*FP_1
mov playerAngle,256
call UpdateCamera3D
mov interactTargetId,0
call CheckHallInteractions3D
cmp interactTargetId,INTERACT_HELL_FPS
je hf_test_fail
mov playerX,HF_PORTAL_X
mov playerY,FP_1*255/10
mov playerAngle,128
call UpdateCamera3D
call RenderCeilingAndFloor
call RenderWalls
call PrepareCityDepth3D
call RenderHall3D
invoke HFTestCapture,OFFSET hfTestHall
mov hfOldX,HF_PORTAL_X
mov hfOldY,FP_1*294/10
mov hfOldAngle,128
mov hfOldMap,OFFSET casinoMap
mov hfOldFloor,100
mov hfOldLevel,0
mov hfOldEye,HALL_STANDING_EYE
mov hfOldCeiling,FP_1*5/2
call HFReset
mov hfTestStage,3
call RenderHellFPS3D
invoke HFTestCapture,OFFSET hfTestFrame
invoke HFLineClear,3*FP_1+FP_HALF,26*FP_1+FP_HALF,7*FP_1+FP_HALF,26*FP_1+FP_HALF
cmp eax,1
jne hf_test_fail
invoke HFLineClear,3*FP_1+FP_HALF,26*FP_1+FP_HALF,13*FP_1,26*FP_1+FP_HALF
test eax,eax
jnz hf_test_fail
invoke CanWalk3D,0,0
test eax,eax
jnz hf_test_fail
mov hfTestStage,4
; Same hitscan must stop at wall, then damage the nearest target in open space.
mov hfEnemies.x,12*FP_1
mov hfEnemies.y,26*FP_1+FP_HALF
mov hfEnemies.hp,100
mov hfEnemies[SIZEOF HFEnemy].hp,0
call HFFire
cmp hfEnemies.hp,100
jne hf_test_fail
mov hfEnemies.x,7*FP_1
mov hfCooldown,0
call HFFire
cmp hfEnemies.hp,100
jae hf_test_fail
cmp hfBullets,78
jne hf_test_fail
mov hfTestStage,5
mov bankroll,12345
invoke HFPlayerCanWalk,hfEnemies.x,hfEnemies.y
test eax,eax
jnz hf_test_fail
mov hfArmor,20
invoke HFDamagePlayer,10
cmp hfHealth,95
jne hf_test_fail
cmp hfArmor,15
jne hf_test_fail
mov hfArmor,0
invoke HFDamagePlayer,1000
cmp hfState,1
jne hf_test_fail
call HFReset
cmp hfState,0
jne hf_test_fail
cmp hfHealth,100
jne hf_test_fail
cmp bankroll,12345
jne hf_test_fail
; Natural shotgun pickup and consumption; no injected weapon ownership.
mov playerX,3*FP_1+FP_HALF
mov playerY,27*FP_1+FP_HALF
call HFPickups
cmp hfHasShotgun,1
jne hf_test_fail
cmp hfShells,16
jne hf_test_fail
call HFFire
cmp hfShells,15
jne hf_test_fail
mov hfTestStage,12
; Three separated targets: pistol hits centre only; one shell hits all three.
call HFReset
mov esi,OFFSET hfEnemies
mov ecx,HF_ENEMIES
@@:
mov [esi].HFEnemy.hp,0
add esi,SIZEOF HFEnemy
loop @B
mov hfEnemies.x,8*FP_1
mov hfEnemies.y,FP_1*103/4
mov hfEnemies.hp,1000
mov hfEnemies[SIZEOF HFEnemy].x,8*FP_1
mov hfEnemies[SIZEOF HFEnemy].y,FP_1*106/4
mov hfEnemies[SIZEOF HFEnemy].hp,1000
mov hfEnemies[2*SIZEOF HFEnemy].x,8*FP_1
mov hfEnemies[2*SIZEOF HFEnemy].y,FP_1*109/4
mov hfEnemies[2*SIZEOF HFEnemy].hp,1000
call HFFire
cmp hfEnemies.hp,1000
jne hf_test_fail
cmp hfEnemies[SIZEOF HFEnemy].hp,1000
jae hf_test_fail
cmp hfEnemies[2*SIZEOF HFEnemy].hp,1000
jne hf_test_fail
mov hfCooldown,0
mov hfWeapon,1
mov hfShells,2
call HFFire
cmp hfShells,1
jne hf_test_fail
cmp hfEnemies.hp,1000
jae hf_test_fail
cmp hfEnemies[2*SIZEOF HFEnemy].hp,1000
jae hf_test_fail
mov esi,OFFSET hfImpacts
mov ecx,HF_SHOT_PELLETS
@@:
cmp [esi].HFImpact.life,8
jne hf_test_fail
add esi,SIZEOF HFImpact
loop @B
; The whole pellet cone must stop at the separating wall.
mov hfEnemies.x,12*FP_1
mov hfEnemies.hp,1000
mov hfEnemies[SIZEOF HFEnemy].x,12*FP_1
mov hfEnemies[SIZEOF HFEnemy].hp,1000
mov hfEnemies[2*SIZEOF HFEnemy].x,12*FP_1
mov hfEnemies[2*SIZEOF HFEnemy].hp,1000
mov hfCooldown,0
call HFFire
cmp hfEnemies.hp,1000
jne hf_test_fail
cmp hfEnemies[SIZEOF HFEnemy].hp,1000
jne hf_test_fail
cmp hfEnemies[2*SIZEOF HFEnemy].hp,1000
jne hf_test_fail
cmp hfShells,0
jne hf_test_fail
mov hfCooldown,0
call HFFire
cmp hfCooldown,0
jne hf_test_fail
call HFReset
mov hfTestStage,6
mov playerX,21*FP_1+FP_HALF
mov playerY,6*FP_1+FP_HALF
call HFUse
cmp hfMap[6*32+22],188
jne hf_test_fail
mov playerX,20*FP_1+FP_HALF
mov playerY,3*FP_1+FP_HALF
call HFPickups
cmp hfKey,1
jne hf_test_fail
mov playerX,21*FP_1+FP_HALF
mov playerY,6*FP_1+FP_HALF
call HFUse
cmp hfMap[6*32+22],0
jne hf_test_fail
mov playerX,5*FP_1+FP_HALF
mov playerY,13*FP_1+FP_HALF
call HFUse
call HFUse
cmp hfSecrets,1
jne hf_test_fail
mov playerX,23*FP_1
mov playerY,27*FP_1
call HFUse
cmp hfSecrets,2
jne hf_test_fail
mov hfTestStage,7
; Real AI/projectile/pickup loop stress; no alternate Python simulation.
call HFReset
mov playerX,14*FP_1
mov playerY,21*FP_1
mov playerAngle,430
mov hfWeapon,1
mov hfHasShotgun,1
mov hfShells,80
mov hfFireHeld,1
mov ebx,1050
hf_test_combat:
mov hfHealth,100
mov hfState,0
call HFTick
dec ebx
jnz hf_test_combat
mov hfFireHeld,0
call RenderHellFPS3D
invoke HFTestCapture,OFFSET hfTestCourt
mov hfTestStage,11
; Isolated boss encounter: real pickups, finite ammo, normal damage and ticks.
call HFReset
mov esi,OFFSET hfEnemies
mov ecx,HF_ENEMIES
hf_test_isolate_boss:
cmp [esi].HFEnemy.kind,3
jne hf_test_disable_enemy
mov hfTestBoss,esi
jmp @F
hf_test_disable_enemy:
mov [esi].HFEnemy.hp,0
@@:
add esi,SIZEOF HFEnemy
loop hf_test_isolate_boss
mov playerX,3*FP_1+FP_HALF
mov playerY,27*FP_1+FP_HALF
call HFPickups
mov playerX,4*FP_1+FP_HALF
mov playerY,28*FP_1+FP_HALF
call HFPickups
mov playerX,8*FP_1+FP_HALF
mov playerY,14*FP_1+FP_HALF
call HFPickups
mov playerX,27*FP_1+FP_HALF
mov playerY,24*FP_1+FP_HALF
mov playerAngle,128
mov hfFireHeld,1
mov ebx,1400
hf_test_boss_fight:
cmp playerX,28*FP_1+FP_HALF
jl @F
mov hfTestStrafe,-5000
@@:
cmp playerX,26*FP_1+FP_HALF
jg @F
mov hfTestStrafe,5000
@@:
mov eax,playerX
add eax,hfTestStrafe
push eax
invoke HFPlayerCanWalk,eax,playerY
pop ecx
test eax,eax
jz @F
mov playerX,ecx
@@:
call HFTestAimBoss
call HFTick
cmp hfState,0
jne hf_test_fail
cmp hfKills,1
je hf_test_boss_killed
dec ebx
jnz hf_test_boss_fight
jmp hf_test_fail
hf_test_boss_killed:
mov hfFireHeld,0
mov hfTestStage,8
; Locked exit, then one-time reward, then repeat use cannot pay twice.
call HFReset
mov bankroll,10000
mov hfCompleted,0
mov playerX,28*FP_1
mov playerY,28*FP_1
call HFUse
cmp hfState,0
jne hf_test_fail
mov esi,OFFSET hfEnemies
mov ecx,HF_ENEMIES
@@:
mov [esi].HFEnemy.hp,0
add esi,SIZEOF HFEnemy
loop @B
call HFUse
cmp hfState,2
jne hf_test_fail
cmp bankroll,16660
jne hf_test_fail
call HFUse
cmp bankroll,16660
jne hf_test_fail
mov playerX,26*FP_1
mov playerY,24*FP_1
mov playerAngle,96
mov hfTicks,18000
mov hfKills,28
mov hfSecrets,2
call RenderHellFPS3D
invoke HFTestCapture,OFFSET hfTestWin
mov hfTestStage,9
; Save extension survives native signed profile round trip.
mov nativeProfilesDirPtr3D,OFFSET hfTestProfileDir
mov nativeProfileActive3D,1
invoke CopyCString,OFFSET saveFileName,OFFSET hfTestProfileName
call BuildNativeProfileDir3D
call SaveGame
mov hfCompleted,0
call LoadSave
cmp hfCompleted,1
jne hf_test_fail
cmp bankroll,16660
jne hf_test_fail
mov hfTestStage,10
; Preview save uses the same new bit without changing its 40-byte format.
mov nativeProfileActive3D,0
mov arrivalConfirmed3D,1
invoke CopyCString,OFFSET savePath3D,OFFSET hfTestPreview
call SaveProgress3D
mov hfCompleted,0
call LoadProgress3D
cmp hfCompleted,1
jne hf_test_fail
cmp bankroll,16660
jne hf_test_fail
mov arrivalConfirmed3D,0
call LeaveHellFPS3D
cmp engineMode,MODE_WALK
jne hf_test_fail
cmp playerX,HF_PORTAL_X
jne hf_test_fail
cmp hallLevel3D,0
jne hf_test_fail
call FreeHellFPS3D
xor eax,eax
ret
hf_test_fail:
mov eax,hfTestStage
ret
RunHellFPSTest ENDP
IFDEF HELL_FPS_WINDOW_TEST
.data
hfWindowShot db 'build\scratch\hell-window.bgra',0
hfMusicStatus db 'status hellmusic mode',0
hfAudioMode db 32 dup(0)
.code
RunHellFPSWindowTest PROC uses esi ebx
call InitPaths3D
call Create3DWindow
test eax,eax
jz hf_window_failed
; Keep test window modest and avoid taking keyboard/mouse focus.
cmp fullscreen3D,0
je @F
call ToggleFullscreen3D
@@:
invoke SetWindowPos,h3DWnd,0,80,80,800,640,14h
call InitHall3D
call LoadTextures3D
call LoadHallAssets3D
call InitializeCasinoSprites3D
mov playerX,HF_PORTAL_X
mov playerY,FP_1*294/10
mov playerAngle,128
mov engineMode,MODE_WALK
call BeginHellFPS3D
cmp engineMode,MODE_HELL_FPS
jne hf_window_failed
cmp hfMusicError,0
jne hf_window_failed
cmp hfSoundFailures,0
jne hf_window_failed
cmp hfVolumeError,0
jne hf_window_failed
mov settingsVolume,10
call HFUpdateMusicVolume
cmp hfVolumeError,0
jne hf_window_failed
mov eax,SIZEOF hfVolumePrefix-1
cmp DWORD PTR hfVolumeCommand[eax],00303533h ; "350", not full volume 1000
jne hf_window_failed
mov settingsVolume,0
call HFUpdateMusicVolume
cmp hfVolumeError,0
jne hf_window_failed
mov eax,SIZEOF hfVolumePrefix-1
cmp WORD PTR hfVolumeCommand[eax],0030h
jne hf_window_failed
mov settingsVolume,5
call HFUpdateMusicVolume
cmp hfVolumeError,0
jne hf_window_failed
invoke ShowWindow,h3DWnd,4
mov esi,120
hf_window_frame:
invoke PeekMessageA,OFFSET msg3D,0,0,0,PM_REMOVE
test eax,eax
jz hf_window_simulate
invoke TranslateMessage,OFFSET msg3D
invoke DispatchMessageA,OFFSET msg3D
jmp hf_window_frame
hf_window_simulate:
mov deltaTime3D,17
add animationClock3D,17
cmp esi,110
jne @F
invoke WindowEvents3D,h3DWnd,201h,1,0
@@:
cmp esi,35
jne @F
invoke WindowEvents3D,h3DWnd,202h,0,0
@@:
call HandleHellFPS3D
call RenderHellFPS3D
call Present3D
invoke Sleep,17
dec esi
jnz hf_window_frame
invoke HFTestCapture,OFFSET hfWindowShot
cmp hfBullets,80
jae hf_window_failed
cmp hfFireHeld,0
jne hf_window_failed
invoke mciSendStringA,OFFSET hfMusicStatus,OFFSET hfAudioMode,32,0
test eax,eax
jnz hf_window_failed
cmp DWORD PTR hfAudioMode,'yalp'
jne hf_window_failed
call LeaveHellFPS3D
call FreeHellFPS3D
invoke DestroyWindow,h3DWnd
xor eax,eax
ret
hf_window_failed:
call FreeHellFPS3D
mov eax,111
ret
RunHellFPSWindowTest ENDP
ENDIF