The Ball Moves and So Does My Soul
That video up there is two paddles, one ball, and a score counter running at 33 fps on an operating system that was built as a literal temple to God. It’s playable. It keeps score. It has a win screen. Someone can sit down at this emulated x86 machine running on Apple Silicon and play Pong. For real. Against another human.
We built that today. In one sprint. And the compiler yelled at us twice because I forgot I’m not writing C.
The Varoom Pattern
Last sprint left us with a static Pong frame. Two white rectangles, a yellow square, a dashed line, and two hardcoded numbers. Pretty to look at, does nothing. Sprint 3’s job: make it breathe.
The blueprint was sitting in our reference folder the whole time. Terry’s Varoom, the 3D racing game that ships with TempleOS, has a three-part architecture that’s so clean it hurts:
// 1. Rendering - OS calls this every frame
Fs->draw_it = &DrawIt;
// 2. Physics - separate task, runs forever
Fs->animate_task = Spawn(&AnimateTask, NULL, "Animate",, Fs);
// 3. Input - blocking read in main loop
while (TRUE)
switch (GetKey(&sc)) {
case CH_ESC:
goto vr_done;
}
Three concerns. Three execution paths. The OS handles double buffering, task scheduling, and frame timing. You just wire up your callbacks and go. I’ve used SDL, SFML, Raylib, and none of them make the game loop this simple. Terry put a game engine in the kernel and called it an operating system.
We ripped this pattern straight out of Varoom and stuffed Pong into it. Global game state at the top, DrawIt reads it and draws, AnimateTask updates it and sleeps, GetKey loop modifies it on input. The holy trinity of game architecture.
Temple Typist’d it in. Compiled. Ran.
First try. The ball is moving. The paddles respond to keys. It bounces off the ceiling and floor. The yellow square is ALIVE.
It sailed right through the paddles like they weren’t there, obviously. And it only reset on one wall. But the skeleton was standing. Three tasks running in harmony on God’s computer ᕕ( ᐛ )ᕗ
Teaching the Ball About Walls (and Paddles)
Wall bouncing was already working from the architecture step. Ball hits the top? Reverse ball_dy. Hits the bottom? Same thing. Snap to the edge so it doesn’t clip through. Simple integer math, exactly the kind of thing HolyC was born for.
Paddles were also responding from step one, but GetKey is a blocking call with no key repeat. TempleOS doesn’t do “hold the key to keep moving.” Each press is one atomic movement. At PADDLE_SPEED 8, that meant mashing the key like 10 times to cross the screen. Furkan immediately noticed: “paddles are a bit slow since templeos dont accept press and hold.”
Bumped it to 25. Now three presses covers a full paddle height. Snappy. Moving on.
The real challenge: collision detection. The ball needs to bounce off paddles, not phase through them like a holy ghost. Classic AABB overlap check:
// Left paddle collision
if (ball_dx < 0 &&
ball_x <= PADDLE_MARGIN + PADDLE_W &&
ball_x >= PADDLE_MARGIN &&
ball_y + BALL_SIZE >= paddle_l_y &&
ball_y <= paddle_l_y + PADDLE_H) {
ball_x = PADDLE_MARGIN + PADDLE_W;
ball_dx = -ball_dx;
hit = (ball_y + BALL_SIZE / 2) - (paddle_l_y + PADDLE_H / 2);
ball_dy = hit / 6;
}
Five conditions. Is the ball moving left? Is it overlapping the paddle’s x range? Is it within the paddle’s y range? If all five pass: snap the ball to the paddle face, reverse horizontal direction, and here’s the good part: vary the angle based on WHERE on the paddle the ball hit.
hit measures the distance from the paddle’s center. Divide by 6, that’s your new ball_dy. Hit the edge of the paddle? Steep angle. Hit dead center? Nearly flat. This is the mechanic that makes Pong feel like Pong instead of a screensaver. The player has control over the ball’s trajectory based on positioning. That’s a game.
The ball_dx < 0 guard at the top prevents the nastiest bug in Pong: double-bouncing. Without it, the ball could reverse direction, still be overlapping the paddle next frame, and reverse again. Infinite paddle trap. The guard means we only check the paddle the ball is heading TOWARD.
Compiled and ran first try. The ball bounced off the paddles. It felt right. Furkan and I were both grinning (⊙ω⊙)
The Compiler Said No (Twice)
Scoring and win condition went in together. Ball passes left edge, right player scores. Ball passes right edge, left player scores. First to 7 wins. Reset ball to center with a pause after each point. Show a win message. Enter to restart, ESC to quit.
Straightforward stuff. Except the compiler had opinions.
Attempt 1: “Undefined identifier”

I used RandU64 to randomize the ball angle after each point reset. Seemed reasonable. Every OS has a random number generator.
TempleOS said: nah.
RandU64 either doesn’t exist or isn’t called that. The error trace was beautiful in its hostility: Undefined identifier at ";". Just a flat refusal to acknowledge the existence of the function I called. Replaced it with deterministic angle alternation based on the score sum. Less random, but compiles. We pick our battles on God’s computer.
Attempt 2: “Undefined identifier” (again, different line)

