KAZIK/tools/tests/hell_fps_test.inc
Игорь Федосеев ef5ebff353 fix: align hell weapons with crosshair and HUD
Center resting barrels and anchor every firing pose to the HUD edge.

Add native framebuffer regression checks for all nine poses and update game assets, binaries and launcher integrity hash.
2026-11-01 22:41:36 +03:00

568 lines
15 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
hfTestWeaponBase0 db 'build\scratch\hell-weapon-pistol-base.bgra',0
hfTestWeaponBase1 db 'build\scratch\hell-weapon-shotgun-base.bgra',0
hfTestWeapon0 db 'build\scratch\hell-weapon-0.bgra',0
hfTestWeapon1 db 'build\scratch\hell-weapon-1.bgra',0
hfTestWeapon2 db 'build\scratch\hell-weapon-2.bgra',0
hfTestWeapon3 db 'build\scratch\hell-weapon-3.bgra',0
hfTestWeapon4 db 'build\scratch\hell-weapon-4.bgra',0
hfTestWeapon5 db 'build\scratch\hell-weapon-5.bgra',0
hfTestWeapon6 db 'build\scratch\hell-weapon-6.bgra',0
hfTestWeapon7 db 'build\scratch\hell-weapon-7.bgra',0
hfTestWeapon8 db 'build\scratch\hell-weapon-8.bgra',0
; Weapon, cooldown (-1 hides both packs for a clean reference), capture path.
hfTestWeaponCases dd 0,-1,OFFSET hfTestWeaponBase0
dd 0,0,OFFSET hfTestWeapon0
dd 0,9,OFFSET hfTestWeapon1
dd 0,7,OFFSET hfTestWeapon2
dd 0,5,OFFSET hfTestWeapon3
dd 0,3,OFFSET hfTestWeapon4
dd 1,-1,OFFSET hfTestWeaponBase1
dd 1,0,OFFSET hfTestWeapon5
dd 1,29,OFFSET hfTestWeapon6
dd 1,18,OFFSET hfTestWeapon7
dd 1,10,OFFSET hfTestWeapon8
HFTestCapture PROTO :DWORD
.code
HFTestWeaponFrames PROC uses esi ebx
local pistolPixels:DWORD,shotgunPixels:DWORD
mov eax,hfPistol.pixels
mov pistolPixels,eax
mov eax,hfShotgun.pixels
mov shotgunPixels,eax
call HFReset
mov hfHasShotgun,1
mov esi,OFFSET hfTestWeaponCases
mov ebx,11
hf_test_weapon_frame:
mov eax,[esi]
mov hfWeapon,eax
mov eax,[esi+4]
mov hfCooldown,eax
cmp eax,-1
jne @F
mov hfCooldown,0
mov hfPistol.pixels,0
mov hfShotgun.pixels,0
@@:
call RenderHellFPS3D
invoke HFTestCapture,DWORD PTR [esi+8]
mov eax,pistolPixels
mov hfPistol.pixels,eax
mov eax,shotgunPixels
mov hfShotgun.pixels,eax
add esi,12
dec ebx
jnz hf_test_weapon_frame
call HFReset
ret
HFTestWeaponFrames ENDP
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 HFTestWeaponFrames
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