Skip to content

Wuss picklepuss - #12

Merged
dpt merged 98 commits into
developfrom
wuss-picklepuss
Sep 9, 2026
Merged

dpt merged 98 commits into
developfrom
wuss-picklepuss

Conversation

@dpt

@dpt dpt commented Sep 9, 2026

Copy link
Copy Markdown
Owner
Screenshot 2026-09-08 at 13 04 08

dpt and others added 30 commits September 6, 2026 17:35
A single global wuss->hover_icon meant moving the pointer from a parent
menu row into its submenu window switched the highlight to the submenu
row and dropped it from the parent, unlike the RISC OS feel where every
ancestor row on the open-submenu path stays lit.

wuss__icon_set_hover now consults a new wuss__menu_row_pinned() before
dropping the previous hover icon's highlight: a row that is some open
chain level's open_index with a live child keeps its highlight. When a
re-entering move closes a submenu, menu.c clears the ex-parent row's
highlight explicitly unless the pointer landed on that row.

Covers arbitrary nesting (walks the whole chain) and borrowed-window
submenus (keyed off open_index/child). WUSS_ICONS can build without
WUSS_MENUS, so the call site is #ifdef WUSS_MENUS guarded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop the Test submenu (Menu / Menu-desc exercisers) and its static menu
tree, the hidden Details window and the print-only fallback from the demo
task launcher. The launcher menu is now just Launch / Quit Wuss.

The descriptor-built menu (wuss_menu_create_from_desc) moves to the image
task, opened on a MENU-button click, rebuilt on each open and freed at
QUIT. image_task_t gains wuss / delegate / menu fields for it.

tasks_teardown is gone with the last thing it freed; its call in main.c
and declaration in tasks.h go too, along with the now-unused stdio.h,
geom/box.h and wuss/menu-desc.h includes in tasks.c.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Move the result_t rc/err/crc local to the top of its declaration block, ahead of the other locals, keeping column alignment. Affects 45 functions across wuss core, menu/icon/component/helpers, the wuss test tasks, bmfont and the cache test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The 2/3-height size was a temporary scroll-testing tweak; show the whole image.
wuss_proginfo is a shared WUSS_COMPONENTS helper modelled on the RISC OS
Toolbox ProgInfo / "Info" dialogue: a small fixed window of Name / Purpose
/ Author / Version rows a task fills in once from a wuss_proginfo_desc_t,
then hangs off its Menu-button pop-up as a wuss_menu_item_t.window.

The dialogue window is created hidden, wuss_WINDOW_NO_CLOSE, on a task the
caller passes in; it lays out from the task's font metrics with one
right-justified label and one value per non-NULL field. wuss_proginfo_destroy
closes the window and frees the handle. The owning task must not be an
autoclose task (the hidden window would keep its window list non-empty) and
must outlive the handle; as with any borrowed menu window, close any menu
chain referencing it first.

The image test task grows an "Info" row wired to a wuss_proginfo, and drops
autoclose accordingly (QUIT now arrives via wuss_destroy).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The proginfo dialogue shares the image task, so image_handle receives REDRAW
for it too and was blitting the bitmap behind its labels. Bail from the REDRAW
case unless the event is for the task's own image window.
A window with this flag gets no wuss_EVENT_REDRAW: wuss fills its backdrop
and draws its icons, and for a window whose appearance is entirely backdrop
and/or icons that is the lot. Saves such a task a no-op redraw handler.

wuss_proginfo sets it -- the dialogue is labels on white -- which also removes
the need for the image test task to filter REDRAW by window handle now that
the dialogue shares its task.
wuss_icon_spec_t gains `border` (wuss_icon_border_t: NONE / RIDGE / GROOVE),
honoured by wuss_ICON_TYPE_LABEL and ignored by every other type. RIDGE is a
1px raised bevel, GROOVE a 1px sunken one -- a RISC OS-style read-only display
field -- both in the window manager's bevel shades. Zero stays the current
borderless look, so zero-initialised specs are unaffected. An out-of-range
border on a label spec is result_WUSS_BAD_ICON.
Each value label now carries wuss_ICON_BORDER_GROOVE and is centred in its
box, with a few pixels of padding added each side, matching the RISC OS
ProgInfo look.
wuss_destroy closed doomed->menu_chain first, freeing the chain node, then
delivered wuss_EVENT_QUIT. A QUIT handler that closes its own menu chain
(via a wuss_menu_handle_t it still holds) then dereferenced the freed node
before wuss_menu_close's stale-handle guard could fire -- a heap
use-after-free at shutdown.

