**The Goal:** Track keyboard input with two modes - "held down" and "just pressed" (transition detection).
**The Tool:** `SDL_GetKeyboardState(NULL)` returns a pointer to an internal array indexed by scancode. Each entry is `true` or `false` representing the current key state.
---
### Breakpoint #1: The Return Type Ambiguity
`const bool *SDL_GetKeyboardState(int *numkeys);`
That `const bool *` could mean anything - a single value, an array, the first element of a larger structure. The type system doesn't tell you the contract. You learn it from the docs.
In Rust, the same function might look like:
```
fn get_keyboard_state(&self) -> &[u8] // Borrowed slice, length known
// or
fn get_keyboard_state(&self) -> &[u8; 512] // Fixed-size array, compile-time size
```
Either way, the type communicates something: slice (length known at runtime), or fixed array (size encoded in the type). You don't have to read documentation to understand what you're getting back.
C's `bool *` tells you nothing except "there's a bool somewhere in memory pointed to by this address." Rust's `&[T]` or `[T; N]` tells you "this is a collection, here's how many elements."
The difference isn't correctness - it's discoverability. C puts the burden on you to read the docs. Rust puts the burden on the compiler to verify the contract.
---
### Breakpoint #2: Frame Boundary Sequencing
You can't detect transitions from one snapshot. You need two frames:
1. Grab new snapshot
2. Compare current vs previous
3. Overwrite previous with current
Order matters. Copy first? You lose all transitions.
---
### Breakpoint #3: Terminal goes brrrrr
After some debugging using the best debugging tool - `printf` - comparing `current` and `previous` values revealed that `W Just released` never triggered.
```
if (!input_state->current[SDL_SCANCODE_W] && input_state->previous[SDL_SCANCODE_W]) {
printf("W Just released\n");
}
if (input_state->current[SDL_SCANCODE_W] && !input_state->previous[SDL_SCANCODE_W]) {
printf("W Just pressed\n");
}
```
The game loop was running uncapped - well above 60fps - flooding the terminal with output. My first hypothesis: the "just released" message was getting lost in the buffer due to sheer volume.
Adding VSync to the renderer capped the loop to the monitor's refresh rate:
`SDL_SetRenderVSync(engine->renderer, 1);`
Output became readable. But "just released" still didn't fire. Speed wasn't the cause - the `previous` array was never updating at all. The debug output confirmed it:
```
current[W]=1 previous[W]=0
current[W]=1 previous[W]=0
current[W]=1 previous[W]=0
current[W]=0 previous[W]=0
```
`previous[W]` stayed at `0` regardless of what `current[W]` showed. The `memcpy` at the end of `input_update` wasn't copying anything. This pointed straight to the struct definition - which became Breakpoint #4.
---
### Breakpoint #4: The `bool *` vs `bool[]` Confusion
```
// Wrong
typedef struct InputState {
const bool *current;
bool *previousi[SDL_SCANCODE_COUNT]; // ← Pointer, not array
} InputState;
// Right
typedef struct InputState {
const bool *current; // SDL's internal pointer
bool previous[SDL_SCANCODE_COUNT]; // Your own array } InputState;
```
Mixing them up caused `memcpy` to write nowhere. Silent corruption. Compiler warnings ignored. Debugger output lying to me until I added explicit tests.
The `incompatible integer to pointer conversion` error should have been the first stop at this breakpoint.
---
### What Ships
- `InputState` with `current` (pointer) and `previous` (array)
- `input_update()` handles snapshot, compare, advance
- `input_is_down()` and `input_just_pressed()` expose transitions
- `memcpy()` copies state at frame boundary
---
### The Rust Comparison
Rust catches these at compile time. C catches them at runtime. The difference is when the pain arrives, not whether it arrives.
---
**Next:** Game struct + scaffolding.
---
_[
[email protected]](mailto:
[email protected]) | Previous: [[0x2004_Issue 0x1 - Timer]] → Next: [[0x2006_Issue 0x3 - TBD]]_
>_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._