• src/doors/termgfx/test/test_termgfx_termio_fitcanvas.csrc/doors/termgf

    From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Fri Aug 7 01:29:30 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/d10053d16770eb07faf62b30
    Added Files:
    src/doors/termgfx/test/test_termgfx_termio_fitcanvas.c
    Modified Files:
    src/doors/termgfx/termgfx_termio.c src/doors/termgfx/test/unit_termgfx_termio.sh
    Log Message:
    termgfx: fit against the canvas the terminal declared, not a 640x400 guess

    termgfx_termio.c starts at g_canvas_w/h = 640x400 and corrects it from the ESC[4;h;wt reply to ESC[14t. SyncTERM never sends that reply -- its CSI t
    is the CTerm-private palette setter -- so on SyncTERM the guess stood, and every fit, centering and cell-size derivation was computed against it.

    The guess is only right in 80x25 and 80x50. SyncTERM's 80x43 canvas is
    640x350 (43 rows of 8px, plus 6 scanlines no character cell covers), and a captured session running Maniac Mansion there traced

    canvas=640x400? emit=640x400@0,0 cell=10

    -- a 400-line frame emitted into a 350-line canvas, and a 10px cell derived against a real 8. SyncTERM 1.9 discards the oversized JXL blob whole, so
    the screen stayed blank; 1.10 clips it, so the picture lost its bottom. The wrong cell size separately misplaced the sixel tier's CUP origin and its dirty-rect boxes. 80x28 (640x392) overshoots by 8 lines the same way;
    80x30 (640x480) merely wastes the difference.

    The terminal had already answered with the right number. SyncTERM replies
    to XTSMGRAPHICS with charwidth*cols x charheight*rows -- its drawable area, 640x336 for that session -- but the reply landed in g_gfx_max_w/h and was applied to the SIXEL tier's ceiling alone.

    termgfx_fit_canvas() now clamps the canvas to that declared ceiling, and termgfx_fit_cell_w/h() derive the cell from it over the real 999;999-CPR
    grid whenever the terminal reports no cell size of its own. Both are used everywhere the geometry is computed: the fit, the centering, the sixel CUP re-derivation in present() and present_rgbx(), the dirty-box cell size, and
    the mouse mapping. The same session now traces

    canvas=640x400? fit=640x336 emit=537x336@51,0 cell=8

    Clamping rather than adopting is what keeps this safe for a terminal whose reply is a true graphics ceiling rather than a text area (xterm's
    ~1000x1000): staying inside a declared ceiling is what a ceiling is for,
    and such a terminal answers ESC[14t as well, so its real canvas is already known there.

    The mouse mapping carried its own copy of the 8x16 guess. Converting a
    1-based text cell to canvas pixels with an over-tall cell walks off the
    bottom of the image and clamps, so on that terminal every row below about
    21 of 42 mapped to the last game row -- felt as the pointer drifting
    further out the further down the screen it moved. Its test asserts the mid-screen row maps mid-picture and that the rows produce distinct values:
    the endpoints prove nothing, being deliberately snapped to the image edges
    so the extreme game rows stay reachable on a cell-granular terminal.

    The trace line gains fit=WxH beside canvas=WxH, since the gap between the
    two is where this class of bug lives.

    Affects every door on this module -- syncconquer, syncretro, syncrpg, syncscumm. 80x25 and the no-ceiling fallback are asserted unchanged.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net