@fafling : That was indeed the right thing to point at. VDP1 was locked to the field, it had been since 17 June, and it took me until 19 August to unlock it - not because the idea was hard, but because three things that each had to be true had never once been true in the same build. This post is what "I'll try that" turned into.
Standing disclaimer: feel free to correct anything below. Several claims in earlier posts here turned out to be wrong, and the corrections were worth more than the posts.
Two corrections to my own reply first, because both matter to anyone following the same advice. The route is not SGL's variable-framerate mode: I did try that in late June already, but it died because slSynch never writes EWLR/EWRR, so VDP1 erases a rectangle of width zero, and writing those two registers by hand freezes the menu. What shipped is the mechanism underneath it - the manual frame change the VDP1 manual calls VBE erase-and-change - driven directly, register by register. And the 30 fps cap never came into it: this engine runs at 7 to 16 fps, so one game frame already lasts four to eight fields.
Update Video, 11 minutes, every frame captured from a real console:
-- THE SYMPTOM --
From 17 June to 19 August, gaps opened between the VDP1 walls and the software picture, mostly at wall/ceiling (or cpu fallback walls to vdp1 walls) joins, and only while the view moved. My first guesses were bad movement calculation in my vdp1 quads creation, or a sync error. So I explored everything I could.
I tried four geometric corrections - shift the quad, shift the user clip window, sweep the projection gain, re-project the corners - and every one made the artefact worse, symmetrically, in both directions of rotation. It convinced me that it was a sync error, so I moved towards this instead.
Between 27 June and 2 August, I built six different presentation mechanisms - a draw-gated present (no tearing on hardware, walls a field behind on 30-60 % of frames); a full-slSynch branch; an NBG1 couple (gated off and never re-armed, because it waits on CEF); a present A/B pair of toggles, parked the same day; a coherent-pair present holding a walls+floors pair this renderer never builds; and a field-lock that pins the BLIT rather than the SWAP - perfect pairing on every capture, holes intact.
All of them failed with no improvement at all, or worsening the issue. So I parked them and I stopped looking in early July. I tried one last time on August 2nd, right before the last update (last item, labeled as "The hunt for the flickering seams") and this when
@fafling pointed out what I was missing. I wanted to give it a try right away, but to be honest, at that time, after 10 failed attempts to fix the same issue, I was gutted. So instead, I built a cover up, cpu drawn fallbacks over the holes, and chose to focus on my Abe's Odyssee port.
Then I came back strong with new energy, and most importantly, the corrected VDP1 & VDP2 manuals, and found issues with my previous trials:
-- THE FIRST WRONG THING: CEF --
Whether the drawing was finished was read from CEF, EDSR bit 1 at 100010H. The manual carries the warning on the same page: "This bit is reset to 0 when the frame buffers are changed or when drawing is started", and "If fetch of the draw terminate command matches when the frame buffer changes, CEF and BEF might not become 1.".
On silicon my two probe campaigns disagree about CEF: the first read it set on 30 to 60 % of frames, a later one read it always B and retired the metric as dead. They have never been reconciled by measurement, and it does not matter here, because neither reading makes CEF a usable gate. The gate that is never ambiguous is the command address register, COPR.
Note: the video contains the first measurement of frames (which is an error).
-- THE SECOND WRONG THING: TWO ERRATA ON THE SAME DEVELOPER CD --
ST-013 p.38, developer CD: "The number of characters that can be drawn in one FRAME is limited. Therefore, in order to draw more characters, the manual mode must be set." Corrected scan: "in one FIELD". With "frame", the sentence is a tautology, because the manual defines a frame as the interval between two framebuffer changes. With "field", the sentence is an instruction.
ST-013 p.39, developer CD: ERASE is described as "writing 0 to the VBE, FCM and FCT registers". Corrected scan: "writing 0 to the VBE and FCT registers and 1 to the FCM register". The first spelling is FBCR = 0x0000, which the table on the facing page calls 1-cycle mode.
Worth adding, because the manual calls all three of them "registers" and the mode table on p.38 lists them as one triple (FCT, FCM, VBE) : VBE is not an FBCR bit. It is bit 3 of TVMR at 100000H. FBCR is at 100002H and holds EOS, DIE, DIL, FCM, FCT. The erase-and-change sequence therefore spans two registers, which is p.40, not p.39.
-- WHAT THE MANUAL'S OWN DEFINITIONS SETTLE --
ST-013, Introduction: "A field is the time it takes a scanning line to scan one screen. A frame is the time it takes from one change of the frame buffer to the next change." And: "When the change mode of the frame buffer is in a one cycle mode, one frame is equal to one field." So the drawing window is closed by the SWAP, by definition. In 1-cycle mode the hardware closes the window every field, finished or not; in manual mode the CPU closes it with one write. At 7-16 fps one game frame lasts 4 to 8 fields, so 1-cycle mode was handing this engine somewhere between a quarter and an eighth of the budget actually available.
And overrun does not cost time, overrun costs content: the command list restarts at 00000H every frame, so an undrawn tail is abandoned, never resumed (as
@fafling pointed out his comment above).
-- AND WHAT THOSE ERRATA ARE ACTUALLY WORTH --
One fifth of an answer. My own audit lists five reasons this took me two months, and the errata are one of them. The one that outranks them is structural: a manual present, a fenced blit and a gate that does not read CEF had never once been in the same build - the fence post-dates every present experiment by a month. Two more sit above the errata as well. As far as I know, every
present verdict formed on Ymir was void because it models neither manual CEF nor LOPR, and never overruns. And the CEF issue was mine, not the manual's - the manual prints that caveat correctly. The shipped driver's own comment records that v1 already wrote FBCR = 0x0003, the value the corrected page prescribes, and failed anyway - what changed in v2 is the window, not the value: v1 pulsed it in the p.38 OUT window, v2 arms TVMR.VBE=1 with it at a fresh vblank IN and clears VBE after the OUT edge. The errata are a real trap, established as a trap; nothing establishes that this driver fell into it. Both spellings are still worth publishing, which is why they are in the video.
-- THE SEQUENCE THAT WORKS --
VBE erase and change: arm the erase on a fresh V-blank IN with TVMR bit 3 set and FBCR = 0x0003, and the swap lands at the end of that same blank. One write, one edge, nothing left to infer. Nothing exotic about it either - SEGA's own SBL ships that sequence in SCL_VBLV.C with interval 0xfffe, and Lobotomy Software's SlaveDriver ran manual frame change in-game in PowerSlave, whose source is what was read here.
-- THE OTHER WALL: 38 MAPS THAT WOULD NOT LOAD --
A 415-map bench (Doom, Ultimate, Doom II, TNT, Plutonia, Hell Revealed, Scythe, Nuts 1/2/3) had 38 maps that did not run slowly - they refused to load at all. Not for want of RAM: the Doom zone is 1,040,384 bytes and around 240 KB of it was free. It was in eight pieces, the biggest 48 KB, cut up by two PU_STATIC blocks parked at 64 KB and 655 KB, and the single largest allocation on a blocked map wants about 110 KB in one run. Two levers, not one: shrink the request (line_t 64 to 24 bytes - that one alone took the blocked count from 38 down to the Nuts family alone, while seg_t 32 to 14 and node_t 52 to 28 freed real RAM and moved zero maps, because LINEDEFS was the dominant term on every one of the 38), and grow the longest run the zone can still hand out (texture directories made lazy and purgeable: largest contiguous 48 KB to 130 KB). Defragmenting all the way to 110 KB is possible but is not free - that RAM is holding the sprites, the textures and the sound the map is playing.
-- STILL BROKEN, NAMED --
- The frame rate is 7 to 16 fps on a stock console, and the dominant term is not the present. The two real gains of the month were offline WAD work and a struct diet, not this fix, even if this looks way better, and brings back performance thanks to the deletion of everything I did before to fix it.
- The Nuts family of maps still refuses to load (but honestly I don't think it's a realistic goal). Every other map in a 415-map test set now does.
-- THANKS --
@fafling for naming the cause and sharing his retail Saturn Doom knowledge;
@slygamer and
@Wesker, who ran the hardware tests;
@TrekkiesUnite118 who has properly corrected several of my wrong claims in this forum, more than once;
the Kronos team (Runik, fafling, Benjamin Siskoo) who produced the errata-corrected VDP1 and VDP2 manual scans, and ST-013 is what this post leans on;