This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Precision Calibration: Main Loop Fine Tuning
Summary
Adjusted the emulator's main loop to improve timing accuracy between the CPU, the Timer and the Interrupts. Reduced batch size from 456 to 64 T-Cycles and frame skip removed (from 2 to 0) for a more gaming experience more precise and smooth. These changes fix random Game Over issues in Tetris (caused by Timer-based RNG) and lag in controls.
Hardware Concept
On a real Game Boy, all subsystems (CPU, PPU, Timer, Interrupts) are synchronized by the same system clock (4.194304 MHz). Each instruction of the CPU consumes a specific number of M-Cycles (Machine Cycles), which are converted to T-Cycles (T-Cycles = M-Cycles × 4) to synchronize with the PPU and Timer.
The problem of aggressive batching:When we group too many cycles (456 T-Cycles = 1 full scanline), the CPU executes many instructions before update the Timer and Interrupts. This causes:
- Timer desynchronization:Games like Tetris use registration DIV (Timer) to generate random numbers. If the Timer is not updated with frequently enough, the RNG generates incorrect values, causing parts invalid and Game Over.
- Interrupt Lag:Interruptions (VBlank, Timer, Joypad) are processed late, causing delay in response to controls.
- Visual glitches:The frame skip makes the rendering look choppy, losing visual softness.
The solution:Reduce batch size to 64 T-Cycles (~16 M-Cycles) maintains good performance (reduces Python overhead) but is sufficiently small so that the Timer and Interrupts are updated accurately. Delete frame skip (SKIP_FRAMES = 0) restores visual smoothness to a true 60 FPS.
Source: Pan Docs - System Clock, Timing, Timer, Interruptions
Implementation
Two constants were modified in the methodrun()of the classViboyinsrc/viboy.py:
Change 1: Batch Size Reduction
Before: BATCH_SIZE_T_CYCLES = 456(1 full scanline)
After: BATCH_SIZE_T_CYCLES = 64(~16 M-Cycles)
This reduces the batch size from 456 to 64 T-Cycles, making the Timer and the Interrupts are updated approximately 7 times more frequently. The overhead of Python is still low (fewer function calls than without batching), but the Accuracy improves significantly.
Change 2: Removal of Frame Skip
Before: SKIP_FRAMES = 2(render 1 out of every 3 frames)
After: SKIP_FRAMES = 0(render all frames)
This removes the frame skip, restoring rendering to a true 60 FPS. The logic of the game already ran at 60Hz, only the visual drawing was skipped. Now it renders each frame for maximum smoothness.
Modified components
src/viboy.py: Methodrun()- Adjustment of constants performance (BATCH_SIZE_T_CYCLES and SKIP_FRAMES)
Design decisions
Why 64 T-Cycles?It is a balance between performance and precision:
- 64 T-Cycles = ~16 M-Cycles, enough to reduce Python overhead
- It is 7 times smaller than 456, improving the precision of the Timer
- Maintains batching (we do not update again by instruction), preserving performance
Why remove frame skip?We want to verify if the PC supports Real 60 FPS without skipping frames. If it's slow, you can go up to 1, but we'll start with maximum quality to evaluate real performance.
Affected Files
src/viboy.py- Modification of BATCH_SIZE_T_CYCLES constants (456 → 64) and SKIP_FRAMES (2 → 0) in the methodrun()
Tests and Verification
State:Pending verification with Tetris ROM.
Trial by Fire (Tetris):
- ROM:Tetris (user-contributed ROM, not distributed)
- Execution mode:UI with pygame, no frame skip, batch size 64
- Success Criterion:
- Pieces rotate correctly (no random Game Over)
- Controls respond without lag
- Movement looks fluid (real 60 FPS)
- The game allows you to play without killing yourself
- Observation:Pending execution
python main.py tetris.gbto verify that the changes solve the accuracy problems. - Result: draft- Pending verification
Legal notes:The Tetris ROM is provided by the user for testing local. It is not distributed or linked in the repository.
Sources consulted
- Bread Docs:System Clock, Timing, Timer, Interruptions
- Implementation based on analysis of Timer behavior and its use in game RNG
Educational Integrity
What I Understand Now
- Batching vs Precision:Batching reduces Python overhead grouping instructions, but a batch that is too large desynchronizes the subsystems. The optimal size depends on the balance between performance and required precision.
- Timer and RNG:Games like Tetris use the DIV (Timer) register as a seed for random number generation. If the Timer does not update frequently enough, the RNG generates incorrect values, causing Erratic game behavior.
- Frame Skip:Frame skip reduces rendering load skipping frames, but sacrifices visual smoothness. for an experience Game Boy 100% real, it is preferable to render all the frames if the hardware allows it.
What remains to be confirmed
- Actual performance:Check if the PC supports 60 real FPS with batch size 64 and without frame skip. If it's slow, set SKIP_FRAMES to 1.
- Timer Accuracy:Confirm that batch size 64 is enough for Tetris to generate pieces correctly without random Game Over.
- Control lag:Verify that the inputs respond without noticeable delay with the new batch size.
Hypotheses and Assumptions
Hypothesis:A batch size of 64 T-Cycles is enough to maintain the accuracy of the Timer and Interrupts while preserving the performance of the batching. This hypothesis is based on the fact that 64 is approximately 7 times smaller than 456, significantly improving the refresh rate of peripherals.
Assumption:The user's PC has enough power to render at real 60 FPS without frame skip. If not, SKIP_FRAMES will be set to 1 as a compromise.
Next Steps
- [ ] Execute
python main.py tetris.gband verify that the changes solve precision problems - [ ] If performance is insufficient with SKIP_FRAMES=0, set to 1 as commitment
- [ ] If batch size 64 is still causing problems, reduce to 32 or 16 T-Cycles (although this will increase Python overhead)
- [ ] Document the optimal batch size found for future reference