Decouple gameplay from presentation and preserve signed movement fractions. Separate cinematic camera ownership with safe body handoff and transactional current-room restarts. Resample input latches on focus regain, discard modal timing debt, and attach cinematic lighting to the view to match editor preview. Verify editor 137/137 and ASM 288/288, cinematic traces, RGB bake and IPC framebuffer parity without changing project assets.
471 lines
17 KiB
PHP
471 lines
17 KiB
PHP
.data
|
|
scheduleSnapshot dd 8 dup(0)
|
|
symmetryOK dd 1
|
|
speedOK dd 1
|
|
COMM cinematicReference:BYTE:FRAME_BUFFER_SIZE
|
|
.code
|
|
ResetSimulationFixture PROC
|
|
call ResetCombatFixture
|
|
mov spriteCount, 0
|
|
mov playerX, 800
|
|
mov playerY, 800
|
|
mov playerMoveSpeed, 3
|
|
mov playerRunSpeed, 6
|
|
mov playerRadius, 16
|
|
mov keyAttack, 0
|
|
mov keyForward, 1
|
|
mov simulationTicks, 0
|
|
mov lightingTick, 0
|
|
mov gameToastTimer, 100
|
|
mov mapErrorFrames, 0
|
|
mov DWORD PTR qpcFrequency, 60000
|
|
mov DWORD PTR qpcFrequency+4, 0
|
|
mov DWORD PTR tickAccumulator, 0
|
|
mov DWORD PTR tickAccumulator+4, 0
|
|
ret
|
|
ResetSimulationFixture ENDP
|
|
|
|
MoveSixtyTicks PROC uses esi
|
|
mov esi, 60
|
|
move_sixty_loop:
|
|
call UpdatePlayer
|
|
dec esi
|
|
jnz move_sixty_loop
|
|
ret
|
|
MoveSixtyTicks ENDP
|
|
|
|
RunSimulationChecks PROC uses ebx esi edi
|
|
call ResetSimulationFixture
|
|
mov playerAngle, 32
|
|
mov esi, 60
|
|
schedule_one:
|
|
invoke AdvanceSimulation, 1000, 0
|
|
call Render3DScene
|
|
dec esi
|
|
jnz schedule_one
|
|
mov eax, playerX
|
|
mov scheduleSnapshot, eax
|
|
mov eax, playerY
|
|
mov scheduleSnapshot[4], eax
|
|
mov eax, playerFractionX
|
|
mov scheduleSnapshot[8], eax
|
|
mov eax, playerFractionY
|
|
mov scheduleSnapshot[12], eax
|
|
CHECK simulationTicks, 60 ; QPC accumulator executes exactly sixty ticks per second
|
|
CHECK lightingTick, 60 ; lights advance once per simulation tick
|
|
CHECK gameToastTimer, 40 ; toast lifetime follows simulation rather than renders
|
|
call ResetSimulationFixture
|
|
mov playerAngle, 32
|
|
mov esi, 20
|
|
schedule_three:
|
|
invoke AdvanceSimulation, 3000, 0
|
|
call Render3DScene
|
|
call Render3DScene
|
|
dec esi
|
|
jnz schedule_three
|
|
mov ebx, scheduleSnapshot
|
|
CHECK playerX, ebx ; batched ticks preserve X under a different render schedule
|
|
mov ebx, scheduleSnapshot[4]
|
|
CHECK playerY, ebx ; batched ticks preserve Y under a different render schedule
|
|
mov ebx, scheduleSnapshot[8]
|
|
CHECK playerFractionX, ebx ; batched ticks preserve fractional X
|
|
mov ebx, scheduleSnapshot[12]
|
|
CHECK playerFractionY, ebx ; batched ticks preserve fractional Y
|
|
CHECK simulationTicks, 60 ; catch-up does not change total simulated time
|
|
CHECK gameToastTimer, 40 ; repeated drawing does not age toasts
|
|
invoke AdvanceSimulation, 60000, 0
|
|
CHECK simulationTicks, 60 ; long stalls discard timing debt instead of fast-forwarding
|
|
invoke AdvanceSimulation, 15000, 0
|
|
CHECK simulationTicks, 68 ; catch-up is bounded to eight ticks
|
|
CHECK DWORD PTR tickAccumulator, 0 ; overload discards excess catch-up debt
|
|
|
|
call ResetSimulationFixture
|
|
mov keyForward, 0
|
|
mov keyHud, 1
|
|
mov hudStyle, HUD_STYLE_CLEAN
|
|
invoke AdvanceSimulation, 100, 0
|
|
movzx eax, keyHud
|
|
CHECK eax, 1 ; pending edge survives a frame with no simulation tick
|
|
invoke AdvanceSimulation, 2900, 0
|
|
CHECK hudStyle, HUD_STYLE_DEBUG ; edge toggles exactly once across three catch-up ticks
|
|
movzx eax, keyHud
|
|
CHECK eax, 0 ; simulation consumes edge after its first tick
|
|
mov keyPause, 1
|
|
invoke AdvanceSimulation, 3000, 0
|
|
CHECK isGamePaused, 1 ; pause edge is applied inside the simulation
|
|
CHECK simulationTicks, 4 ; pause boundary discards remaining catch-up ticks
|
|
CHECK DWORD PTR tickAccumulator, 0 ; pause clears timing debt
|
|
mov ebx, lightingTick
|
|
mov keyAttack, 1
|
|
call Engine_Tick
|
|
CHECK lightingTick, ebx ; pause freezes dynamic lights
|
|
CHECK playerAmmo, 50 ; paused tick consumes attack without firing
|
|
mov keyMenu1, 1
|
|
call Engine_Tick
|
|
CHECK isGamePaused, 0 ; menu resume returns to gameplay without advancing world
|
|
mov clockReset, 0
|
|
mov gameDialogActive, 1
|
|
mov keyDismiss, 1
|
|
call Engine_Tick
|
|
CHECK gameDialogActive, 0 ; Enter dismisses dialogue on a simulation tick
|
|
CHECK lightingTick, ebx ; dialogue dismissal does not advance world timers
|
|
CHECK gameToastTimer, 97 ; pause and modal ticks preserve toast lifetime
|
|
|
|
call ResetSimulationFixture
|
|
call MoveSixtyTicks
|
|
CHECK playerX, 980 ; cardinal speed remains three world units per tick
|
|
CHECK playerY, 800 ; cardinal movement has no orthogonal drift
|
|
call ResetSimulationFixture
|
|
mov keyStrafeRight, 1
|
|
call MoveSixtyTicks
|
|
CHECK playerX, 927 ; diagonal input is normalized rather than sqrt-two faster
|
|
CHECK playerY, 927 ; diagonal normalization is symmetric on both axes
|
|
call ResetSimulationFixture
|
|
mov playerAngle, 32
|
|
call MoveSixtyTicks
|
|
mov ebx, playerX
|
|
sub ebx, 800
|
|
mov scheduleSnapshot, ebx
|
|
mov ebx, playerY
|
|
sub ebx, 800
|
|
mov scheduleSnapshot[4], ebx
|
|
call ResetSimulationFixture
|
|
mov playerAngle, 288
|
|
call MoveSixtyTicks
|
|
mov ebx, 800
|
|
sub ebx, scheduleSnapshot
|
|
CHECK playerX, ebx ; opposite angle has exactly symmetric accumulated X
|
|
mov ebx, 800
|
|
sub ebx, scheduleSnapshot[4]
|
|
CHECK playerY, ebx ; opposite angle has exactly symmetric accumulated Y
|
|
call ResetSimulationFixture
|
|
mov keyBackward, 1
|
|
mov keyStrafeLeft, 1
|
|
mov keyStrafeRight, 1
|
|
call MoveSixtyTicks
|
|
CHECK playerX, 800 ; opposing forward inputs cancel before rounding
|
|
CHECK playerY, 800 ; opposing strafe inputs cancel before rounding
|
|
|
|
call ResetSimulationFixture
|
|
mov playerX, 175
|
|
mov playerY, 184
|
|
mov byte ptr [gameMap+3*MAP_W+3], CELL_BRICK
|
|
call UpdatePlayer
|
|
CHECK playerX, 175 ; collision checks leading corners beyond the center cell
|
|
CHECK playerFractionX, 0 ; blocked axis discards its fractional remainder
|
|
mov keyStrafeLeft, 1
|
|
call UpdatePlayer
|
|
CHECK playerY, 182 ; blocked X still permits sliding on free Y
|
|
invoke CanOccupyPlayer, 8, 160
|
|
CHECK eax, 0 ; collision body cannot extend outside the map
|
|
mov byte ptr [gameMap+3*MAP_W+3], CELL_PORTAL
|
|
invoke CanOccupyPlayer, 184, 184
|
|
CHECK eax, 1 ; portal cells remain walkable for the complete collision body
|
|
mov byte ptr [gameMap+2*MAP_W+3], CELL_DOOR_OPEN
|
|
mov playerX, 180
|
|
mov playerY, 160
|
|
mov playerAngle, 0
|
|
call InteractDoor
|
|
movzx eax, byte ptr [gameMap+2*MAP_W+3]
|
|
CHECK eax, CELL_DOOR_OPEN ; door cannot close over an overlapping player body
|
|
mov playerX, 160
|
|
call InteractDoor
|
|
movzx eax, byte ptr [gameMap+2*MAP_W+3]
|
|
CHECK eax, CELL_DOOR ; unoccupied door still closes normally
|
|
|
|
call ResetSimulationFixture
|
|
mov isCutsceneActive, 1
|
|
mov csCameraX, 184
|
|
mov csCameraY, 184
|
|
mov csCameraAngle, 128
|
|
mov byte ptr [gameMap+3*MAP_W+3], CELL_BRICK
|
|
call SelectRenderCamera
|
|
CHECK viewX, 184 ; renderer selects cinematic camera without replacing player pose
|
|
CHECK playerX, 800 ; camera ownership leaves gameplay position untouched
|
|
call StopCutscene
|
|
CHECK playerX, 800 ; unsafe cinematic handoff retains last safe gameplay position
|
|
CHECK playerAngle, 0 ; unsafe cinematic handoff also retains gameplay orientation
|
|
mov isCutsceneActive, 1
|
|
mov csCameraX, 864
|
|
mov csCameraY, 800
|
|
call StopCutscene
|
|
CHECK playerX, 864 ; walkable cinematic handoff transfers camera position
|
|
CHECK playerAngle, 128 ; walkable cinematic handoff transfers orientation
|
|
|
|
; Render routines must not dismiss, age or transition anything.
|
|
mov gameToastTimer, 12
|
|
mov gameDialogActive, 1
|
|
mov keyAction, 1
|
|
mov fadeState, 1
|
|
mov fadeLevel, 4
|
|
call RenderGameToast
|
|
call RenderGameDialog
|
|
call ApplyScreenFade
|
|
call ApplyScreenFade
|
|
CHECK gameToastTimer, 12 ; drawing toast never decrements its timer
|
|
CHECK gameDialogActive, 1 ; drawing modal never consumes input or closes it
|
|
CHECK fadeLevel, 4 ; drawing fade never advances transition level
|
|
CHECK fadeState, 1 ; drawing fade never switches room state
|
|
|
|
call ResetSimulationFixture
|
|
invoke LoadSublevelBinary, OFFSET subPath, 4, 6, 64
|
|
mov playerX, 600
|
|
mov playerHealth, 1
|
|
mov playerAmmo, 2
|
|
mov playerKeys, 3
|
|
mov playerScore, 700
|
|
mov isPlayerDead, 1
|
|
mov gameDialogActive, 1
|
|
mov playerFractionX, 12345
|
|
mov gameMode, GAME_MODE_ADVENTURE
|
|
mov enableWeapons, 0
|
|
call RestartCurrentRoom
|
|
CHECK eax, 1 ; current-room restart succeeds after a sublevel transition
|
|
CHECK playerX, 288 ; restart retains actual transition entry X rather than file spawn
|
|
CHECK playerY, 416 ; restart retains actual transition entry Y
|
|
CHECK playerAngle, 64 ; restart retains actual transition entry angle
|
|
CHECK spriteCount, 1 ; restart reloads current sublevel rather than startup room
|
|
CHECK playerHealth, 100 ; restart resets health after successful reload
|
|
CHECK playerAmmo, 50 ; restart resets ammunition after successful reload
|
|
CHECK playerKeys, 0 ; restart clears acquired keys
|
|
CHECK playerScore, 0 ; restart clears room score
|
|
CHECK playerFractionX, 0 ; restart clears fractional movement
|
|
CHECK gameDialogActive, 0 ; restart clears old modal dialogue
|
|
CHECK gameMode, GAME_MODE_ADVENTURE ; restart preserves configured game genre
|
|
CHECK enableWeapons, 0 ; restart preserves weapon configuration
|
|
; Replace only remembered path in this isolated fixture to force load failure.
|
|
mov DWORD PTR currentMapPath, 73696D61h
|
|
mov DWORD PTR currentMapPath+4, 676E6973h
|
|
mov DWORD PTR currentMapPath+8, 7461642Eh
|
|
mov BYTE PTR currentMapPath+12, 0
|
|
mov playerHealth, 17
|
|
mov playerAmmo, 9
|
|
mov playerFractionX, 222
|
|
mov isPlayerDead, 1
|
|
call RestartCurrentRoom
|
|
CHECK eax, 0 ; failed restart reports failure without procedural fallback
|
|
CHECK playerHealth, 17 ; failed restart preserves health transactionally
|
|
CHECK playerAmmo, 9 ; failed restart preserves ammunition transactionally
|
|
CHECK playerFractionX, 222 ; failed restart preserves movement remainder
|
|
CHECK isPlayerDead, 1 ; failed restart does not revive player in old room
|
|
CHECK spriteCount, 1 ; failed restart preserves active room entities
|
|
CHECK playerX, 288 ; failed restart preserves current camera pose
|
|
|
|
; Keys pressed while another application owns focus must not become commands.
|
|
call ResetSimulationFixture
|
|
mov hEngineWnd, 1
|
|
mov testFocus, 1
|
|
mov testKeys[VK_KEY_E*2], 0
|
|
mov testKeys[VK_KEY_CONTROL*2], 0
|
|
mov testKeys[VK_KEY_SPACE*2], 0
|
|
call PollInput
|
|
call ConsumeInputEdges
|
|
mov testFocus, 0
|
|
call PollInput
|
|
mov testKeys[VK_KEY_E*2], 8000h
|
|
mov testKeys[VK_KEY_CONTROL*2], 8000h
|
|
mov testFocus, 1
|
|
call PollInput
|
|
movzx eax, keyAction
|
|
CHECK eax, 0 ; focus regain suppresses action pressed while unfocused
|
|
movzx eax, keyAttack
|
|
CHECK eax, 0 ; focus regain suppresses attack pressed while unfocused
|
|
call ConsumeInputEdges
|
|
call PollInput
|
|
movzx eax, keyAttack
|
|
CHECK eax, 0 ; resynchronized held attack does not fire on the next poll
|
|
mov testKeys[VK_KEY_CONTROL*2], 0
|
|
call PollInput
|
|
mov testKeys[VK_KEY_CONTROL*2], 8000h
|
|
call PollInput
|
|
movzx eax, keyAttack
|
|
CHECK eax, 1 ; releasing and pressing again rearms attack after focus regain
|
|
|
|
call ResetSimulationFixture
|
|
invoke ShowGameDialog, 0, 0
|
|
CHECK clockReset, 1 ; opening a modal discards outstanding timing debt
|
|
mov clockReset, 0
|
|
call CloseGameDialog
|
|
CHECK clockReset, 1 ; closing a modal through the API discards timing debt
|
|
|
|
call ResetSimulationFixture
|
|
mov lightingMode, LIGHTING_MODE_DYNAMIC
|
|
mov isFlashlightOn, 1
|
|
mov isCutsceneActive, 1
|
|
mov csCameraX, 160
|
|
mov csCameraY, 160
|
|
mov csCameraAngle, 0
|
|
invoke SamplePointLighting, 224, 160, 0, 0
|
|
mov eax, lightRed
|
|
mov scheduleSnapshot, eax
|
|
mov isCutsceneActive, 0
|
|
mov playerX, 160
|
|
mov playerY, 160
|
|
invoke SamplePointLighting, 224, 160, 0, 0
|
|
mov ebx, scheduleSnapshot
|
|
CHECK lightRed, ebx ; cinematic flashlight matches preview camera rather than safe player pose
|
|
|
|
; Exhaust all half-turn pairs, including signed fractional remainders.
|
|
mov edi, 0
|
|
mov symmetryOK, 1
|
|
mov speedOK, 1
|
|
symmetry_angle:
|
|
call ResetSimulationFixture
|
|
mov playerAngle, edi
|
|
call MoveSixtyTicks
|
|
mov eax, playerX
|
|
sub eax, 800
|
|
mov scheduleSnapshot, eax
|
|
imul eax, eax
|
|
mov ebx, playerY
|
|
sub ebx, 800
|
|
mov scheduleSnapshot[4], ebx
|
|
imul ebx, ebx
|
|
add eax, ebx
|
|
cmp eax, 178*178
|
|
jb speed_mismatch
|
|
cmp eax, 181*181
|
|
jbe speed_valid
|
|
speed_mismatch:
|
|
mov speedOK, 0
|
|
speed_valid:
|
|
mov eax, playerFractionX
|
|
mov scheduleSnapshot[8], eax
|
|
mov eax, playerFractionY
|
|
mov scheduleSnapshot[12], eax
|
|
call ResetSimulationFixture
|
|
lea eax, [edi+256]
|
|
mov playerAngle, eax
|
|
call MoveSixtyTicks
|
|
mov eax, 800
|
|
sub eax, scheduleSnapshot
|
|
cmp playerX, eax
|
|
jne symmetry_mismatch
|
|
mov eax, 800
|
|
sub eax, scheduleSnapshot[4]
|
|
cmp playerY, eax
|
|
jne symmetry_mismatch
|
|
mov eax, scheduleSnapshot[8]
|
|
neg eax
|
|
cmp playerFractionX, eax
|
|
jne symmetry_mismatch
|
|
mov eax, scheduleSnapshot[12]
|
|
neg eax
|
|
cmp playerFractionY, eax
|
|
je symmetry_next
|
|
symmetry_mismatch:
|
|
mov symmetryOK, 0
|
|
symmetry_next:
|
|
inc edi
|
|
cmp edi, 256
|
|
jb symmetry_angle
|
|
CHECK symmetryOK, 1 ; all 512 directions retain opposite-position and fraction symmetry
|
|
CHECK speedOK, 1 ; all angular speeds remain within integer projection tolerance
|
|
|
|
call ResetSimulationFixture
|
|
mov playerX, 160
|
|
mov playerY, 160
|
|
mov byte ptr [gameMap+2*MAP_W+4], CELL_BRICK
|
|
mov cutsceneCount, 2
|
|
mov [cutsceneEvents].CutsceneEvent.tickTimestamp, 0
|
|
mov [cutsceneEvents].CutsceneEvent.opCode, OP_MOVE_CAM_TO
|
|
mov [cutsceneEvents].CutsceneEvent.paramA, 416
|
|
mov [cutsceneEvents].CutsceneEvent.paramB, 160
|
|
mov [cutsceneEvents].CutsceneEvent.dataId, 4
|
|
mov [cutsceneEvents+16].CutsceneEvent.tickTimestamp, 5
|
|
mov [cutsceneEvents+16].CutsceneEvent.opCode, OP_END
|
|
call StartCutscene
|
|
call UpdatePlayer
|
|
call UpdatePlayer
|
|
call UpdatePlayer
|
|
CHECK csCameraX, 288 ; cinematic path can cross a wall independently of the body
|
|
CHECK playerX, 160 ; wall-crossing camera path never teleports gameplay body
|
|
mov keySkipScene, 1
|
|
call UpdatePlayer
|
|
CHECK playerX, 160 ; skipping inside a wall retains safe player pose
|
|
mov keySkipScene, 0
|
|
call StartCutscene
|
|
mov esi, 6
|
|
wall_scene_tick:
|
|
call UpdatePlayer
|
|
dec esi
|
|
jnz wall_scene_tick
|
|
CHECK isCutsceneActive, 0 ; wall-crossing cinematic reaches END normally
|
|
CHECK playerX, 416 ; END hands off safe endpoint across the wall
|
|
|
|
call ResetSimulationFixture
|
|
mov playerMoveSpeed, 64
|
|
mov playerX, 175
|
|
mov playerY, 160
|
|
mov byte ptr [gameMap+2*MAP_W+3], CELL_BRICK
|
|
call UpdatePlayer
|
|
CHECK playerX, 175 ; high-speed movement cannot tunnel through a wall
|
|
|
|
call ResetSimulationFixture
|
|
mov keyRun, 1
|
|
call MoveSixtyTicks
|
|
CHECK playerX, 1160 ; sprint retains six world units per fixed tick
|
|
mov keyForward, 0
|
|
mov keyTurnLeft, 1
|
|
mov keyTurnRight, 1
|
|
call UpdatePlayer
|
|
CHECK playerAngle, 0 ; opposing turn inputs cancel
|
|
|
|
call ResetSimulationFixture
|
|
invoke LoadSublevelBinary, OFFSET subPath, 4, 6, 64
|
|
mov isPlayerDead, 1
|
|
mov keyRestart, 1
|
|
call Engine_Tick
|
|
CHECK isPlayerDead, 0 ; death R uses the shared current-room restart
|
|
CHECK playerX, 288 ; death restart restores transition entry pose
|
|
mov clockReset, 0
|
|
mov isGamePaused, 1
|
|
mov playerHealth, 7
|
|
mov keyMenu4, 1
|
|
call Engine_Tick
|
|
CHECK isGamePaused, 0 ; pause-menu restart resumes only after successful reload
|
|
CHECK playerHealth, 100 ; pause-menu restart uses the same transient reset
|
|
CHECK playerY, 416 ; pause-menu restart keeps the current room entry
|
|
|
|
call ResetSimulationFixture
|
|
; Frequency > 2^32 exercises carries in accumulator and elapsed multiplication.
|
|
mov DWORD PTR qpcFrequency, 8
|
|
mov DWORD PTR qpcFrequency+4, 1
|
|
invoke AdvanceSimulation, 71582788, 0
|
|
CHECK simulationTicks, 0 ; QPC scheduler keeps a fractional high-frequency tick
|
|
invoke AdvanceSimulation, 2, 0
|
|
CHECK simulationTicks, 1 ; QPC scheduler carries elapsed counts across DWORD boundaries
|
|
CHECK DWORD PTR tickAccumulator, 96 ; QPC scheduler retains exact high-frequency remainder
|
|
mov clockReset, 1
|
|
invoke AdvanceSimulation, 1000, 0
|
|
CHECK DWORD PTR tickAccumulator, 0 ; explicit clock boundary discards existing fractional debt
|
|
invoke AdvanceSimulation, -1, -1
|
|
CHECK simulationTicks, 1 ; backwards counter samples cannot fast-forward simulation
|
|
|
|
call ResetSimulationFixture
|
|
call InitLightingSystem
|
|
mov lightingMode, LIGHTING_MODE_DYNAMIC
|
|
mov isCutsceneActive, 1
|
|
mov csCameraX, 160
|
|
mov csCameraY, 160
|
|
mov csCameraAngle, 32
|
|
mov byte ptr [gameMap+3*MAP_W+6], CELL_BRICK
|
|
call Render3DScene
|
|
mov esi, OFFSET screenBuffer
|
|
mov edi, OFFSET cinematicReference
|
|
mov ecx, FRAME_BUFFER_SIZE/4
|
|
rep movsd
|
|
mov isCutsceneActive, 0
|
|
mov playerX, 160
|
|
mov playerY, 160
|
|
mov playerAngle, 32
|
|
call Render3DScene
|
|
mov esi, OFFSET screenBuffer
|
|
mov edi, OFFSET cinematicReference
|
|
mov ecx, FRAME_BUFFER_SIZE/4
|
|
repe cmpsd
|
|
sete al
|
|
movzx eax, al
|
|
CHECK eax, 1 ; cinematic lit framebuffer equals camera-only preview with separate gameplay body
|
|
ret
|
|
RunSimulationChecks ENDP
|