> "In C, the assignment has no influence on how the expression is evaluated." Issue 0x1 was supposed to be straightforward: calculate delta time, expose it to the game loop. The math seemed simple - `SDL_GetPerformanceCounter()` gives hardware ticks, divide by frequency, done. I overcomplicated it in three ways. ## Breakpoint #1: Global State My first implementation used file-level globals for `last_tick`, `current_tick`, and `delta_time`. This worked, but it undid the entire point of 0x0, which was to extract static globals out of `main.c` and into an `Engine` struct to establish ownership. **Fix:** Embed a `Timer` struct inside `Engine`. The timer belongs to the engine. It dies when the engine dies. No globals, no ambiguity. ## Breakpoint #2: Everything Is a Pointer When I rewrote the Timer struct, my Rust brain kicked in. I started making everything a pointer: ``` // My instinct from Rust typedef struct Engine { Timer *timer; // pointer — because Rust taught me to borrow } Engine; ``` In Rust, `&` and `&mut` are borrows - temporary views into data. In C, a pointer is just a memory address. Nobody tracks it. Making `timer` a pointer meant `malloc` and `free`, extra allocation, extra failure points, for a 24-byte struct that's owned entirely by Engine. **Fix:** Embed by value. `Timer timer;` lives inside `Engine`. No allocation. No lifetime tracking. ## Breakpoint #3: Integer Division Truncation The real gotcha arrived when the delta time printed as `0.000000` even though the ticks were advancing correctly. **My initial diagnosis was wrong.** I thought: "Maybe `frequency` didn't initialize, so I divided by zero? Or maybe the result got truncated to a float?" Neither was true. The actual problem was invisible to me until I logged the intermediate values. **The broken math:** ``` Uint64 last_tick = 0; Uint64 current_tick = SDL_GetPerformanceCounter(); // 13,912,992,878,646 Uint64 frequency = SDL_GetPerformanceFrequency(); // 1,000,000,000 // The calculation delta_time = (current_tick - last_tick) / frequency; // 13,912,992 / 1,000,000,000 ``` Here's where C and Rust diverge. **In Rust:** ``` let delta: f64 = ticks / frequency; // If either side is f64, Rust promotes automatically let delta: f64 = (ticks as f64) / (frequency as f64); // Explicit cast ``` Rust either complains or promotes based on the destination type. The compiler doesn't let you silently lose precision. **In C:** C evaluates expressions **before** assignment. The destination type has zero influence on the calculation. ``` delta_time = (current_tick - last_tick) / frequency; // ALL Uint64, integer division ``` 1. First frame `last_tick` was set to `0` in `engine_init` 2. First tick difference was ~316,000 3. `316,000 / 1,000,000,000` equals `0` (integer division) 4. `0` assigned to `double` becomes `0.0` **The fix:** Cast **one operand** before the division, not after: ```// WRONG — cast after calculation delta_time = (double)((current_tick - last_tick) / frequency); // Still integer division // CORRECT — cast before calculation delta_time = (double)(current_tick - last_tick) / (double)frequency; ``` Now C promotes both sides to `double` before the division happens. The result is `0.000316` seconds, correct for a 317 FPS loop. **The lesson:** In C, the type of the destination variable does not determine how the expression is calculated. You control the arithmetic by casting operands, not the result. > `a / b` evaluated in integer arithmetic, then assigned to double ≠ `(double)a / (double)b` evaluated in floating-point arithmetic This is a silent killer. No warning. No crash. Just wrong numbers that look plausible. ## What Shipped - `Timer` struct embedded in `Engine` by value - `SDL_GetPerformanceCounter()` for nanosecond precision (vs wall clock) - Frequency cached in `engine_init` to avoid per-frame function calls - `engine_update(Engine*)` computes delta time each frame - Delta time clamped to 0.05s max (prevents physics jumps on window drag) - **Double-casting operands** to force floating-point division - No globals, no `extern`, no unnecessary pointers Issue 0x1 done. Next up: **[[0x2005_Issue 0x2, 0x3 - Input]]** — Keyboard state, `keys_down` and `keys_just_pressed`. --- _[[email protected]](mailto:[email protected]) | Previous: [[0x2003_Issue 0x0 - Extraction]] → Next: [[0x2005_Issue 0x2, 0x3 - Input]]_ >_Transparency note: I use Lumo as a technical reviewer for these posts — checking C semantics, verifying claims, and catching formatting issues. The ideas, mistakes, and fixes are my own._