• src/doors/syncconquer/door/door_io.c src/doors/syncdoom/syncdoom.c src

    From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Sat Jul 11 15:00:28 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/969a17630973a69b89d980ad
    Modified Files:
    src/doors/syncconquer/door/door_io.c src/doors/syncdoom/syncdoom.c src/doors/syncduke/syncduke.h syncduke_input.c syncduke_io.c src/doors/syncmoo1/syncmoo1_input.c src/doors/syncretro/syncretro_audio.c syncretro_audio.h syncretro_input.c syncretro_io.c src/doors/termgfx/apc.c apc.h audio.c audio.h audio_mgr.c audio_mgr.h caps.c caps.h
    Log Message:
    doors: adopt SyncTERM APC blob media (audio + video), no cache files

    SyncTERM's blob APC verbs (Deuce, ef5b7f98a3) carry base64 media inline and decode straight to a slot/screen with no cache file, gated on CTerm version
    = 1.329. This teaches termgfx and all five doors to use them when the client is new enough, falling back to the existing cache path otherwise.

    Detection (termgfx/caps.{c,h}):
    - termgfx_caps_cterm_version() reads the version from the DA1 reply
    ("ESC[=67;84;101;114;109;MAJ;MIN;...c", 67.. spells "Cterm"): MAJ*1000+MIN.
    - Named thresholds TERMGFX_CTERM_VER_{PPM=1002,BLOB=1329} replace scattered
    magic numbers (syncdoom's inline copy now calls the shared helper).

    Audio (termgfx/audio.c, audio_mgr.c):
    - termgfx_audio_load_blob_file()/_load_blob() build A;LoadBlob (WAV/OGG).
    - stream_chunk() ships inline when the manager's blob_ok is set, else the
    legacy Store+Load with rotating cache names. Wins where audio actually
    streams: syncconquer FMV and syncretro game audio (its OGG chunks now skip
    the cache ring). The SFX/music doors keep the upload-once cache (better for
    repeated sounds) -- blob_ok is set there only for uniformity.

    Video (termgfx/apc.c):
    - termgfx_apc_image() gains a blob mode: C;DrawJXLBlob/DrawPPMBlob inline vs.
    the per-frame C;S Store + Draw. Wired in the JXL/PPM-tier doors
    (syncconquer, syncduke, syncdoom). Sixel is already inline, unaffected.

    Each door's DA1 handler enables the audio/video blob flags at >= 1329. The stats overlay shows a blob token only when actually in use (JXL/PPM tier inline, or retro's "a-blob" audio stream); moo1 has none.

    #256 workaround (non-blob clients): the JXL/PPM frame cache filenames were fixed strings, so two SyncTERM windows of one dialing-entry -- which share a cache dir -- collided. Salt the name per session with the door's PID via termgfx_session_salt() (xpdev GetCurrentProcessId): syncalert_<pid>.jxl etc. Only the cache path is affected; blob clients write no file. (syncterm #256.)

    Compile-verified across all five doors; the blob paths need SyncTERM >= 1.329 to live-test, and the fallback is unchanged so older clients are unaffected.

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

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Mon Jul 13 21:37:59 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/561cd5b998b75665f085c391
    Modified Files:
    src/doors/syncconquer/door/door_io.c src/doors/syncdoom/syncdoom.c src/doors/syncduke/syncduke_io.c src/doors/syncmoo1/syncmoo1.h syncmoo1_input.c syncmoo1_io.c src/doors/syncretro/syncretro.h syncretro_input.c syncretro_io.c
    Log Message:
    doors: size the image to what the terminal will DRAW, not to its window

    SyncDOOM (and SyncDuke) rendered all black in an xterm bigger than its default size. The image was fine; xterm was throwing it away whole.

    A sixel whose DECLARED raster exceeds the terminal's graphics limit is not clipped -- xterm discards the entire image (graphics_sixel.c, GetExtent: "raster width %d > max %d" -> finished_parsing + return, nothing drawn). The limit is maxGraphicSize, which defaults to 1000x1000. So any window whose canvas is wider than ~1000px got a sixel xterm refused outright, and the
    screen stayed black; the default small window stayed under the limit and worked, which is exactly the reported symptom.

    And the terminal TELLS us the limit -- we were discarding the answer. XTSMGRAPHICS (ESC[?2;1S) reports Min(window, graphics_max) -- the biggest
    sixel it will accept -- and every door already sends the query as part of termgfx_term_probe. But xterm answers BOTH that and ESC[14t, and the doors either preferred the ESC[14t window size or never parsed the graphics reply at all, so they fitted the image to a window the terminal would not draw into.

    The graphics geometry is now authoritative in every door that uses termgfx, whichever order the replies arrive in:

    SyncDOOM used XTSMGRAPHICS only as a fallback when ESC[14t was silent --
    so on xterm it fitted the 1920x1080 window, emitted a 1280x800
    sixel, and got it discarded. Now prefers it.

    SyncDuke fitted the sixel to the ESC[14t window (syncduke_update_outsize)
    even though syncduke_canvas_*() already preferred the graphics
    geometry for JXL -- and its OUT_W_MAX is 1024, itself over
    xterm's limit. Now fits the canvas.

    SyncMOO1 never parsed the reply at all (only ESC[14t). Added, and it
    outranks the window.

    SyncRetro likewise never parsed it -- and worse, its F4 handler assumed "no
    terminal SENDS a CSI S as input", so the XTSMGRAPHICS reply (CSI
    ? 2;0;W;H S) fell through as an F4 KEYPRESS and silently cycled
    the render tier at startup on any terminal that answers it. The
    '?' private intro now routes it to the canvas handler instead,
    fixing both.

    SyncConquer already landed on the right value, but only because the 't'
    reply happened to arrive before the 'S' one. Made explicit rather
    than left to ordering.

    Verified by driving SyncDOOM through a simulated xterm (answering ESC[14t with 1920x1080, ESC[?2;1S with 1000x1000, DA1 with sixel): it now sizes to win=1000x1000 and emits a 1000x625 image, inside the limit. The old code
    fitted the window and produced 1280x800 -- discarded.

    Pre-existing, not a regression from the recent sixel-scaling work: the width path is unchanged by it. It surfaced now because that work is what got xterm tested at size.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Mon Jul 13 21:37:59 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/d45db994a03ca93e4f80858b
    Modified Files:
    src/doors/syncconquer/door/door_io.c src/doors/syncdoom/syncdoom.c src/doors/syncduke/syncduke_input.c src/doors/syncmoo1/syncmoo1_io.c src/doors/syncretro/syncretro_io.c src/doors/termgfx/geometry.h
    Log Message:
    doors: assume xterm's 1000x1000 sixel ceiling when nothing advertises one

    The previous commit made the terminal's XTSMGRAPHICS graphics geometry authoritative, which was right but useless against the terminal that motivated it: xterm ships with window operations DISABLED, so it answers NEITHER ESC[14t NOR the XTSMGRAPHICS query. With no reply to act on, the doors fell back to sizing the image from cols*cell -- the only geometry they can still get -- and a large window yields a large estimate.

    From the reported session's log: cols=369 rows=90, cell=6x13, so win=2214x1170 (369*6 x 90*13, derived, not reported) and image 1024x640. That sixel declares a raster wider than xterm's maxGraphicSize, which defaults to 1000x1000, and xterm does not clip an oversized image -- it aborts the parse and draws
    NOTHING (graphics_sixel.c: GetExtent -> finished_parsing + return). Hence a black screen in any big xterm window, and a correct picture in a small one.

    So the ceiling has to be assumed rather than queried. TERMGFX_SIXEL_SAFE_MAX (1000) caps the canvas in every termgfx door whenever no graphics geometry was advertised. A terminal that can take more says so and gets the bigger image; a silent one now gets a picture instead of a void.

    Verified against both shapes of terminal, driving SyncDOOM through a pty: replaying the reported xterm (answers nothing, 369x90 grid, 6x13 cell) it now sizes win=1000x1000 -> image 1000x625, inside the limit, where before it built 1024x640 and had it discarded; and a SyncTERM that answers XTSMGRAPHICS with its real 640x400 canvas is UNCHANGED at image 640x384, since the cap never engages when the terminal advertises.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Mon Jul 13 21:37:59 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/b42e26c050046c896233f631
    Modified Files:
    src/doors/syncconquer/door/door_io.c src/doors/syncdoom/syncdoom.c src/doors/syncduke/syncduke.h syncduke_input.c syncduke_io.c src/doors/syncmoo1/syncmoo1_io.c src/doors/syncretro/syncretro_io.c src/doors/termgfx/geometry.c geometry.h
    Log Message:
    doors: clamp the IMAGE to the graphics ceiling, not the canvas

    The previous commit stopped the black screen by capping the canvas to xterm's 1000x1000 default when the terminal advertises no graphics geometry. That
    fixed the rendering and broke the centering: the canvas is also what the image is CENTERED AGAINST, so a 1000-wide image was centered inside a 1000-wide "canvas" and pinned to the left of a 2214-wide xterm window. Doom sat centered under foot (which reports a canvas) and hugged the left edge under xterm.

    Two different quantities were being conflated. The graphics geometry is a
    limit on what the terminal will DRAW -- exceed it and the sixel is discarded whole. The canvas is the area to CENTER IN, and it is the real terminal, however big. Clamp the fitted image to the former; keep measuring against the latter.

    termgfx_geom_gfx_clamp() shrinks a fitted image to the ceiling preserving aspect, and every door now applies it to the FIT result while centering
    against the true canvas. SyncDOOM and SyncDuke clamp the fit inputs directly (they build the fit themselves); SyncMOO1, SyncRetro and SyncConquer clamp the fitted image after the fact, which leaves their page/reserve math untouched.

    Verified by driving SyncDOOM through a pty against both shapes of terminal: replaying the reported xterm (2214x1170, advertises nothing) it now emits
    image 1000x625 @607,266 -- inside the ceiling AND centered, since
    (2214-1000)/2 = 607 -- where the previous commit produced a correctly-sized image pinned at the left. A SyncTERM that advertises its 640x400 canvas is unchanged at image 640x384, the cap never engaging.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • 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/d0fd3b5d129f7a20f419edfb
    Modified Files:
    src/doors/syncconquer/door/door_io.c src/doors/syncdoom/syncdoom.c src/doors/syncduke/syncduke_io.c src/doors/syncmoo1/syncmoo1.h syncmoo1_geom.c syncmoo1_geom.h syncmoo1_input.c syncmoo1_io.c src/doors/syncmoo1/tests/test_geom.c src/doors/syncretro/syncretro.h syncretro_input.c syncretro_io.c src/doors/termgfx/geometry.c geometry.h termgfx_termio.c src/doors/termgfx/test/test_termgfx_termio_fitcanvas.c
    Log Message:
    doors: measure the terminal's canvas instead of assuming 640x400

    Every graphical door starts from a 640x400 canvas 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 assumption stood, and every fit, centering, cell-size and pointer calculation was built on it.

    It is only true in 80x25. SyncTERM's 80x43 canvas is 640x350 and its
    132x43 is 1056x350, and the door had no idea. Four defects followed, each
    found by a captured session rather than by reading the source:

    - 80x43 emitted a 400-line frame into a 350-line canvas. SyncTERM 1.9
    discards an oversized JXL blob WHOLE, so the screen stayed blank;
    1.10 clips it, so the picture lost its bottom. The terminal had been
    answering XTSMGRAPHICS with its real drawable area all along, but the
    reply was filed as a sixel ceiling and never used as the canvas.

    - The cell size was derived from the assumed canvas: 400/42 = 10 against
    a real 8, which misplaced the sixel tier's CUP origin and every
    dirty-rect box.

    - The mouse mapping carried its own 8x16 guess. Converting a text cell
    to canvas pixels with an over-tall cell walks off the bottom of the
    image and clamps, so on an 80x43 terminal every row below about 21 of
    42 mapped to the last game row -- the pointer drifting further out the
    further down the screen it moved.

    - 132-column modes are 1056 wide, i.e. LARGER than the assumption, where
    clamping to the reported area does nothing at all. The picture was fit
    and centered inside a phantom 640-wide canvas and sat against the left
    edge with the right half of the screen empty.

    So the reported area is now ADOPTED as the canvas when no ESC[14t answer arrived, and only clamped to when the canvas is genuinely known -- the
    xterm case, where the raster ceiling is smaller than the window it
    reports.

    Separately, a door emits pixels but does not decide their SHAPE. SyncTERM scales its framebuffer to the video mode's display aspect
    (vidmodes.c aspect_width/height, applied by bitmap_get_scaled_win_size_nomax
    aspect_fix_wc), so a canvas pixel is 0.833 as wide as tall in the classic
    640x400 and 0.442 in a 132-column 1056x350 -- more than twice as tall as square. Doors were calibrated on the 640x400 shape, where 320x200 mode-13h
    art lands right because it was drawn for that same squeeze; everywhere else
    the picture was stretched, 14% in 80x43 and 88% in 132x43.

    termgfx_geom_par_correct() returns the factor to widen the source by so it keeps the shape it has on that reference canvas. It is exactly 1.0 for any 640x400-shaped canvas, so the mode every door was tuned in stays
    bit-identical. It assumes a 4:3 display, which is exact for 48 of SyncTERM's
    52 modes; the display aspect cancels out of the ratio, so only the current
    and reference modes need share it. The four that differ cannot be told apart from their 4:3 twins by anything a door can query -- C80 and LCD80X25 are
    both 80x25 with 8x16 cells on a 640x400 framebuffer, with pixel aspects of 0.833 and 1.000 -- so no door-side rule can separate them, and a terminal
    that reported its pixel aspect would retire the assumption.

    The correction is gated on the peer being SyncTERM: every other terminal
    here draws its canvas with square pixels, where this would be the
    distortion rather than the cure.

    The two doors that CACHE their geometry needed one thing more. SyncTERM
    sends its probe replies in one burst with the identification LAST, after
    the canvas and grid replies that trigger a recompute -- so the fit was
    already decided by the time the door knew who it was talking to, and the correction stayed gated off for the whole session. Both now re-fit when the identification lands. SyncRetro only appeared to escape this because sr_io_set_aspect() re-fits afterwards, and that early-returns when the
    core's aspect is unchanged.

    SyncMOO1 also gains the per-geometry-change log line SyncRetro already
    prints. The canvas-vs-page-vs-cell chain is where every wrong-size bug in
    these doors has lived, and it is what identified this one: canvas, grid,
    cell and page were all correct and only the SyncTERM flag was wrong.

    Live-verified on SyncTERM 1.9rc4 and 1.10a in 80x25, 80x43 and 132x43: SyncSCUMM, SyncRPG, SyncMOO1, SyncRetro, SyncDOOM. 80x25 with the status
    line hidden is bit-identical; with it shown the image is 15px shorter and
    no longer runs under the status bar, which is the same assumption failing
    in the mode it was made for.

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

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