• src/doors/syncmoo1/syncmoo1_geom.c syncmoo1_geom.h src/doors/syncmoo1/

    From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Thu Jul 9 01:55:11 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/f4d189b62669e4e8690c26f3
    Added Files:
    src/doors/syncmoo1/syncmoo1_geom.c syncmoo1_geom.h src/doors/syncmoo1/tests/test_geom.c
    Modified Files:
    src/doors/syncmoo1/CMakeLists.txt syncmoo1_io.c src/doors/syncmoo1/tests/CMakeLists.txt
    Log Message:
    syncmoo1: never encode the sixel below the native frame

    The sixel encode is resampled from 1oom's native 320x200 indexed frame
    with a nearest-neighbour step, which DROPS a source row or column
    whenever the encode is smaller than the source on that axis. MoO1's UI
    font draws 1-pixel-wide strokes, so a dropped column takes a whole glyph
    with it: the options screen showed "SFX volume 00" for a volume of 100,
    the title screen "1oom v1.1.8", and the options menu "Inaut" for "Input".

    Two independent causes, both now removed:

    Width. The fit preserves the source aspect, so any page shorter than
    400px is height-limited and comes out narrower than the native 2x. An
    80x24 terminal (an 80x25 SyncTERM showing its status line) fits to
    588x368 and encodes 294 columns, dropping 26 of 320. The <=8% letterbox stretch was meant to cover this and doesn't: 640 vs 588 is an 8.8% bar.
    Widen a height-limited fit back out to the native 2x instead of tuning
    the percentage -- but only while the frame still gets its native height,
    so a degenerately short page can't be squashed below 1:1 vertically.

    Height. The pixel-aspect upscale we ask the terminal for was a hardcoded pan=pad=2, so the encode was always half the displayed size -- and half
    of a 368px-tall fit is 184, below the 200 rows we started with. Even the healthy 80x25 case dropped 8. Each axis now takes the 2x only when the resulting encode still covers the native frame on that axis, and falls to
    1 (upsampling, which duplicates and never loses) when it wouldn't. The
    6-row sixel band clamp factors into that choice: trimming a 200-row
    encode to a whole 198 drops two source rows just as surely.

    Source pixels discarded per frame, before -> after:

    80x24 294x180, 26 columns + 20 rows -> 320x366, none
    80x25 320x192, 0 columns + 8 rows -> 320x384, none

    Costs ~1.6x the sixel bytes per frame on a page under 400px tall (28.9K
    45.9K at 80x25); run-length coding absorbs the rest of the doubling.
    Frame de-duplication still skips static screens and the DSR-ACK pacing
    adapts, so a slow link loses frame rate rather than backing up. A page
    tall enough to clear 200 rows at pan=2 -- a 116x36 foot session, say --
    keeps the cheap 2x on both axes and costs exactly what it did before.

    The fit and encode math moves to a new pure syncmoo1_geom.c, since syncmoo1_io.c pulls in termgfx's audio manager (and with it
    C++/libADLMIDI) and can't be linked into a unit test. tests/test_geom.c
    locks down the invariant on both axes across every 8x16 page from 20
    rows up; it is what caught the band-clamp case above.

    Verified end-to-end against a fake SyncTERM driving the real encode path:
    at 80x24 the raster goes 294x180 -> 320x366 and "1oom v1.11.8" renders
    with all three 1s. The 80x25 sixel is byte-identical to before at the
    old pan/pad, and the sm_geom_encode_dims() change is what moves it.

    Not verified on a live SyncTERM. Note also that SyncDOOM and SyncDuke
    carry the same defect in their own copies of this math (both encode
    320x192 at 80x25, dropping 8 rows); their JXL tier is immune, being a 1:1
    blit the door upscales itself.

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