Skip to content

Wuss sharp - #6

Merged
dpt merged 48 commits into
developfrom
wuss-sharp
Aug 28, 2026
Merged

dpt merged 48 commits into
developfrom
wuss-sharp

Conversation

@dpt

@dpt dpt commented Aug 28, 2026

Copy link
Copy Markdown
Owner
Screenshot 2026-08-28 at 13 47 52

dpt and others added 30 commits August 27, 2026 00:48
Lets tasks read the system font handle given to wuss_create, so they
can draw their own content in the same face as window titlebars.
Displays every glyph 0-255 from wuss's system font, one per cell, so
the whole font can be eyeballed at a glance. Skips itself if wuss was
created without a font.
bp[w3] is an unsigned char promoted to int; shifting a byte >= 0x80
left by 24 overflowed signed int range, undefined behaviour caught by
UBSan. Cast each byte to uint32_t before shifting.
Wraps the flags previously passed by hand into the ad-hoc build-asan/
directory, so any build tree can opt in with -DUSE_ASAN=YES.
Squared per-channel deltas multiplied by libjpeg's fixed-point weights
overflowed signed int at the top of the byte range (e.g. 255 apart on
green), undefined behaviour caught by UBSan. Cast each squared delta
to unsigned int before the multiply.
bmfont_draw indexes glyphs as c - ' ' with no bound check, and Chars
fed it every byte value 0-255; bytes past the font's actual glyph
count walked off the glyph table (AddressSanitizer heap-buffer-overflow).
Add bmfont_get_count() and have chars_redraw leave a cell blank
instead of drawing when its byte value falls outside the font's range.
Adjust click now cycles through sofa, ship, tetrahedron, cube,
octahedron, icosahedron and dodecahedron wireframes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace line_clip's w*dy/h maths with a muldiv() helper that widens
the multiply using 32-bit half-word arithmetic and does a bit-serial
division, so no 64-bit or floating-point intermediate is needed while
still avoiding UBSan's signed-overflow trap.
wuss_window_move()'s blit-and-slide optimisation only fired for the
topmost window, so dragging a window sent to the back always fell
back to a full-footprint redraw even when nothing above it actually
overlapped its old position. Replace the topmost check with an
explicit occlusion test (wuss__occluded_above) that walks the z-order
list above the window and only declines the fast path when some
window's visible box genuinely overlaps "before".
Regression test for the fix in wuss_window_move(): a window sent to
the back with nothing above it overlapping its old footprint must
still take the blit-and-slide fast path, not fall back to a full
redraw just because it isn't topmost.
wuss_window_resize() used to invalidate the union of the old and new
footprint on every resize, forcing a full repaint even though a
resize's top-left corner is fixed and everything within both
footprints is unchanged. Add invalidate_grown_or_shrunk() to
decompose the delta into the shrunk-away and grown-into slivers, each
still clipped against occluders via wuss__invalidate_clipped (unlike
wuss__invalidate_minus, which has no occlusion awareness and would
have broken the fully-occluded-resize test).

Add a regression test asserting the untouched interior corner is
never marked dirty and the dirtied area stays below a full repaint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The grown/shrunk sliver invalidation added in 7a319b9 treats the old
∩ new footprint as fully unchanged, but window furniture (outline,
scrollbars) is drawn along the trailing edge of the old footprint.
When a window grows, that edge lands inside the new interior and was
never marked dirty, leaving stale outline/scrollbar pixels baked into
the enlarged window. Mirror the fix already used by toggle-size
(toggle-action.c): force the old footprint's furniture dirty when
growing, then invalidate furniture at the new position too.

Also move the resize regression test's interior probe point below the
titlebar, since the titlebar itself is legitimately redrawn now.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The grown/shrunk-sliver optimization assumes a task's content is just
anchored positions plus furniture, so the old/new overlap needs no
repaint. That's false for a task like the palette swatch grid, which
lays itself out across the whole window -- exactly the case
wuss_WINDOW_NO_TOGGLE_BLIT already exists to flag for toggle-size, but
resize.c never checked it, so such tasks got stale interior content on
a plain resize. Fall back to full old-union-new invalidation when the
flag is set.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss__occluded_above() only checked the window's old footprint before
allowing the move blit fast path. screen_copy_rect() is a raw pixel
copy with no awareness of window ownership or z-order, so when a
window's NEW footprint landed under an occluder (its old footprint
having been clear), the blit pasted stale pixels straight over that
occluder with no invalidation to fix it up afterwards -- corrupting
it until something unrelated redrew it. Check both the old and new
footprint against occluders above the window in z-order before
allowing the blit.