Deliver QUIT to every registered task first, so a handler tears down its
own windows and menu chain against live objects; the manager's own
leftover-chain close and the z_order sweep then just find less to do.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two more nearest-palette named colours for the conventional body fills:
WINDOW is a light grey, MENU is white. Resolved the same way as the other
wuss_COLOUR_* names, via the symbolic_rgb table in rebuild-palettecache.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss_COLOUR_WINDOW / wuss_COLOUR_MENU move from the named-colour block
(fixed RGB, nearest-palette) into the chrome-roles block, echoing two new
wuss_config_t::body fields: body.window and body.menu. When config is NULL
they default to wuss_COLOUR_GREY (light grey) and wuss_COLOUR_WHITE.
Resolved and range-checked at wuss_create like backdrop, and always
honoured regardless of WUSS_FURNITURE / WUSS_ICONS / WUSS_MENUS.

BREAKING CHANGE: the numeric values of wuss_COLOUR_WINDOW and
wuss_COLOUR_MENU changed (SYMBOLIC+9/+10 to SYMBOLIC+23/+24), and they no
longer resolve to a fixed light-grey / white -- a caller wanting those
must set wuss_config_t::body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Menu windows now use wuss_COLOUR_MENU for their backdrop; the proginfo
dialogue and the flat-fill demo windows (text, swatches) use
wuss_COLOUR_WINDOW. Demos that draw their own backgrounds
(wuss_NO_BACKGROUND) or pick a colour on purpose are left alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fill_chrome_config left the new wuss_config_t::body sub-struct
uninitialised on its stack config, so wuss_COLOUR_MENU / wuss_COLOUR_WINDOW
resolved from garbage -- menu backdrops came out black. Extend g_chrome to
carry body.window and body.menu per palette (PICO-8: light grey / white;
WIMP16: 75% grey / white) and assign them. Cover both roles in the
symbolic-colour test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss__icon_draw_label measured the label with target_width set to the box
interior less 2px. A label whose text filled its box (the widest of a
right-justified set) made bmfont_measure break early and return a short
width, so pos.x = b->x1 - 1 - width placed it too far left; bmfont_draw
then drew the full string and its right edge overhung the narrower
labels. The proginfo dialogue's name column showed this as a ragged
right edge.

Measure the whole string (INT_MAX, no split) so every label's right edge
lands at b->x1 - 1. Drop the now-unused interior_w/split_point locals.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss__menu_click_outside and wuss_menu_open's replace-branch freed the whole
menu chain without notifying the task that holds the wuss_menu_handle_t from
wuss_menu_open. The task's stored handle then dangled, and its next
wuss_menu_close (e.g. image.c on QUIT) was a heap-use-after-free.

Add a wuss_EVENT_MENU_CLOSED task event, delivered to the chain's owner
before the nodes are freed whenever wuss tears the chain down itself -- not
for a pick (wuss_EVENT_MENU_SELECT covers that) nor for a wuss_menu_close the
task made. Tasks drop their handle on it. A new wuss__menu_abandon helper is
the single chokepoint for both internal close paths; it unlinks
wuss->menu_chain before delivering so a re-entrant wuss_menu_close is a no-op.

image.c also dropped its handle on a SELECT pick (same latent UAF). The
wuss menu-move test now stores handles and asserts MENU_CLOSED fires on both
the replace and the click-outside path, then calls wuss_menu_close on the
now-NULL handle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two teardown paths left a menu chain's owner holding a freed
wuss_menu_handle_t, so a QUIT handler that called wuss_menu_close on it
faulted:

- wuss_task_destroy closed the owner's chain with a bare wuss_menu_close,
  then delivered QUIT. A close_chain_on_quit-style handler then
  double-freed the chain. It now calls wuss__menu_abandon, which delivers
  wuss_EVENT_MENU_CLOSED first so the handle is dropped before QUIT.

- wuss_destroy freed the internal wuss__menu_task in the same task sweep
  that fires client QUITs. A client QUIT handler closing a chain it still
  held then walked the freed menu task's window list (ASan
  heap-use-after-free in list_remove via wuss_window_close). The sweep now
  skips wuss__menu_task and tears it down last, after every client QUIT
  and the leftover-chain close. Retires the matching ponytail TODO.

wuss__menu_abandon is de-static'd and declared in core impl.h. New
two-phase wuss-test regression covers both paths; each phase reproduces
its UAF when its fix is reverted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
proginfo: tighten margins, add PROGINFO_ROW_LEADING so the label/value
fields no longer abut -- rowh is the pitch, the field box is rowh minus
the leading.

icon draw: replace the four screen_draw_line calls for a LABEL icon's
GROOVE/RIDGE border with two screen_draw_lines polylines (top+left,
bottom+right).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
icon draw: widen the LABEL GROOVE/RIDGE border to two pixels by drawing
the L-shaped hi/lo polyline pair twice, the second inset by one.

proginfo: subtract one PROGINFO_ROW_LEADING from the window height so the
bottom margin matches the top -- rowh is the pitch and the final row
needs no trailing leading.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New wuss_info component (component/info.h, component/info.c): builds a
fixed label:value grid window from a wuss_info_row_t array -- measures the
two columns, sizes the window from the task font, lays out two LABEL icons
per row. This is the layout half that proginfo carried inline.

proginfo is now a thin cap: wuss_proginfo_create maps the non-NULL desc
fields to rows and calls wuss_info_create; wuss_proginfo_t is wuss_info_t
and wuss_proginfo_destroy / _window are #define aliases. Public API and
behaviour unchanged; the image test task still drives it as before.

First consumer of a planned app-support component set; a second
label:value dialogue will reuse wuss_info directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- info_widen helper folds the per-row strlen-guard + bmfont_measure pair
  into one call site each for label and value.
- spec array moves to the stack (INFO_MAX_ROWS = 16 rows); drops the
  malloc, its OOM branch, the memset-with-cast and the matching free.
  nrows outside 1..INFO_MAX_ROWS is now result_BAD_ARG.
- index specs[i*2] / [i*2+1] directly through named label/value pointers;
  nspecs, the alloc-copy alias and the llen/vlen locals go.

Behaviour unchanged: wuss_icon_create deep-copies each spec and its text,
and the font-absent path still yields rowh from MAX(0 + PAD, 12).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New "Borders" group in the icons task: a framed pair of LABEL icons, one wuss_ICON_BORDER_GROOVE (sunken display field) above one wuss_ICON_BORDER_RIDGE (raised). ICONS_N_BORDERS keeps the spec-count enum in step.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The icons task hard-coded 0xBB/0xDD greys for its icon backgrounds and the crosshatch backdrop. Swap them for the wuss_COLOUR_WINDOW / wuss_COLOUR_GREY chrome roles so the demo tracks the configured colour scheme; keep the crosshatch texture by hatching GREY over WINDOW. task->paper stays a literal -- no public call resolves a symbolic wuss_colour_t to a concrete colour_t for the ruler glyph blend.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
lay.black is exactly black, so the wuss_COLOUR_BLACK symbol serves. lay.red stays on wuss_nearest_colour -- 0xCC3333 is an off-primary demo tone with no matching wuss_COLOUR_* symbol.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New label border style: a 6px-per-edge "action" surround -- a 2px raised outset, a 2px moat in wuss_COLOUR_ACCENT_BG (the default-action-button fill), then a 2px raised inset -- for a RISC OS default-action look. wuss__icon_from_spec accepts it; the icons demo grows an "Action" example alongside Groove and Ridge.