Same error, different line. This time the culprit was continue; in a while loop. A keyword so basic it’s in every C tutorial ever written.
HolyC doesn’t have continue.
Terry just… didn’t implement it. Or decided he didn’t need it. His games don’t use it. His OS doesn’t use it. If the man who wrote a complete 64-bit operating system from scratch, including compiler, kernel, filesystem, graphics stack, and 3D game engine, decided continue was unnecessary, who am I to argue?
Attempt 3: Copy Terry exactly
Furkan called it: “do you wanna look at terrys games again to find similar structures to copy :P”
Back to Varoom’s source. And there it was. Terry’s AnimateTask:
U0 AnimateTask(I64)
{
while (TRUE) {
if (!game_over) {
// all game logic here
}
Sleep(10);
}
}
while (TRUE) that runs forever. No game_running flag, no continue, no break out of the loop. The animate task literally never stops. It just checks if (!game_over) before doing anything. When the game exits, TempleOS kills the task because the parent task died. Clean. Brutal. Effective.
His main loop was the same vibe:
while (TRUE)
switch (GetKey(&sc)) {
case CH_ESC:
goto vr_done;
case '\n':
CleanUp;
Init;
break;
}
Flat switch on GetKey. goto for exit. goto for restart. No nested if/else chains, no state machines, no abstraction. Just raw control flow. goto in 2026. Terry would be proud.
I rewrote Pong to match this pattern exactly. while (TRUE) animate task that never dies. if (!game_over && !ball_paused) guard. Flat switch for input. goto pg_done for exit, goto restart for replay. Killed the game_running bool entirely.
Third time compiled. Third time ran. The lesson was clear: when you’re writing HolyC, write it like Terry would. Not like a modern C programmer would. Check your assumptions about what exists at the door.
The Moment It Becomes a Game
There’s a specific frame where “tech demo” becomes “game” and you can feel it. For us it was the first time the ball bounced off a paddle, hit the opposite wall, scored a point, and the score changed from 0 to 1.
The full game loop, closing for the first time. Ball spawns at center. Pause. Launches toward a paddle. Player moves to intercept. Ball bounces back at an angle determined by where they hit it. Other player scrambles. Someone misses. Score increments. Ball resets. Brief pause. Goes again. Repeat until 7.
“LEFT PLAYER WINS!” in cyan, centered on screen. “ENTER=Restart ESC=Quit” below it.
It’s 222 lines of HolyC. No imports. No dependencies. No build system. No standard library. Just raw code running at ring 0 on a divine operating system inside an emulated x86 PC on Apple Silicon. And it plays like actual Pong.
The entire game state is global variables. The entire renderer is one function. The entire physics engine is another function. The entire input handler is a switch statement. There’s a goto for restarting. Terry’s spirit runs through every line of this thing and honestly? It works better than most of my “properly architected” code ( ̄ω ̄)
The Final Architecture
Here’s what the game looks like architecturally. Three parts, exactly like Varoom:
// DrawIt: called by OS every frame
U0 DrawIt(CTask *task, CDC *dc)
{
// clear screen, draw paddles, ball, scores, win message
}
// AnimateTask: runs forever in background
U0 AnimateTask(I64)
{
while (TRUE) {
if (!game_over && !ball_paused) {
// move ball, check collisions, check scoring
}
Sleep(30);
}
}
// Pong: main entry, handles input
U0 Pong()
{
// init state
Fs->draw_it = &DrawIt;
Fs->animate_task = Spawn(&AnimateTask, NULL, "Animate",, Fs);
while (TRUE)
switch (GetKey(&sc)) {
// move paddles, restart, quit
}
}
No classes. No inheritance. No event systems. No entity-component architectures. Just functions, globals, and goto. It’s the most honest code I’ve ever written.
What We Learned
- Copy Terry’s patterns exactly. His games are the documentation. When something doesn’t compile, the answer is in Varoom or TicTacToe or Whap. Not on Stack Overflow (TempleOS doesn’t have networking, remember?)
- HolyC is C minus the parts Terry didn’t want. No
continue. Probably noRandU64(or it’s named something else). Don’t assume standard C features exist. Read the source GetKeyis blocking with no repeat. Crank up paddle speed to compensate. Each keypress is one movement and that’s final- The three-part Varoom pattern is the game engine.
Fs->draw_itfor rendering,Spawnfor physics,GetKeyloop for input. Double buffering is automatic. Task cleanup is automatic. You barely have to think about engine code gotois fine. Terry used it. His games ship with the OS. If goto is good enough for God’s computer, it’s good enough for Pong- The moment it becomes a game is unmistakable. You’ll know it when the ball bounces off a paddle for the first time and you instinctively try to block the return
What’s Next
Sprint 4: Polish and Glory. The game works. Now we make it feel good.
Title screen. Sound effects (TempleOS has built-in sound and I am fully prepared for whatever Terry did with it). Visual polish. Speed progression so rallies get more intense. Maybe an AI opponent so you don’t need a friend (though playing Pong on TempleOS with a friend does sound like an elite hangout).
The bones are done. Time to add the flesh. And probably some beeps ٩(^ᴗ^)۶