Add a regression test dragging a clear window onto a topmost occluder
and asserting the exposed sliver of the destination gets queued dirty
by the fallback clipped redraw.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dd0dca6 fixed the destination-occlusion corruption bug by declining
the blit fast path entirely whenever either the old or new footprint
overlapped an occluder above the window in z-order. That's correct
but overly broad: it falls back to a full clipped redraw of the whole
moved footprint even when only a small part of the new position is
covered, discarding the blit's exposed-sliver savings for no reason --
the exposed part is still purely this window's own content.

Only the old footprint still needs to be occlusion-free (its pixels
must genuinely be this window's own rendering to be worth sliding).
The new footprint can still land under an occluder: after the blit,
call wuss__invalidate_uncovered() to force exactly the overlapped part
back to its rightful owner (the occluder now painted over), leaving
the window's own exposed content untouched since the blit already
placed it correctly.

Rework the regression test from dd0dca6 to assert both halves: the
occluded overlap is forced dirty (fixing the corruption), and the
exposed sliver is NOT (proving the blit is still used, not a full
fallback redraw).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss_window_move() used to require a window's whole old footprint be
occlusion-free before blitting; any overlap at all fell back to a full
clipped redraw of the moved footprint. Split "before" into its clean
(unoccluded) and hidden pieces via wuss__clip_to_visible(), blit each
clean piece individually to its translated destination, and only
genuinely repaint the translated hidden pieces plus any destination
occlusion.

Exposes subtract_boxes as wuss__subtract_boxes for reuse here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… against inter-piece clobber

wuss_window_move() forced a window's whole current occluded footprint
dirty after any blit, even parts a blit never touched (never-blitted
occluded source pieces need no such repair) -- unnecessarily redrawing
an untouched occluder above it.

Sequential per-piece screen_copy_rect() calls could also clobber each
other: one piece's destination landing on another still-unread piece's
source (e.g. dragging past a thin mid-window occluding band), producing
corrupted "bands" in the moved window's own content. Detect this before
blitting and fall back to a full clipped redraw rather than risk it.

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

Confirms wuss_window_move never marks an occluder dirty when the piecewise
blit is declined (wuss__pieces_would_clobber): with no blit, nothing ever
touches the occluder's pixels, so it must stay untouched even as the
overlap between the dragged window and the occluder grows during the move.

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

wuss__pieces_would_clobber declared any pairwise dest/clean overlap
unsafe, forcing a full-footprint redraw even when a different blit
order would avoid the conflict entirely -- routinely true for the
common case of a two-piece L-shaped corner occlusion. Adjust-dragging
a window behind a corner occluder was regressing into a full redraw.

Replace it with wuss__order_pieces, which builds the "must happen
before" dependency graph and topologically sorts it, only falling
back when a genuine cycle leaves no safe order.

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

Blitting the full destination then force-invalidating the occluded
overlap to "repair" it forced a spurious repaint of the occluder
whenever a window was dragged underneath it. The occluder hasn't
moved, so its pixels there are already correct — clip each clean
piece's destination against current occlusion before blitting, so the
occluded remainder is never touched and needs no repair.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers wuss_get_font, wuss_WINDOW_NO_TOGGLE_BLIT, the wuss_get_dirty_count/
wuss_get_dirty(index) pair, and drops the now-false "no finer than a
window's bounding box" damage-tracking limitation.
Matches ms-sans-serif.png, which never had the suffix.
The flag guards wuss_window_resize itself, not just the toggle-size
icon action, so drag-resizing a flagged window also gets a full
redraw instead of blit-preserve. The old name only described one of
its two triggers.
Its paragraph reflows across the whole window width, so the default
resize optimisation (preserve the unchanged interior, repaint only
the grown/shrunk sliver) left stale wrapped lines behind whenever the
task's own idle animation resized the window. Same fix already
applied to the palette swatch task.
The text's clip rect was the whole titlebar, not the text_x0..text_x1
slot computed to dodge the close/back/toggle icons, so a title too
long to fit was drawn past that boundary. It usually stayed hidden
because the icon(s) painted after it happened to cover the overrun,
but that only holds when the redraw region includes those icons too:
a redraw covering just the title (e.g. after a title-only invalidate)
left the overrun visible. Now the draw itself is clipped to the slot.
wuss_task_t::bg was mutated at runtime via wuss_window_set_background,
despite the struct being documented as an immutable content delegate
copied in at creation. Move bg to a dedicated wuss_window_create
parameter (and a wuss_window_t field) so window-owned mutable state
lives on the window, not the delegate.

BREAKING CHANGE: wuss_task_t drops its bg field (now {handle, task_data});
wuss_task_start drops its bg parameter; wuss_window_create gains a
wuss_colour_t bg parameter after flags.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Doxygen comments and enum/struct member docs wrapped with nice
greedy-fill wrapping, matching the description-column-aligned param
style used elsewhere (cache.h, screen.h).
Reflows /** ... */ blocks (\param/\return/\brief/prose paragraphs)
greedy-fill, continuation lines aligned under the description column,
matching the style used by hand in wuss.h/window.h. Leaves /**<
trailing member comments and overlong code lines alone (those need
structural judgement) and reports them for manual follow-up.
dpt and others added 18 commits August 28, 2026 00:48
Run tools/wrap_doxygen.py over every DPTLib-owned .h file (fortify/
excluded, vendored third-party code). /**< trailing member comments
left untouched by design; flagged on stderr for manual follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss_config_t gains a backdrop colour (wuss_NO_BACKGROUND to opt out,
matching window bg), painted by wuss_redraw/wuss_redraw_dirty before
windows -- previously every caller had to repaint the background
itself around each dirty rect. BREAKING: existing wuss_config_t
initialisers that build the struct field-by-field must now also set
backdrop.

wuss-test.c's per-frame background flash is replaced by config.backdrop,
and Shift-F1 now forces a full wuss_redraw (backdrop included) to
exercise it interactively.
…coded one

wuss__icon_size_for used WUSS_DEFAULT_TITLEBAR_HEIGHT as its fallback for a
NO_TITLEBAR window that still wants scrollbars/resize, so such a window's
furniture didn't match its titled siblings whenever the actual (derived
or configured) titlebar height differed from the hardcoded default.
… word

Replaces struct wuss_window's standalone toggled int with a
wuss_window_state_t bitfield (wuss__window_toggled/wuss__window_set_toggled
helpers), separate from the public wuss_window_flags_t appearance flags,
so future internal per-window state can add bits instead of int fields.
…:scroll

Groups arrows/wells/sausages into a scroll sub-struct so the flat colour
list reads as one furniture class instead of nine unrelated fields.
BREAKING: existing wuss_config_t initialisers must update
palette.arrows/wells/sausages to palette.scroll.arrows/wells/sausages.
Adds a turns counter alongside angle, incremented each time the
spin wraps; hitting SOFA_ROTATIONS_PER_MODEL advances to the next
shape, same as an Adjust click (which also resets the counter).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records the wuss__ prefix and internal-bitflags-with-accessors
pattern established this session, so future work follows it
instead of reinventing per-window state as loose ints.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds WUSS_SCROLL_INSET so the drag handle sits with a small cosmetic
margin on its cross-axis instead of filling the well's full breadth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
launcher_redraw read scroll.y but never scroll.x, so rows stayed put
when scrolled horizontally (reachable once the window is resized
narrower than LAUNCHER_WIDTH, which enables the hscroll bar).
wuss_window_set_scroll's topmost live-blit-and-shift optimisation
assumes redraw output is a deterministic function of the scroll
offset; violating that shifted real pixels by the scroll delta while
the exposed edge strip was redrawn at the stale unscrolled position,
corrupting the window's content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The three line rasterizers seeded their Bresenham/Wu error terms from the
endpoints line_clip() had recomputed on the clip boundary. As wuss splits a
dirty rectangle into up to 32 pieces and redraws the same logical content
under a different clip box for each, the same line took a different pixel
path per piece, leaving visible seams at the piece boundaries.

Use line_clip() against the clip box for rejection only, discarding its
recomputed endpoints, and step from the true endpoints instead, leaving the
per-pixel clip in screen_draw_pixel()/screen_blend_pixel() to do the real
clipping. Bound the work with the screen bounds, which -- unlike the clip
box -- never vary between calls: screen_draw_line() and
screen_draw_line_wu_fix8() clip their endpoints against them, and
screen_draw_line_wu_float() clamps its mid-point loop to them, fast-
forwarding the gradient in closed form.

Adds a screen test asserting a line drawn in one go and drawn once per
piece of a partition of the canvas produce identical pixels, plus a check
that nothing is drawn outside the clip box.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wuss_mouse_click, wuss_mouse_move and wuss_scroll all add the window's
scroll offset before dispatching, so the point a task receives is already in
virtual content space. The curve, ball and launcher tasks each added the
offset a second time via wuss_window_get_scroll, putting hit-tests, drags,
ball spawns and launcher row indices at twice the scroll offset once a
window was scrolled.

wuss_scroll also computed its event point after stepping the scrollbar, so
the point described the content the wheel had just brought under the pointer
rather than the content the pointer was over when it turned. Compute the
point first.

The wuss_EVENT_MOUSE documentation described the point as window-local
content coordinates, which is what invited the extra addition; say that the
scroll offset is already applied.

Adds a wuss test covering all three dispatch paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wuss_idle operates on the window manager, not a single window, so it
belongs alongside the other wuss_t entry points.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Move the event and task types, wuss_task_start and wuss_task_stop out of
window.h into a new public wuss/task.h, and relocate their sources to
libraries/wuss/task/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add a size2d_t (width, height) type alongside point_t, plus box_size to
extract one from a box, and use them for wuss_window_create's document
extent, wuss_window_resize's new size and struct wuss_window's stored
document extent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace bitmap_t/screen_t's width and height members with a single
size2d_t, and take a size2d_t in bitmap_init, screen_init and
screen_draw_rect. Most screen_draw_rect call sites now pass box_size()
directly instead of subtracting the box's edges by hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The content area's right and bottom edges abutted the scrollbars (or the
resize corner) with no dividing line, leaving the frame's interior looking
unfinished.

Reserve a further pixel on each carved axis in wuss__furniture_carve_for --
so window creation, resize and toggle-size all account for it -- and draw
the rule there, in the same colour as the outline. The requested content
box is unaffected: like the rest of the furniture, the rule is added
outside it, not carved out of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wuss__furniture_invalidate_for decided the scrollbar strip widths from
the NO_VSCROLL/NO_HSCROLL flags, but wuss__furniture_carve_for still
reserves a strip on both axes for a window with both scrollbars off and
resize on, to hold the resize icon and the interior rules. Growing such
a window left the old icon and rules painted inside the new content
area. Drive the strips off the same carve the layout uses.

Also:

- Floor the scroll sausage's cross-axis inset at 0 when the well is no
  wider than two insets, which a small titlebar font can produce; the
  box used to invert and the sausage vanish.
- Zero-initialise test_task_t in the scroll-offset test, which only set
  mouse_count while test_handle increments the other counters.
- Correct the screen_copy_rect failure comment in window/move.c and
  document muldiv's domain in geom/line.
- Clamp the chars task's glyph count so no byte above CHAR_MAX reaches
  bmfont_draw, which indexes its glyph table off a plain char.

The resize-sliver test summed per-region dirty areas, double-counting
overlaps; it passed by 257 pixels and the extra strips tipped it over.
Measure the true union instead.

Co-Authored-By: Claude Opus 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 @dpt, your pull request is larger than the review limit of 150,000 diff characters

@dpt
dpt changed the base branch from master to develop August 28, 2026 12:44
@dpt
dpt merged commit 62a9193 into develop Aug 28, 2026
11 checks passed
@dpt
dpt deleted the wuss-sharp branch August 28, 2026 12:50
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