Factor the shared edge draw into screen_draw_bevel_edge(scr, box, a, b): one 2px bevel ring inside an inclusive-exclusive box, colour a top/left, b bottom/right, the bottom edge owning both corners so the ring closes cleanly. GROOVE/RIDGE draw one ring; ACTION nests three on boxes inset 0/2/4.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The two g_chrome rows were wrapped four-per-line, so matching a colour to its config field (fill_chrome_config assigns c[0]..c[16] in order) meant counting across. One constant per line, a blank between the palettes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dpt and others added 25 commits September 9, 2026 18:30
image_click now calls wuss_window_resize before wuss_window_set_doc so the visible frame tracks the new bitmap; set_doc alone only changed the scroll extent.

Add an 8px solid PICO-8 pink band inset inside the ninepatch frame (IMAGE_BORDERSZ / IMAGE_MARGINSZ).
Add span_p1 (fill + blendconst, MSB-first bit packing) and register it, so bitmap_init hands out a valid span for pixelfmt_p1.

Add a p1 case to screen_copy_bitmap: blit a 32bpp RGBA source onto a 1bpp screen, quantising to the 2-entry screen palette via the existing deep->paletted pixelmap fast path (pixelmap already handled p1). Alpha-tested transfer, both indices opaque.

Cover both with tests: rgba8888->p1 in pixelmap-test, screen_copy_bitmap to a p1 screen (packing, spill, odd-x) in screen-test.
Add p1 plot paths to the screen primitives so the wuss demo can run on a
monochrome framebuffer:

- screen_set_pixel / screen_blend_pixel / screen_fill_pattern gain a p1
  case, packed MSB-first to match PNG and the existing pattern bit order.
- New pixelfmt_paletted_nentries() replaces the
  (scr->format == pixelfmt_p4) ? 16 : 0 hack at every colour_to_pixel call
  site, which passed nentries=0 (broken) for any non-p4 paletted screen.
- bitmap_convert grows a p1 -> bgrx8888 path, used by the SDL frontend's
  per-frame display conversion.
- apps/wuss: --depth 1 selects pixelfmt_p1 (stride (width + 7) / 8).
- screen_copy_rect returns result_NOT_SUPPORTED for p1; callers fall back
  to redraw.

Fix a latent bug in spanregistry_get(): its one-slot cache started with
lastformat zero-initialised, which equals pixelfmt_p1 (the first enum
member), so the first p1 lookup short-circuited to a NULL span and
tripped the assert in screen_fill_hline. Seed lastformat with
pixelfmt_unknown and guard the fast path on a non-NULL span.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add p1 drawchar variants and a pixelfmt_p1 case to bmfont_draw's dispatch.
A single bmfont_p1_plot_row helper does the per-pixel MSB-first bit write
for both 1- and 2-byte glyph rows and both opaque and transparent
backgrounds; the LUT-driven width unrolling the p4 path uses buys nothing
for a plain bit write. The rest of bmfont_draw (log2bpp, shift, screen
pointer math, native fg/bg via 1 << (1 << log2bpp)) already handles
log2bpp == 0.

Add pixelfmt_p1 to bmfont_test's format table.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Andrew's monotone chain over the active control points, drawn as a closed
light-grey polyline before the curve and blobs. Also fixes the curve-type
label origin: it used the per-redraw dirty piece (event->data.redraw.content)
instead of the full content box (bounds), so a partial redraw drew the
label at the wrong place.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mirror the recent p1 work for pixelfmt_p2 (four palette entries, packed
MSB-first: bits 7..6 of a byte are the leftmost pixel).

- span/p2.c + span-p2.h: span_p2_fill and span_p2_blendconst, registered
  in the span registry.
- screen-draw.c: screen_set_pixel_p2, screen_blend_pixel_p2 and
  screen_copy_bitmap_p2, wired into the three pixelfmt_log2bpp switches.
