https://gitlab.synchro.net/main/sbbs/-/commit/0aa6830227b28facbb622eeb
Modified Files:
docs/door_graphics_audio_matrix.md src/doors/syncdoom/syncdoom.c src/doors/syncduke/syncduke.h syncduke_input.c 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/termgfx/geometry.c geometry.h sixel.c sixel.h
Log Message:
doors: measure the terminal's sixel scaling instead of guessing
Whether a sixel terminal honors the raster attribute's pan (vertical) scaling is not something you can infer from its identity, and the three sixel doors were each guessing differently. SyncDuke and SyncDOOM assumed every terminal honors pan and sent a half-height encode to all of them; SyncMOO1 assumed none off SyncTERM did and always sent a full 1:1 encode. Both are wrong, on different clients: xterm and WezTerm draw the sixel at its ENCODED size, so Duke/DOOM handed them a half-height picture until the player found the full- res tier, while foot, Contour and Windows Terminal DO scale, so SyncMOO1 paid ~4x the raster bytes it needed to.
So measure it. termgfx_sixel_vscale_probe() draws the same 36px sliver twice
-- once with pan=1, once with pan=2 -- with a cursor-position report before
and after each. A terminal that honors pan advances the cursor twice as far
for the second copy; one that ignores it advances the same. Comparing the two advances is self-calibrating (it needs no knowledge of the cell height, and survives whatever the terminal's cursor-after-sixel convention is) and it
tests the BEHAVIOR we depend on rather than sniffing a name that rots.
Verified against a real SyncTERM: 36px advanced 2 rows, 72px advanced 4.
All three doors now share one policy -- SyncTERM halves both axes, a terminal the probe catches honoring pan halves the height only, and one that scales neither gets a full 1:1 encode. No answer inside the window reads as "does not scale", the safe direction, since 1:1 is correct everywhere and merely fatter. The probe runs once at startup, only for a non-SyncTERM terminal that has answered the capability probe and has sixel, and its cursor reports are consumed before the DSR frame pacing can miscount them as frame acks.
KNOWN BLIND SPOT, documented at the probe: it measures the SPACE the image occupies, not whether the content was STRETCHED to fill it. A terminal that honored pan by letterboxing the pixels inside a taller box would advance the cursor exactly as far as one that scales, and be misread as scaling. Nothing
on the wire distinguishes them -- the cursor position is the only thing a terminal ever reports back about a sixel -- so any probe over this protocol inherits the hole. No characterized terminal behaves this way, the failure is loud (half-size picture in a full-size box), and the remedy is the manual full-res tier, which is correct regardless of what the terminal does with pan. That is why the manual override earns its keep alongside the auto-detection.
Also: never encode below native. Whatever the terminal can scale, the door now caps the factor at what keeps the ENCODE at or above the game's native frame (termgfx_geom_sixel_scale(), long SyncMOO1's rule, hoisted so every door gets it). The encode is what carries the pixels; the display is encode x factor.
Let the encode fall under native and you discard rows the game actually drew, and the terminal replicates them back to the right SIZE -- right-sized, permanently blurrier. It answers per axis and the axes genuinely disagree: fitting 320x200 into 640x384 gives pad=2 (320 encoded columns IS native) but pan=1 (384/2 = 192 < 200). Duke and DOOM hardcoded 2 on both axes and silently dropped 8 of the 200 rows there, and 50 of them in a small window. Big
canvases -- where sixel actually lives -- are unaffected: their half-res
encode sits well above native, so it stays lossless AND cheap.
With correctness handled automatically, the full-res (F4) tier goes back to being what it should be: the player's explicit bytes-for-exactness call (a half-res encode quantizes the scale to 2x2 blocks; a 1:1 encode lands on the fitted rectangle pixel-exactly), plus the backstop for a fooled probe. It
stays a sticky per-user preference, and is now offered as an F4 stop ONLY
where it is a different picture -- on a terminal that scales neither axis the only possible encode is 1:1, so a second stop would have been the same image under another name, and cycling onto it would have persisted a preference the player never meaningfully made.
All three doors build clean; SyncMOO1's geom tests cover the new pan-only branch and the never-below-native refusal (4/4). Live-verified only on the scaling side (SyncTERM); the does-not-scale branch is reasoned, not yet run against xterm/WezTerm.
---
þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net