From 0e670d7224f8547fb764206e8f45fd1e3779ab80 Mon Sep 17 00:00:00 2001 From: Luxferre Date: Sat, 24 Oct 2026 14:21:24 +0300 Subject: [PATCH] games: jitter-free stepping via timerfd + clearer Snake playfield Replace the select() timeout sleep with a Linux timerfd high-resolution timer. It fires on an absolute CLOCK_MONOTONIC deadline, so wakeups are no longer quantized to the kernel tick (a short final wait was rounded up to a whole tick, causing +-10-22ms jitter and lurches). The loop now select()s on both the X connection fd and the timer fd, so input still wakes it immediately and no busy-spin is needed -- the controls stay responsive. A select()-timeout fallback is kept for non-timerfd setups. Snake: draw a bright frame around the actual playfield so its boundary is unmistakable, and move the SCORE/BEST label down into the margin between the window frame and the playfield frame. --- overlay/rootfs/usr/bin/snake | Bin 13716 -> 13716 bytes overlay/rootfs/usr/bin/tetris | Bin 17812 -> 17812 bytes prebuilt/games/usr/bin/snake | Bin 13716 -> 13716 bytes prebuilt/games/usr/bin/tetris | Bin 17812 -> 17812 bytes src/games/snake.cpp | 162 ++++++++++++++++++++++++---------- src/games/tetris.cpp | 150 ++++++++++++++++++++++--------- 6 files changed, 225 insertions(+), 87 deletions(-) diff --git a/overlay/rootfs/usr/bin/snake b/overlay/rootfs/usr/bin/snake index 9f8bcdf795c3936f5ecf7cc0867739d4f98014a0..8bdf63a03475762a15d5765ded9655c9c6652892 100755 GIT binary patch literal 13716 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 diff --git a/overlay/rootfs/usr/bin/tetris b/overlay/rootfs/usr/bin/tetris index 84af923ed46bbaff202bf859eeaae33b37f8167f..ebc99c4696c4a4dd128bb9a2f903ef90fa9672b7 100755 GIT binary patch delta 7285 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 I*(Fu~4MzF=>Hq)$ delta 6876 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 diff --git a/prebuilt/games/usr/bin/tetris b/prebuilt/games/usr/bin/tetris index 84af923ed46bbaff202bf859eeaae33b37f8167f..ebc99c4696c4a4dd128bb9a2f903ef90fa9672b7 100755 GIT binary patch delta 7285 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 I*(Fu~4MzF=>Hq)$ delta 6876 zcmcgxYgAN6w%+G7kM8Cn3L-C|=|*V42MQ`af-N39C}c1*YDk=kIMIj>DpBJo;2ba} zV{Tj#E#nyB8pq_C#3-6*?jjF^*I>rUf{70%8iUjLh~Z76NFT+}_p5Huw%5&{+`I1S zwfp<3YFE{+T~)hJRquRF*!h}J79u_!vOO$vaLO2>0ZApI)a|ntq!l2OizhNDfJgv% zq&<|OBGFM4di9qhNxmp|%JysXhq!9rNA>#xn=OO|&HP~Nk=It3drq%f7!$U9>CVlU ztJQ415HWZnTHw7ndW@mciR?cF!;mU1?|?`}{lvsB3t>-&bItx|2yhPXzyIOs$!_U% zw{*E%dJmf?P7{xbY?l~0xWcWF*l96J_HoPo&dr4JZs`?n`U!679|!COa+AMytKa=- zTBNP)WmVLWzq&PZLty`@`qjA2ZrSS|txfaY(hbCdJtOsRxfr1XU*x9NMQoi%`z>(aobn9l^=IYf687|Mag|`xfk48dEBl1cWz@k<);6Nn|=w>uf`GaYD376YLNZ} z%f^}3GUmBYzi`&vCl;<=vUJ&+wM$mdePTxbs+CLTF8Y1`5?Z)$?Xne1CZrsD!K++g z5o(Qh{^DipR;*eKK5=CJvfn?qaLw99tJf}Ev1r*!TDoLy!Lr4u1j6gBdVcYu4gD>! zz3Mb3Xrh=^?ZtGOFh~qUEAs^|Py_72j&eNnVmJ731MS>KJbcp(1FHM+(llqfu^-#| zkxb6~2Be3oh;DH7C2Hb0n8?a8lBkBcLlJy9h7qmh7*4dFV+2t#$KgaJ9HWRfbHu>5aEvC}%F#en#xaIy zC&v*)yQh&%!LdXYJi|z|k7GR1I~)^;-s6}^bco|fL^qD3h>mj{O;p7Z5%F`5Nkk_( zjwPz$IF9HH$7G^fjwwWEIgTf)=QsgV|FKNfCz=FP!Q+_c#!)|bG`{tNf8(}Bwe^c1 zOsGI#ZM&xWdh0b)_vb$h@KhI`E?hq9l6cRB?{ne1U3i%b-{QhcT=;qyUf{y>TzIaN%MSPExD?EB z;TbO6ADI|Y4R zvqy~A&QL>LvnW}V`?Kt}k2)-h5-*sGr9!D5&DJRSfp%MZr`;CO>1i(c(4sU<63z9< zEpn8Xbd*h*d3T$)-L|j82>V{|w8+ZIS){ippNCRxu_VN8kg~0FG`um3a_CjNZ6&&R zFVqu_z-s*lNs;Wf#T`LqK_7@rXApKJIGPVW>?mg*2Gw8ew&NYChQ!=U7*<_#0L&d? zxY8crbESPqI5#Ey1FoB3o^f}Z)^2-hH#*1)9p~s^0~(*uF|=&xL9RH(k!QDE>)@$m zM`|3bKGRX7=v2IsdZoZ{R@s`Q=@pX~EGNdh1}%_l9QC=r-L|fS ztULMy>*os6$z#n%b9tT*v)c}I>{fzwiFyBQ76nQmYyKvSl2Mi2UAS88Ro(5C$iVqg z^nSF%r@T~BS))|m_f)rinMH}o<8A*5w(sgN9pik$kx9^OYoAaqPKlBe2Vb%@sjTk= z3_KMBB!E3&0UIq!!?jmb60kd@!NA2rS@HK^F3*g5|l)VWR2V;z;L@Q zt7CK?%zD9ToY3pl!_Bwbp6&3G4zy}Cmlbu=CtdQHLB{HA>lw`%Y4^;K3!0sC&7Ybr zO7S0M>7pVT_-MtK_UFCVDyq|GZmlN0px3u(#cZpfrG)l#nhJDLC|F|@isqdT=@`R1 zT}=jUueZ(FVQh!DR2`lz>Bg9D1-=B2U?AZexv76I{ZKZq{L!L>OysL9XjqZ7LAv0W z3T^vi<-60#`hBTIq5hF?K6mu$mF6_V#EoBJDrt<|U3}!-t7N@tFIR#JczW%Tx2{rj zqM5d<_3^o>=BM7rVs4Mmqj<7@R{8?Ix0bGtr(2(uM#blUr<;@$FE4}p2V2im9zN?G z-<-Lpc)1Pb*Xa*Jj*v&MJ)${K;}@X(prPvy#vA{iK2FmHy0yW8>>j^0Z^A=e?<+ zt;gC|sZ_0{D!NluU{MyI7Q3)gT4w}~K6qbx()`?Ai?%@_s%szZQ`eppenD>c@VU_L z6<*gq4jC!oWPP!hj3#T^roSnsw=IhCfz?|okHMUX{!vllu5)CS`z%Vp1C2jc?uUj4 z#^o`B4~vg}7#5n`y|gmhYEL5T#ru3<&sDw-y&EaqKQbS;-2K9l3(8c3U)MXn7cjnS z<_n4`87^C^Wxt7dCLLq)M-WUAttwHLa;i^tk$;g?*X;kRusrWORco{r-nTrS((Ld@ zx!77Nj(*>7yQ(!kQ2WL_$B3^eG5a2jxPQQdzHtm$#+VN^4WcP|etT*aN*a?FZGK8h zHIwz$J^nlJMXAU5gXy=i%S|c%6-AQv-<@+PR*u3Ll2RZskB;@$VL?aM=Nu#LJRAnC zpqN7GJbXck)4HEOB49ndQYykLwn}&~>V9A5z{0q6Sk>5@N%|rw?8SRo#ZrCqnE<-w`-QxAExd3_X*bsgDdL<5b@z(@!9&*9H` z<>&N&E?@q+0-panNOq5%Pf+*whYS1X^7c){0R!TI>OA%V-xCNn|An>hk2nyRQ~i%F z67NeqUu-QAeeYBiTa@Ehg|WV^=@AnS_DT7O0sle_&~C45pCDt`r-nCRQ#3fDz+|MS zA_}a#8}ga2~tLOF@gz?n!3f zx5$vL5t95`H;Jc|O9f$Odc$rDZmGRN*73b>-w?3PmmZ=A;cfAuZ8tNr9z zi~r2*+y{nJe5)W#%snLEBx^|T$seX?Ucp{>Y`)y6jcL&PQki<4>!crAano)))iOCV zLO(e}h`ye)UJB2%+mc!Yo!vIO#S6!e1{@&Ta2z2VLG~hD0a=c7)2|`(WhBOd^gzJ~ zyorNV8O|{I5F^AFqTph|OOG#WA&!p~BZ$KmS|4p(O&eUSHX;3@=ugaeotPK5=)MjXn%g;YUWAZO6# zp9V8+Xqb3l1d9z#39M-LJW40ZaEQBvvKmM&TM-)NHy;PuxsVK$)U!83iAs zP7|vOjgoVl?Y3&WHuHK;_=lQ|^>|b2X%h5mcsZ;f-~?6Qhh_`TiH&IWYw)u`J%&;X zuKw6N(|dN2RGyRgq1P<$j13a6KehQpliGCfN2=b1dN-h70o`ju;fZGJbi^p%z$d)T zTg<`3H%dY54`DOKEwL;(JV7|XriTxmRn=6lj5Iid-_9nyV9&2tbcUh`9tP_ab;&$* zaiFPAp|UCFx@O@OUMf8143c|LH>tnwI`&rh(Aa-#@^00txE`+|OySy2-Qp%sN06lI z(Xs4uxL(L$KZS1;Vp&nd2yGrLH$nd7&k}~m2M0rf{m?SXKAa3U#CDv!#!mjG? z%-Pd6s2twjH{k0&n;tDV=d{nZwGCw@nK&{yXQIzG1f|Yd>$4R*N*B14dbG3Hn51FP z;5Sca*yETyOy>l3EbZ#E<*^kp;r`9N4-K5!#&*R-3VYedG3R7;@64I)=W1vB-T7%= zy^@g}G`>*M;Z+${w9*@b9@UbwqW2+K%}Ovn_w1@vK851x94ySKjZ+akS)UMYiR%@-75Dy|L7xr0=cF67-dsYod## zeYzrv7%xYl>^+z%=!>EAFweL50P+sY=yjTDO6b8!Y?Is0?m ztHPITuyNE7|9j&duFB|lRZ0t+V~iAf*eatzzH!&@$39zk3sq;}n(en(OMHFt3n?wq zuj7JhxhEDYaK0CUlLv0jAYUN%8iD?hsi3qkYlF!wcg`vuh``y)51z3n(Z=*>GKx5eDsJ% z$@*4~>Jn3%Ge5V>E}ya6*FB3qIPyD7jx9iJVaK&9 zVu9nj6{?PVOV~;n7r|D?{bfXd`c9vHRT(P7A0*)m{vduqjEzg)7z{SK<31FDlRs=# znx{T!n)BMW0D8DABwT4a?nN`OfjMtfvt4PigHNi7@EYT|ezpR$SY2AO%>RQJcxuqg z|FbOkKj~25?#06|v3Xk5P&lnJU9aIj #include #include +#include +#include +#include +#include static const int TOPBAR = 24; static const int COLS = 24; @@ -111,9 +115,9 @@ static int speed = 100; // ms per step (derived from pps below) static int pps = 90; // base speed px/sec; +1 px/sec per 5 food eaten static bool running = true; static unsigned long now_ms() { - struct timeval tv; - gettimeofday(&tv, nullptr); - return (unsigned long)(tv.tv_sec * 1000 + tv.tv_usec / 1000); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (unsigned long)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); } // Derive the per-step delay (ms) from the target pixels/second. The snake @@ -270,14 +274,20 @@ static void render() { // head XSetForeground(dpy, gc, C_HEAD); XFillRectangle(dpy, back, gc, gx0 + snake[snakelen - 1][0] * cell, gy0 + snake[snakelen - 1][1] * cell, cell, cell); + // bright frame around the playfield so its boundaries are unmistakable + XSetForeground(dpy, gc, C_FG); + XDrawRectangle(dpy, back, gc, gx0, gy0, gw, gh); - // score (top status strip, clear of the playfield border) + // score / best sit in the margin between the window frame (outer) and the + // playfield frame (inner), vertically centred there so the playfield boundary + // stays clear + int scoreY = (2 + gy0) / 2 - 3; char buf[32]; snprintf(buf, sizeof(buf), "SCORE %d", score); - draw_text(gx0, 4, 1, C_DIM, buf); + draw_text(gx0, scoreY, 1, C_DIM, buf); if (best > 0) { snprintf(buf, sizeof(buf), "BEST %d", best); - draw_text(gx0 + gw - 11 * 6, 4, 1, C_DIM, buf); + draw_text(gx0 + gw - 11 * 6, scoreY, 1, C_DIM, buf); } if (state == PAUSED || state == GAMEOVER) { @@ -402,55 +412,117 @@ int main() { render(); int xfd = ConnectionNumber(dpy); - const unsigned long FRAME_MS = 1000 / 30; // 30 fps rendering - unsigned long lastFrame = now_ms(); - unsigned long accumulator = 0; + + // Precise, drift-free stepping via a kernel high-resolution timer (timerfd). + // The timer fires on an absolute CLOCK_MONOTONIC deadline, so wakeups are not + // quantized to the kernel tick the way a select() timeout is. A select() + // timeout shorter than one tick gets rounded UP to a whole tick, which made + // the final few milliseconds of each wait overshoot by up to ~20ms and showed + // up as speed jitter. Input still arrives on the X connection fd, so we + // select() on BOTH fds and react to either immediately -- there is no + // busy-spin, so the X server stays free and the controls stay responsive. + int tfd = -1; +#if defined(__linux__) + tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); +#endif + + unsigned long nextStep = now_ms() + speed; + State prevState = PLAYING; + + auto arm_timer = [&](unsigned long deadline_ms) { + struct itimerspec its; + its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0; // one-shot + its.it_value.tv_sec = (time_t)(deadline_ms / 1000); + its.it_value.tv_nsec = (long)((deadline_ms % 1000) * 1000000UL); + if (its.it_value.tv_nsec >= 1000000000L) { + its.it_value.tv_sec += 1; its.it_value.tv_nsec -= 1000000000L; + } + timerfd_settime(tfd, TFD_TIMER_ABSTIME, &its, nullptr); + }; + auto disarm_timer = [&]() { + struct itimerspec its; + memset(&its, 0, sizeof(its)); + timerfd_settime(tfd, 0, &its, nullptr); + }; + + if (tfd >= 0) arm_timer(nextStep); while (running) { - // Process all pending input immediately (non-blocking) before each frame - while (XPending(dpy)) { - XEvent e; - XNextEvent(dpy, &e); - handle_event(e); + fd_set fds; + FD_ZERO(&fds); + FD_SET(xfd, &fds); + int nfds = xfd + 1; + struct timeval tvbuf, *tvp = nullptr; + + if (tfd >= 0) { + // While playing, also wait on the timer fd. While paused / game over the + // timer is disarmed, so we block on input only (resume stays instant). + if (state == PLAYING) { + FD_SET(tfd, &fds); + if (tfd + 1 > nfds) nfds = tfd + 1; + } + } else { + // Fallback (timerfd unavailable): bounded select so input stays live. + long sleepMs = (state == PLAYING) ? (long)nextStep - (long)now_ms() : 30; + if (sleepMs < 0) sleepMs = 0; + tvbuf.tv_sec = sleepMs / 1000; + tvbuf.tv_usec = (sleepMs % 1000) * 1000; + tvp = &tvbuf; + } + + int r = select(nfds, &fds, nullptr, nullptr, tvp); + if (r < 0) { if (errno == EINTR) continue; break; } + + // 1) Drain pending input first so key presses are never delayed. + if (FD_ISSET(xfd, &fds) || XPending(dpy)) { + while (XPending(dpy)) { + XEvent e; + XNextEvent(dpy, &e); + handle_event(e); + if (!running) break; + } if (!running) break; } - if (!running) break; - unsigned long now = now_ms(); - unsigned long elapsed = now - lastFrame; - lastFrame = now; - - // Cap elapsed time to prevent spiral of death - if (elapsed > 200) elapsed = 200; - - // Accumulate time - accumulator += elapsed; - - // Step game logic at fixed rate (max 2 steps per frame to prevent spiral) - int steps = 0; - while (accumulator >= (unsigned long)speed && steps < 2) { - if (state == PLAYING) step(); - accumulator -= speed; - steps++; + // 2) (Re)start play cleanly: resync the clock on resume / restart / launch, + // and stop the timer whenever we leave the PLAYING state. + if (state != prevState) { + if (state == PLAYING) { nextStep = now_ms() + speed; if (tfd >= 0) arm_timer(nextStep); } + else if (tfd >= 0) disarm_timer(); + prevState = state; } - // Render every frame + // 3) Advance the simulation exactly once per timer expiration (or, in the + // fallback path, whenever the deadline is reached). If we ever fell more + // than one interval behind (e.g. the process was frozen for a screenshot) + // we drop the backlog instead of racing to catch up. + if (state == PLAYING) { + bool due = false; + if (tfd >= 0) { + if (FD_ISSET(tfd, &fds)) { + uint64_t expirations = 0; + ssize_t n = read(tfd, &expirations, sizeof(expirations)); + (void)n; (void)expirations; + due = true; + } + } else { + if ((long)(now_ms() - nextStep) >= 0) due = true; + } + if (due) { + step(); + nextStep += speed; // advance by exactly one interval (no drift) + unsigned long now = now_ms(); + if ((long)(now - nextStep) > (long)speed) nextStep = now + speed; + if (tfd >= 0 && state == PLAYING) arm_timer(nextStep); + } + } + + // 4) Repaint on every loop turn (input or step) so the display never + // appears frozen and key feedback is immediate. render(); - - // Frame pacing: sleep until next frame, but wake up immediately on input - now = now_ms(); - long sleepMs = (long)FRAME_MS - (long)(now - (lastFrame - elapsed)); - if (sleepMs > 0 && sleepMs <= (long)FRAME_MS) { - struct timeval tv; - tv.tv_sec = sleepMs / 1000; - tv.tv_usec = (sleepMs % 1000) * 1000; - fd_set fds; - FD_ZERO(&fds); - FD_SET(xfd, &fds); - select(xfd + 1, &fds, nullptr, nullptr, &tv); - } } + if (tfd >= 0) close(tfd); XFreeGC(dpy, gc); XFreePixmap(dpy, back); XDestroyWindow(dpy, win); diff --git a/src/games/tetris.cpp b/src/games/tetris.cpp index def1d2a..14aa29b 100644 --- a/src/games/tetris.cpp +++ b/src/games/tetris.cpp @@ -22,6 +22,10 @@ #include #include #include +#include +#include +#include +#include static const int TOPBAR = 24; static const int PCOLS = 10; @@ -136,9 +140,9 @@ static bool collide(int px, int py, int cells[4][2]) { } static unsigned long now_ms() { - struct timeval tv; - gettimeofday(&tv, nullptr); - return (unsigned long)(tv.tv_sec * 1000 + tv.tv_usec / 1000); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (unsigned long)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); } // Derive the gravity delay (ms) from the target pixels/second. A piece drops @@ -516,55 +520,117 @@ int main() { render(); int xfd = ConnectionNumber(dpy); - const unsigned long FRAME_MS = 1000 / 30; // 30 fps rendering - unsigned long lastFrame = now_ms(); - unsigned long accumulator = 0; + + // Precise, drift-free stepping via a kernel high-resolution timer (timerfd). + // The timer fires on an absolute CLOCK_MONOTONIC deadline, so wakeups are not + // quantized to the kernel tick the way a select() timeout is. A select() + // timeout shorter than one tick gets rounded UP to a whole tick, which made + // the final few milliseconds of each wait overshoot by up to ~20ms and showed + // up as speed jitter. Input still arrives on the X connection fd, so we + // select() on BOTH fds and react to either immediately -- there is no + // busy-spin, so the X server stays free and the controls stay responsive. + int tfd = -1; +#if defined(__linux__) + tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); +#endif + + unsigned long nextStep = now_ms() + speed; + State prevState = PLAYING; + + auto arm_timer = [&](unsigned long deadline_ms) { + struct itimerspec its; + its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0; // one-shot + its.it_value.tv_sec = (time_t)(deadline_ms / 1000); + its.it_value.tv_nsec = (long)((deadline_ms % 1000) * 1000000UL); + if (its.it_value.tv_nsec >= 1000000000L) { + its.it_value.tv_sec += 1; its.it_value.tv_nsec -= 1000000000L; + } + timerfd_settime(tfd, TFD_TIMER_ABSTIME, &its, nullptr); + }; + auto disarm_timer = [&]() { + struct itimerspec its; + memset(&its, 0, sizeof(its)); + timerfd_settime(tfd, 0, &its, nullptr); + }; + + if (tfd >= 0) arm_timer(nextStep); while (running) { - // Process all pending input immediately (non-blocking) before each frame - while (XPending(dpy)) { - XEvent e; - XNextEvent(dpy, &e); - handle_event(e); + fd_set fds; + FD_ZERO(&fds); + FD_SET(xfd, &fds); + int nfds = xfd + 1; + struct timeval tvbuf, *tvp = nullptr; + + if (tfd >= 0) { + // While playing, also wait on the timer fd. While paused / game over the + // timer is disarmed, so we block on input only (resume stays instant). + if (state == PLAYING) { + FD_SET(tfd, &fds); + if (tfd + 1 > nfds) nfds = tfd + 1; + } + } else { + // Fallback (timerfd unavailable): bounded select so input stays live. + long sleepMs = (state == PLAYING) ? (long)nextStep - (long)now_ms() : 30; + if (sleepMs < 0) sleepMs = 0; + tvbuf.tv_sec = sleepMs / 1000; + tvbuf.tv_usec = (sleepMs % 1000) * 1000; + tvp = &tvbuf; + } + + int r = select(nfds, &fds, nullptr, nullptr, tvp); + if (r < 0) { if (errno == EINTR) continue; break; } + + // 1) Drain pending input first so key presses are never delayed. + if (FD_ISSET(xfd, &fds) || XPending(dpy)) { + while (XPending(dpy)) { + XEvent e; + XNextEvent(dpy, &e); + handle_event(e); + if (!running) break; + } if (!running) break; } - if (!running) break; - unsigned long now = now_ms(); - unsigned long elapsed = now - lastFrame; - lastFrame = now; - - // Cap elapsed time to prevent spiral of death - if (elapsed > 200) elapsed = 200; - - // Accumulate time - accumulator += elapsed; - - // Step game logic at fixed rate (max 2 steps per frame to prevent spiral) - int steps = 0; - while (accumulator >= (unsigned long)speed && steps < 2) { - if (state == PLAYING) step(); - accumulator -= speed; - steps++; + // 2) (Re)start play cleanly: resync the clock on resume / restart / launch, + // and stop the timer whenever we leave the PLAYING state. + if (state != prevState) { + if (state == PLAYING) { nextStep = now_ms() + speed; if (tfd >= 0) arm_timer(nextStep); } + else if (tfd >= 0) disarm_timer(); + prevState = state; } - // Render every frame + // 3) Advance the simulation exactly once per timer expiration (or, in the + // fallback path, whenever the deadline is reached). If we ever fell more + // than one interval behind (e.g. the process was frozen for a screenshot) + // we drop the backlog instead of racing to catch up. + if (state == PLAYING) { + bool due = false; + if (tfd >= 0) { + if (FD_ISSET(tfd, &fds)) { + uint64_t expirations = 0; + ssize_t n = read(tfd, &expirations, sizeof(expirations)); + (void)n; (void)expirations; + due = true; + } + } else { + if ((long)(now_ms() - nextStep) >= 0) due = true; + } + if (due) { + step(); + nextStep += speed; // advance by exactly one interval (no drift) + unsigned long now = now_ms(); + if ((long)(now - nextStep) > (long)speed) nextStep = now + speed; + if (tfd >= 0 && state == PLAYING) arm_timer(nextStep); + } + } + + // 4) Repaint on every loop turn (input or step) so the display never + // appears frozen and key feedback is immediate. render(); - - // Frame pacing: sleep until next frame, but wake up immediately on input - now = now_ms(); - long sleepMs = (long)FRAME_MS - (long)(now - (lastFrame - elapsed)); - if (sleepMs > 0 && sleepMs <= (long)FRAME_MS) { - struct timeval tv; - tv.tv_sec = sleepMs / 1000; - tv.tv_usec = (sleepMs % 1000) * 1000; - fd_set fds; - FD_ZERO(&fds); - FD_SET(xfd, &fds); - select(xfd + 1, &fds, nullptr, nullptr, &tv); - } } + if (tfd >= 0) close(tfd); XFreeGC(dpy, gc); XFreePixmap(dpy, back); XDestroyWindow(dpy, win);