- screen-fill-pattern.c: screen_fill_pattern_p2.
- bitmap.c: bmconv_p2_to_bgrx8888 for bitmap_convert.
- pixelmap and pixelfmt_paletted_nentries already handle p2 generically.
- Tests: p2 cases in pixelmap-test.c and screen-test.c.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The SDL demo now accepts -d/--depth 2, selecting a pixelfmt_p2 framebuffer
(stride (width + 3) / 4) alongside the existing 1, 4 and 32.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add bmfont_p2_plot_row and the four bmfont_drawchar_p2_{1w,2w}_{o,t}
wrappers, dispatched from bmfont_draw's pixelfmt_p2 case. Two bits per
pixel, MSB-first, matching the screen p2 plot helpers. bmfont-test.c
exercises an 800x600 p2 screen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Match the _p1/_p2/_p4 naming of the other paletted set-pixel helpers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SELECT and ADJUST clicks step the ordered-dither matrix forward and
backward through 2x2, 4x4 and 8x8. Each matrix cell is mapped to a fixed
-8..+8 offset regardless of matrix size, so a larger matrix gives a finer
pattern rather than a noisier one (the 4x4 output is unchanged from
before). The current size is drawn as a label in the content area's
top-left corner.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds three small paletted bitmap faces under resources/bmfonts -- 04b_03,
04b_25 and Nokia -- plus tools/ttf2bmfont.py, which renders a TTF into the
bmfont PNG format the loader expects.

