Wuss sourpuss - #11
Merged
Merged
Conversation
dpt
commented
Sep 6, 2026
Owner
wuss_window_move's fast path slid occlusion-clean pieces of a window's old footprint straight onto its new position, but occlusion-clean isn't the same as settled: a piece can still have part of it queued in wuss->dirty[] from an earlier invalidation this frame that wuss_redraw_dirty hasn't repainted yet (e.g. a screen-edge clip from a prior move in the same input batch). Sliding that stale ground pasted it, untouched, onto the window's new position. Emscripten's browser event queue commonly hands SDL several MOUSE_MOTION events per animation frame before the demo's single wuss_redraw_dirty() call, so batched drags near a screen edge hit this every time; native SDL rarely batches more than one motion event per frame, which masked it on macOS. Strip pending wuss->dirty[] regions out of the clean source set before sliding, so only pixels already settled on screen are treated as a valid blit source.
screen_draw_dashed_line clipped x0,y0 to the screen bounds but always reset the dash phase counter to 0 at the new, clipped start point. When a dashed rule (e.g. a menu divider) is scrolled partially off-screen and back, the amount clipped off the start varies per frame, so the dash pattern drifted/smeared relative to its unclipped appearance. Capture the pre-clip start point, compute how many pixels were skipped by the bounds clip, and seed phase with that count mod the dash period so the pattern stays anchored to the original line.
Classic minesweeper on a 12x12/20-mine board: Select reveals (flood-filling zero-neighbour cells), Adjust flags, a MENU click pops a New Game entry, and either click retries a finished board. A one-cell border frames the grid; neighbour counts draw in the bold digit font, centred via bmfont_measure. Wired into apps/wuss/main.c's Launch menu alongside the other demo tasks.
wuss__menu_spawn sized the menu window's content width purely from the widest item label; a title longer than every item (e.g. a one-item menu) got clipped by the titlebar instead of widening the row to fit. Measure the title too (same bold-font-with-fallback rule wuss__titlebar_draw uses) and take the wider of the two.
HUD strip above the grid shows mines-remaining (mines minus flags) on the left and elapsed seconds on the right. Timer starts on the first reveal and freezes once the game ends; a wuss_EVENT_IDLE handler invalidates the window once per elapsed second to keep it live.
The row highlight previously swapped fg/bg across the full item bbox, darkening the tick and submenu-arrow gutters too. Confine the highlight fill and text draw to the column between the two gutters, reserving the arrow gutter even on rows with no arrow so every row's highlight lines up at the same right edge. The text itself is drawn as if a space padded it either side (a measured space-width inset), without touching the item's actual string, and the menu-width calculation budgets two of those space-widths so the window is sized to match. Rename WUSS_MENU_TICK_W/WUSS_MENU_ARROW_W to WUSS_MENU_GUTTER_LEFT/ WUSS_MENU_GUTTER_RIGHT and drop the now-redundant fixed WUSS_MENU_TEXT_PAD gap.
…der menu Top-level MENU click on the text task now opens a root menu with two submenus: "Font" (unchanged fontmenu picker) and "Sample" (Quick Brown Fox, two more pangrams, and Lorem Ipsum, the prior fixed paragraph and still the default). text_redraw draws whichever text_samples[] entry is currently selected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fmt, bytespp, info_ptr and outrow are set before the setjmp and read after a longjmp back into it, so the compiler can't prove they survive the jump. Mark them volatile.
The RISC OS cross-compiler warned that callers pass a non-const pattern_runs_t where the callees declared a const-qualified one, even though the conversion is legal. The params are read-only by convention already; drop the const to match what callers actually pass.
wuss_colour_t is unsigned char and screen_pattern_t is an unsigned enum, so the "< 0" half of each range check is always false and warns under -Wtype-limits. The ">=" upper-bound check already covers the real validation.
Same -Wtype-limits issue as backdrop.c: wuss_colour_t is unsigned char, so "< 0" is always false. Applies to validate_bevel_backdrop and the furniture-palette range check in wuss_create.
Same -Wtype-limits issue: spec->pattern, fg, bg and swatch are all unsigned, so the "< 0" half of each range check is always false.
Replace the hardcoded PICO-8/WIMP16 palette tables with a runtime scan of resources/palettes/*.hex, so new palettes drop in without a rebuild. Add wuss_menu_set_item_ticked to the core menu API so a menu tracking more than one independent tick (selected file plus an Invert toggle) can update a single row in place on an ADJUST pick, without reopening or repositioning the chain. Guard both tick calls against a stale handle after a SELECT pick, which closes and frees the chain before delivering the event. Tick the menu row matching the actual startup palette (WUSS_PALETTE env var or PICO-8 default) on first open, and reset Invert whenever a new palette file is picked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ETTE The palette picker task previously handed its loaded/inverted array to an app-supplied on_select callback, which then had to know about the framebuffer bitmap and physical frontend to push it there. Replace that with the event wuss already broadcasts: palette_create no longer takes a palette/npalette/on_select/user_data at all, palette_menu_select installs a pick with wuss_set_palette directly, and the swatch grid reads the live array back with the new wuss_get_palette accessor on every redraw instead of caching a caller-owned pointer. main.c's menu_task now does the bitmap/frontend push itself from a wuss_EVENT_PALETTE case, reading the same accessor, so the palette task has no reason to know the frontend exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Q collided with normal typing in text-entry tasks; F4 was free since the palette-cycle key that used to live there was removed. Escape still quits either way. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bmfont_enumerate and the wuss palette-picker task each carried their own copy of the RISC OS OS_GBPB / Win32 FindFirstFile / dirent flat-directory walk. Move the walk into a new io/dirscan module; both callers now just filter/collect from dirscan_walk's per-leafname callback.
Replace the default emscripten boilerplate page with a wuss-specific shell (title, control key legend, about blurb), wired via --shell-file.
…ling Add wuss_window_set_doc so a window's virtual document extent can change after creation (used when cycling to a differently-sized image), replace image.c's hardcoded name list with a dirscan_walk() scan of resources/images, and add new sample images for it to cycle through. Add a wuss-test.c case for a dirty-rect containment bug hit while working on this: wuss_invalidate() must not shrink an existing dirty rect down to a newly-invalidated box that is fully contained within it.
…buffer path_join_filename returns a shared static buffer. spawn_image held the ninepatch pointer into it, then image_create called path_join_filename again for its resources/images dirscan, overwriting that buffer with a directory path. bitmap_load_png then fopen'd the directory, file_is_png failed, and image_create returned result_BAD_ARG. Copy the ninepatch path into its own buffer before the call. Also make image_click swallow a failed reload (warn, keep the current image) rather than propagating rc, since some sample PNGs are sub-8-bit and bitmap_load_png returns result_INCOMPATIBLE for them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1/2/4-bpp paletted PNGs were rejected with result_INCOMPATIBLE by the blanket pngbitdepth != 8 check. Restrict that check to non-palette colour types and call png_set_packing() before png_set_palette_to_rgb() so libpng unpacks the sub-byte indices to one per byte; the existing palette-to-RGB expansion then handles the rest unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… window off-edge wuss_mouse_move now pins the incoming point to [0, scr size - 1] before it feeds the drag-move/resize/scrollbar paths and the stored wuss->pointer, so a pointer report from outside the frame can no longer carry a window off the desktop. Click hit-routing is left unclamped. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…g pattern Recasts the circuit-greeble look as a Wang-style tile problem: 19 code-drawn "pipe" tiles (blank, stubs, straights, decorated straights, elbows, tees, cross, pip) each carrying a 4-bit edge mask. A greedy row-major scan fills the grid, at each cell picking uniformly from bag tiles whose west/north edges match the already-placed neighbours; blank's all-closed mask keeps the candidate set non-empty so no backtracking is needed, and the outer ring is forced closed outward so wires stay in-window. Tiles are stamped from code as 2px strokes centre-to-edge, drawn once in a shadow colour at +1,+1 then in the foreground. Click reseeds the pattern (deterministic per seed); scroll changes tile size 6-16px and relayouts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…atter Replaces the 19 code-drawn pipe tiles with the real Xenon 2 greebling sheet: 205 hand-drawn 8x8 PICO-8 stamps baked to greeble-tiles.h by the new tools/gen_greeble_tiles.py, blitted 1:1 with their four palette slots mapped to the active Wuss palette so the pattern recolours with the theme. Edge-matched WFC was tried first (the generator also bakes a per-edge colour-set class table) but the sheet is a scrapbook of decorative fragments, not a connective tile set, so matching only ever collapsed the plane into noise. greeble_generate now just draws a uniform-random stamp per cell, which reads as the intended greeble texture; greeble_tile_edge[] is left in the header, unused, for anyone who wants to revisit adjacency. Drops the tile-size scroll control (stamps are fixed 8px). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
gen_greeble_tiles.py takes an optional second image -- the sheet where the artist laid the stamps out in their intended shapes. Its 4-connected non-empty clusters become prefab blocks (greeble_prefab / greeble_prefab_cells, 0xFF marking an uncovered cell); the lone 1x1 clusters become greeble_filler[], the shortlist of stamps that read well standing alone. Without the second image, filler is every tile and no prefabs are emitted -- the old flat scatter. greeble_generate now clears the grid, scatters prefab blocks without overlap, then fills every unclaimed cell from greeble_filler[]. The repeated xorshift32 step is now the GREEBLE_XORSHIFT macro. greeble_tile_edge[] and the edge-class machinery are gone from both the generator and the header.
gen_greeble_tiles.py takes an optional third image -- a 4xN sheet, one 4-colour palette per row -- and bakes it as greeble_palettes[NPALETTE][4] (packed 0xAABBGGRR). The sheet runs light-to-dark; stamp slots run dark-to-light, so each row is reversed on read. Without the sheet, one PICO-8 row is emitted. greeble_generate picks a palette row from the seed alongside the pattern; greeble_redraw expands that row into colour_t once via colour_rgba and greeble_stamp indexes it by slot. Drops the old greeble_palette[] table and the framebuf/palettes.h / scr->palette indirection.
…ette greeble_stamp walked each 8x8 row a pixel at a time -- 64 screen_set_pixel calls per stamp, each re-clipping and re-resolving the colour. It now walks same-slot runs and draws them with screen_fill_hline: 1-4 contiguous-word run fills per row. Select and Adjust are now split. Select (greeble_select) reseeds the pattern as before; Adjust (greeble_adjust) steps task->palette to the next greeble_palettes[] row without regenerating the grid. greeble_generate no longer picks a palette; greeble_create seeds task->palette to 0.
MENU over the content opens a Greeble menu: the first rows pick a symmetry mode (Scatter/Mirror/Quad) that folds the generated grid onto itself from the same seed, and a trailing toggle gives each scattered prefab its own random palette, tracked per cell in cellpal[][]. Adjust now regenerates after stepping the base palette so filler cells pick up the new row. Prefab sheet rebuilt to 62 blocks. memset clears the grid in one pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…and the menu The symmetry modes and their menu rows are removed for now. The Greeble menu is a single "Random palettes" toggle, and an Adjust click flips the same state via a shared greeble_toggle_randpal that keeps the menu row's tick in step. Adjust no longer cycles the base palette. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A MENU press outside the open menu chain was spent by wuss__menu_click_outside closing the chain, so the target window's delegate never got the MOUSE_DOWN and could not open its own menu. Only the app's bare-backdrop path worked, because it calls wuss_menu_open itself after wuss_mouse_click returns. Now a MENU press that lands on a window is not consumed by the dismissal: the chain still closes, but the press falls through to that window's task, matching the RISC OS "MENU moves the menu" behaviour and the backdrop. Adds a wuss test covering the two-window case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The greeble menu struct is file-scope, shared by every greeble window. The single row's tick showed whichever window toggled last, and an ADJUST pick of the row toggled task state but never redrew the still-open menu. Sync the shared row's flag from task state at open time, capture the chain handle, and on an ADJUST menu pick retick the live level via wuss_menu_set_item_ticked. SELECT nulls the now-stale handle.
main.c kept run_wuss, the frame loop, pixel_stress and the chrome config; the 19 spawn_* callbacks, every menu table and task_handle_event move to a new apps/wuss/tasks.c behind tasks.h. The shared context struct (formerly the anonymous file-scope `g`) becomes struct wuss_app_tasks, declared in the header and defined in tasks.c; run_wuss fills it as before. The backdrop MENU click now calls tasks_open_launcher and teardown calls tasks_teardown. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The SDL window now tracks an integer scale (device pixels per screen pixel) in struct wuss_frontend, starting at 2x. F2 steps it up, Shift-F2 down, clamped to [1, 4], and the window is resized from scr_width/height * scale so repeated halving can no longer drift the size off the grid. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6009781 moved Adjust onto the random-palettes toggle. Put base-palette cycling back on an Adjust click, regenerating from the same seed so the layout is unchanged. The menu's "Random palettes" row still owns the toggle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The generator picked a random prefab on each placement attempt. By the time a large block was tried, small blocks and filler had already fragmented the grid, so its bounding box rarely landed wholly empty and the big circuit shapes were almost never placed. Sort the prefab indices by footprint (w*h) descending once per regenerate and split the attempt budget evenly across prefabs, walking them largest first. Each prefab gets its share of random (row,col) tries while the grid is still open. The xorshift stream still drives every position, so a given seed yields the same grid. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The window opened at a hardcoded 160x320 content area, smaller than the grid the generator can fill. Size it from GREEBLE_MAX_COLS/ROWS * the tile pixel size instead, so it opens showing the whole grid (256x384) with cols and rows landing exactly on the caps. Same value is passed as the minimum content size. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New prefab/filler sheet: 94 prefab blocks (was 62), 30 standalone filler stamps (was 27). GREEBLE_MAX_COLS goes 32 -> 48 to match GREEBLE_MAX_ROWS, so the generator can fill a square grid. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bmfont_set_flags() with bmfont_FLAG_MONOSPACE forces every glyph to advance by the font's widest advance width, giving a fixed-pitch layout. Glyph bitmaps are unchanged: proportional ink sits left-aligned in the wider fixed cell. The widest advance (maxadw) is computed once at load. bmfont_measure and both bmfont_draw advance lookups route through a new bmfont_advance_for() helper that returns maxadw when the flag is set. maxadw <= gridwidth always holds, so the glyph blit's right_skip stays non-negative. bmfont_monospace_test loads a proportional font, sets the flag, and asserts an N-char string measures N times the single-char width with a constant per-char step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tasks that open a wuss_menu all hand-rolled the same two idioms: a pre-open loop setting each row's TICKED flag from task state, and a post-pick check of the ADJUST button to decide whether the stored chain handle is still live. Add two helpers to wuss/menu.h: - wuss_menu_should_keep_open(ev) -- names the "ADJUST keeps the chain open, SELECT has already freed it" predicate. - wuss_menu_open_ticked(task, menu, ticks[], at, out) -- sets each row's TICKED bit from a bool array, then opens; the one call that writes back to the caller's item array. Convert greeble, palette and chars to use them. palette's permanent DASHED flag on the Invert row moves to create time, since wuss_menu_open_ticked only touches TICKED. minesweeper, text and swatches keep no handle and carry no ticks, so they are left as-is. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The idle-tick handler invalidated the whole window every second, forcing a full 480-cell repaint plus per-digit bmfont work just to advance the HUD timer. Add an MS_TIMER_BOX covering the timer's 3-digit field and invalidate only that on a tick. minesweeper_redraw now always paints the cheap HUD strip (its own backdrop, since the window is wuss_NO_BACKGROUND, plus the counters) and runs the frame fill, cell loop and win/lose banner only when the dirty region reaches below the strip. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Draws the string twice with a transparent background: a shadow pass in a given colour at pos+offset, then the main pass at pos. Factors the relief effect that the clipping test previously hand-rolled as two bmfont_draw calls out into the library. The clipping test's transparent pass now uses it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The help/controls list was a fixed-position column on the left of the canvas. Move it below the canvas as a centred wrap-flow row, drop the associated fixed-position and responsive-reflow CSS, and refresh the about/controls copy. Add LINK_DEPENDS on shell.html so editing it relinks the Emscripten target instead of silently reusing the stale shell. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Folds the 44 commits since 9172482 into the Unreleased section: the bmfont relief/monospace additions, io/dirscan extraction, the wuss demo task-launcher split and palette-picker rework, the Minesweeper and greeble tasks, and the window-move/menu-routing fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The compact wuss_test used when WUSS_FURNITURE/WUSS_ICONS are off had not compiled since the backdrop API moved from wuss_colour_t to wuss_backdrop_t; only the furniture build exercises the other body, so the breakage went unnoticed. - wrap the four wuss_window_create bg arguments in wuss_BACKDROP_COLOUR(), matching every other call site - the wheel-scroll case expected the offset to move, but the test's chromeless flag set includes wuss_WINDOW_NO_VSCROLL, which wuss__scroll_step deliberately suppresses; assert the offset is unchanged and say why Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
toggle-action.c and bmfont.c each call a logf_* macro with a fixed message and no substitutions. -std=c99 -pedantic then warns "ISO C99 requires rest arguments to be used" because the GNU ",##__VA_ARGS__" comma-swallow is an extension. Pass "%s" plus the string so a variadic argument is present, and note the constraint in debug.h. icon/draw.c computed gap_x0 = b->x0 + INSET - PAD and tested gap_x0 > b->x0, which GCC's -Wstrict-overflow flags as an x+c > x pattern. INSET (8) always exceeds PAD (2), so the guard was constant true; drop it and keep only the right-stub check, with a comment on why that is the only edge that can vanish. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @dpt, your pull request is larger than the review limit of 150,000 diff characters
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.