; Fixed 60 Hz simulation and QPC scheduler. No presentation or keyboard APIs in ; Engine_Tick. AdvanceSimulation accepts elapsed QPC counts for deterministic tests. .code QueryPerformanceCounter PROTO :DWORD QueryPerformanceFrequency PROTO :DWORD GetSimulationState PROC mov eax, SIM_UNFOCUSED cmp inputFocused, 0 je state_ready mov eax, SIM_PAUSED cmp isGamePaused, 0 jne state_ready mov eax, SIM_DIALOG cmp gameDialogActive, 0 jne state_ready mov eax, SIM_TRANSITION cmp fadeState, 0 jne state_ready mov eax, SIM_DEAD cmp isPlayerDead, 0 jne state_ready mov eax, SIM_CUTSCENE cmp isCutsceneActive, 0 jne state_ready cmp cutscenePending, 0 jne state_ready mov eax, SIM_PLAYING state_ready: ret GetSimulationState ENDP ; UI edges execute on ticks, never during polling or drawing. ApplyInputCommands PROC cmp keyPause, 0 je command_menu xor isGamePaused, 1 mov clockReset, 1 command_menu: cmp isGamePaused, 0 je command_hud cmp keyMenu1, 0 je @F mov isGamePaused, 0 mov clockReset, 1 @@: cmp keyMenu2, 0 je @F xor enableWeapons, 1 mov eax, enableWeapons mov gameMode, eax mov showCrosshair, eax mov playerWeapon, WEAPON_NONE test eax, eax jz @F mov playerWeapon, WEAPON_PISTOL @@: cmp keyMenu3, 0 je @F xor hudStyle, 1 @@: cmp keyMenu4, 0 je @F call RestartCurrentRoom ; A failed reload stays paused with all room/gameplay state intact. @@: cmp keyMenu5, 0 je command_done mov isRunning, 0 ret command_hud: cmp keyMap, 0 je @F xor keyShowMap, 1 @@: cmp keyHud, 0 je @F xor hudStyle, 1 @@: call GetSimulationState cmp eax, SIM_PLAYING jne command_done cmp keyFlashlight, 0 je @F xor isFlashlightOn, 1 @@: cmp keyLightMode, 0 je @F inc lightingMode cmp lightingMode, 3 jb @F mov lightingMode, 0 @@: cmp enableWeapons, 0 je command_done cmp gameMode, GAME_MODE_ACTION jne command_done cmp keyWeapon1, 0 je @F mov playerWeapon, WEAPON_KNIFE @@: cmp keyWeapon2, 0 je @F mov playerWeapon, WEAPON_PISTOL @@: cmp keyWeapon3, 0 je command_done mov playerWeapon, WEAPON_SHOTGUN command_done: ret ApplyInputCommands ENDP Engine_Tick PROC inc simulationTicks mov gameplayTickBlocked, 1 call GetSimulationState mov simulationState, eax cmp eax, SIM_UNFOCUSED je tick_done call ApplyInputCommands cmp clockReset, 0 jne tick_done call GetSimulationState mov simulationState, eax cmp eax, SIM_PAUSED je tick_done cmp eax, SIM_DIALOG je tick_dialog cmp eax, SIM_TRANSITION je tick_transition cmp eax, SIM_DEAD je tick_dead cmp mapErrorFrames, 0 je @F dec mapErrorFrames @@: call UpdatePlayer call IsGameplayBlocked test eax, eax jnz tick_cinematic call UpdateEntities call IsGameplayBlocked test eax, eax jnz tick_done call UpdateCombat call IsGameplayBlocked test eax, eax jnz tick_done call Game_OnTick call IsGameplayBlocked test eax, eax jnz tick_done call UpdateDynamicLights cmp gameToastTimer, 0 jle tick_done dec gameToastTimer jmp tick_done tick_cinematic: cmp isCutsceneActive, 0 je tick_done call UpdateDynamicLights jmp tick_done tick_dialog: mov al, keyAction or al, keyDismiss or al, keyAttack jz tick_done call CloseGameDialog mov clockReset, 1 jmp tick_done tick_transition: call UpdateScreenFade jmp tick_done tick_dead: cmp keyRestart, 0 je tick_done call RestartCurrentRoom tick_done: call ConsumeInputEdges ret Engine_Tick ENDP InitSimulationClock PROC invoke QueryPerformanceFrequency, OFFSET qpcFrequency invoke QueryPerformanceCounter, OFFSET qpcPrevious mov DWORD PTR tickAccumulator, 0 mov DWORD PTR tickAccumulator+4, 0 mov clockReset, 0 ret InitSimulationClock ENDP AdvanceSimulation PROC uses ebx esi edi, deltaLow:DWORD, deltaHigh:DWORD ; Drop elapsed stalls over 250ms, backwards time, and explicit pause/focus ; debt. Bound catch-up to 8 ticks even on an overloaded machine. cmp clockReset, 0 jne clock_discard mov eax, DWORD PTR qpcFrequency mov edx, DWORD PTR qpcFrequency+4 shrd eax, edx, 2 shr edx, 2 cmp deltaHigh, edx ja clock_discard jb clock_add cmp deltaLow, eax ja clock_discard clock_add: mov eax, deltaLow mov ebx, 60 mul ebx imul ebx, deltaHigh add edx, ebx add DWORD PTR tickAccumulator, eax adc DWORD PTR tickAccumulator+4, edx mov esi, 8 clock_tick: mov edx, DWORD PTR tickAccumulator+4 cmp edx, DWORD PTR qpcFrequency+4 jb clock_done ja clock_step mov eax, DWORD PTR tickAccumulator cmp eax, DWORD PTR qpcFrequency jb clock_done clock_step: mov eax, DWORD PTR qpcFrequency mov edx, DWORD PTR qpcFrequency+4 sub DWORD PTR tickAccumulator, eax sbb DWORD PTR tickAccumulator+4, edx call Engine_Tick cmp clockReset, 0 jne clock_discard dec esi jnz clock_tick clock_discard: mov DWORD PTR tickAccumulator, 0 mov DWORD PTR tickAccumulator+4, 0 mov clockReset, 0 clock_done: ret AdvanceSimulation ENDP PumpSimulationClock PROC uses ebx invoke QueryPerformanceCounter, OFFSET qpcNow mov eax, DWORD PTR qpcNow mov edx, DWORD PTR qpcNow+4 sub eax, DWORD PTR qpcPrevious sbb edx, DWORD PTR qpcPrevious+4 mov ebx, DWORD PTR qpcNow mov DWORD PTR qpcPrevious, ebx mov ebx, DWORD PTR qpcNow+4 mov DWORD PTR qpcPrevious+4, ebx invoke AdvanceSimulation, eax, edx ret PumpSimulationClock ENDP