Registers the three new leafnames in bmfont_enumerate_test
(bmfonts_enum_extra[], MAXFONTS_ENUM bumped to 12) so the fixture-directory
walk still matches.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The NO_RESIZE_BLIT branch of wuss_window_resize invalidated the dirty
region but never dropped the cached furniture layout, so wuss__furniture_draw
kept painting titlebar/carve/outline rects at the previous frame's width.
The blit branch gets this via wuss__chrome_repaint; add the equivalent
wuss__chrome_invalidate_layout call here. Surfaced by the sample-text task
(libraries/wuss/test/tasks/text.c), which resizes such a window every idle
frame and drew its furniture inconsistently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
box_intersection returns non-zero when the intersection is empty, but the
guard negated it, so any non-NULL area that overlapped the bitmap returned
early and painted nothing. Only area==NULL worked. Drop the negation to
match screen_fill_pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The decoder ran a bare for(;;) that only stopped on an EOL opcode, so a
truncated or corrupt RLE blob (short data_len, missing EOL, bit-rot) read
opcodes past the allocation. Every other row walker already threads an
`end` limit; the decoder did not. Add the `end` parameter, stop at it, and
have a reserved opcode bail to `end` (release builds previously did a bare
`break`, desynchronising the caller's row cursor) so a bad blob decodes to
nothing rather than garbage.

bitmap_decompress now reads data_len from the blob header to form `end`;
the two screen RLE blitters and the row round-trip test already had it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bmfont_draw formed gid = c - ' ' from a raw signed char and used it to
index adw[] and glyphs + gid*glyphbytes with no bounds check, so a tab, a
DEL or any byte >= 0x80 (UTF-8/Latin-1) produced a negative or oversized
index and an out-of-bounds read/write. bmfont_measure already filters
c < ' ' and gid >= totalchars; apply the same guard in both of
bmfont_draw's loops.

Also give the fully-vertically-clipped case (clippedcharheight <= 0) a
release-build bail: it was caught only by assert(), so an NDEBUG build
would enter drawfn with a negative height and loop ~INT_MAX times.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…task

wuss_destroy's task sweep delivered wuss_EVENT_QUIT and then wuss__free'd
each task node without first setting wuss_TASK__REAPING. A QUIT handler that
closed its last autoclose window hit wuss_window_close's self-destruct path
(AUTOCLOSE set, REAPING clear, windows empty), which delivered a second
QUIT and freed the node -- which the sweep then freed again. A handler
calling wuss_task_destroy(self) hit the same double free via that
function's own unlink-and-free.

Set wuss_TASK__REAPING before QUIT in the sweep, matching wuss_task_destroy,
and make wuss_task_destroy a no-op when the task is already being reaped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The worst-case row estimate assumed one control byte per 128 pixels, but
the encoder emits a 1-byte literal control for every isolated
non-repeatable pixel. A legal y8 image of the form singleton, pair,
singleton, pair, ... needs ~1.33*w bytes/row (literal-of-1 + repeat-of-2
per three pixels), overflowing the allocation and returning
result_BUFFER_OVERFLOW on valid input. bpp >= 4 formats were hidden by the
dominant w*bpp literal-data term.

Budget w*(bpp+1) plus the literal-long split control and an EOL. Adds a y8
round-trip test on the pathological pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The close furniture now routes through wuss_window_try_close, which fires
PRE_CLOSE (vetoable) then CLOSE then frees the window. A task carried over
from the pre-PRE_CLOSE contract may still treat CLOSE as "decide whether
to keep the window" and hold its wuss_window_t past the handler -- now a
use-after-free. Document the contract at the enum: drop the handle in
CLOSE, put keep-open logic in PRE_CLOSE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The loop ran x < src->size.w / 8 and expanded 8 pixels per iteration, so
the final w % 8 pixels of each row were never written -- the output had
uninitialised garbage columns for any width not a multiple of 8. It also
walked src->base continuously, ignoring row padding in src->rowbytes.

Switch to a per-pixel loop honouring both width and rowbytes, matching the
p1 and p2 converters.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss_window_set_hidden marked a window hidden but left
wuss->furniture.dragging, wuss->pressed_icon and wuss->hover_icon pointing
into it. Hiding a window mid-drag (from an event handler) meant the next
wuss_mouse_move still took the dragging branch and drove
wuss_window_move on an off-screen window every pointer report. Clear the
same pointer state wuss_window_close does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
scroll_strip always reserved size + WUSS_DIVIDER_PX at the far end for the
resize corner, but scroll_strip_hit only reserves it when something owns
that corner (a resize icon, or -- for the horizontal strip -- a vertical
scrollbar). With a horizontal scrollbar and NO_VSCROLL and NO_RESIZE the
drawn strip stopped short while the hit box ran to the visible edge: the
rightmost size + 1 px band showed stale pixels yet hit-tested as
HSCROLL_WELL and started a scrollbar drag over empty chrome. Give
scroll_strip the same far-end rule.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The fallback claimed wuss_FURNITURE_RESIZE for a whole bottom or right
carve band whenever a resize icon was present, and a scroll well for an
edge with no scrollbar strip. A click on the bare carve band left of the
corner icon (NO_TITLEBAR, NO_VSCROLL, NO_HSCROLL, !NO_RESIZE) then started
a resize far from the visible handle. Only hand off to a scroll well when
that scrollbar's strip runs the full edge; the resize corner already has
its own hit box, so every other edge pixel is frame -> TITLE or CONTENT.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The comment promises a release build no-ops rather than dereferencing NULL,
but the guard tested only scr->span and then called scr->span->fill(...).
A span registered without a fill entry would pass the check and call
through a NULL pointer. Test scr->span->fill too, matching the assert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, we are unable to review this pull request

The GitHub API does not allow us to fetch diffs exceeding 300 files, and this pull request has 519

@dpt
dpt changed the base branch from master to develop September 9, 2026 23:10
Covers every commit since the last CHANGELOG touch (0ba5456): the 1bpp/2bpp
paletted format work across bitmap/span/screen/bmfont and the wuss demo, the
curve-task convex hull, the ttf2bmfont generator, the gradient-task dither
cycling, the screen_set_pixel_8 -> _p8 rename, and the run of correctness
fixes from the wuss-picklepuss review (RLE bounds, bmfont glyph range check,
task double-free, fill-pattern clip test, p4 conversion, hidden-window pointer
state, scroll strip / hit-region mismatch).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dpt
dpt merged commit 2789c9f into develop Sep 9, 2026
11 checks passed
@dpt
dpt deleted the wuss-picklepuss branch September 9, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant