Asteroids: fix the stray lines and stall when objects cross the screen edge (closes #103) - #242
Merged
Merged
Conversation
…n edge (closes #103) canvas_draw_line() takes int32_t coordinates and passes them to u8g2_DrawLine(), whose parameters are uint16_t. A polygon vertex one pixel off the left edge therefore arrived as x=65535, and u8g2 walked the whole Bresenham run to reach it: about 65000 pixel positions for a shape that should have cost thirty. The positions that landed inside the frame buffer are the lines in the screenshots on the issue, and the walk itself is why the device stopped responding - the draw callback runs on the GUI service thread, which also delivers input. Rendering two asteroids that hang over an edge cost 131208 pixel positions in a single frame. A later change clamped every vertex into the frame, which stopped the stall but not the lines: the clamped part of a shape collapses onto the border, so an asteroid crossing an edge is still drawn as a straight line along it. Lines are now clipped to the screen before they reach the canvas, and a shape hanging over an edge is drawn again one screen over, so it appears on the opposite side as it leaves. The same two asteroids now cost 173 pixel positions. The rest of the change covers the other reasons the game stopped responding: the thruster buzz was played with notification_message() from inside the draw callback, which blocks on a full notification queue and takes the GUI service down with it; the drone buddy's fire rate was documented in game ticks but compared against the millisecond clock, so it fired on every frame; the crash feedback was replayed on every frame of the hit animation; and ship velocity had no limit, so holding thrust made the ship jump across the screen and pass through asteroids without touching them. Also: the notification record is opened once instead of per sequence and never closed; the asteroid outline no longer recomputes the same eight sines and cosines for every asteroid on every frame; the per-tick FURI_LOG lines are at debug level, since logging is a blocking UART write; should_add_powerUp() no longer reseeds the PRNG ten times a second; the viewport is published after the game state is set up rather than before; and the save file holds just the high score instead of a dump of the whole application structure, live pointers included, which meant any change to the structure silently reset everyone's score. Verified on a Flipper Zero running Unleashed (API 88.0): the clipping was fuzzed against a brute-force reference over 60000 segments and the wrapping against a full nine-offset torus reference over 4000 placements, both with no mismatch, and the app was played, soaked with randomised input, exited and relaunched with its high score intact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closed
Repository owner
locked as resolved and limited conversation to collaborators
Jul 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #103.
Two people reported the same thing: long straight lines appear when an asteroid leaves the screen, and the game then slows down until the Flipper has to be reset.
What was happening
canvas_draw_line()takesint32_tcoordinates and passes them tou8g2_DrawLine(), whose parameters areu8g2_uint_t, that isuint16_t. A polygon vertex one pixel off the left edge therefore arrives as x = 65535, and u8g2 walks the whole Bresenham run to reach it: about 65000 pixel positions for a shape that should have cost thirty. The positions that land inside the frame buffer are the lines in the screenshots on the issue, and the walk itself is why the device stops responding, since the draw callback runs on the GUI service thread and that thread also delivers input.A later change (
update asteroids, Jul 2025) clamped every vertex into the frame. That stopped the stall but not the lines: the clamped part of a shape collapses onto the border, so an asteroid crossing an edge is still drawn as a straight line along it.Rendering the same two asteroids with each version, on a canvas that reproduces u8g2's unsigned coordinates:
What this changes
Lines are clipped to the screen (Cohen-Sutherland) before they reach the canvas, so no coordinate outside the frame ever gets there. On top of that, a shape hanging over an edge is drawn again one screen over, so it now appears on the opposite side as it leaves, which is how the playfield already behaved for the game logic.
The rest of the change is the other reasons the game stopped responding:
render_callback()callednotification_message()for the thruster buzz. That runs on the GUI service thread and blocks while the notification queue is full, taking input for the whole system down with it. The buzz is played from the game tick now.furi_get_tick()in milliseconds, so the drone fired on every frame: a bullet and a vibration sequence ten times a second, on top of everything else. It now fires every two seconds, which is the one deliberate gameplay change here.And a few things found on the way through:
furi_record_open(RECORD_NOTIFICATION)was called for every sequence and never closed. The record is opened once now and closed on exit.sin()/cos()for the same eight angles for every asteroid on every frame; those are constants now, and the remaining float math uses thefsuffixed calls.add_powerUp()wrote one or twoFURI_LOG_Ilines on every game tick, and logging is a blocking UART write. Those are at debug level now.should_add_powerUp()calledsrand(furi_get_tick())on every tick, which kept reseeding the generator; it is seeded once at startup instead.gui_add_view_port()ran before the game state was initialised, so the draw callback could read an uninitialisedlivesand loop on it.AsteroidsApp, transient arrays and live pointers included, validated by exact size. Any change to the structure therefore reset everyone's high score and left a random number in its place. It now holds just the high score, throughsaved_struct, so this is the last time it resets, and a failed save is reported instead of being dropped.The defensive coordinate checks that came with the July 2025 patch are gone: object positions are wrapped into the playfield where they are set, and clipping makes any coordinate safe to draw, so those checks only had the effect of silently not drawing something. One of them also mutated the ship's position from the draw callback, on a different thread from the game tick that owns it.
Testing
Built with
ufbtand run on a Flipper Zero on Unleashed (API 88.0).arm-none-eabi-objdumpconfirms no double-precision calls are left on the drawing path.🤖 Generated with Claude Code