Skip to content

The local gate green on an M6: the 7 ./ci.sh failures (and an 8th), fixed at the root - #830

Merged
paul-hammant merged 6 commits into
mainfrom
wip/m6-ci-green
Oct 20, 2026
Merged

paul-hammant merged 6 commits into
mainfrom
wip/m6-ci-green

Conversation

@paul-hammant

@paul-hammant paul-hammant commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

The local ./ci.sh on the M6 (Apple Silicon, MoltenVK and OpenGL 4.1) had 7 failures on main, and an 8th that only the full gate runs (the OpenGL editor drive). They are all fixed here, one commit each, plus one in aether-ui. Each failure was reproduced on main a1aad1d, under the test lock, before it was fixed.

# Failure Cause Fix
1 net_street: a client sent 5.1 KB a second (held under 4); behind that check, walkers' reconciliations moved them up to 1.9 m (held to 0) A regression from 62def79 (#664, solid players). Bisected over the first-parent history, building each tree with the Aether it pins: bae11e8 good, 5289409 bad; inside #739, 06b4776 good and 62def79 bad. Four ways the host's placing of the other players differed from the client's: snapshot times were 32-bit floats of a clock five days from its zero (31 ms coarse); a player spawned on a walker; held views stepped onto ticks the client had lost; a snapshot lost around the view. On top of that, a driver's three view and lead bytes on every command. fa98aab: host times sent as f64 (protocol 8); players meet only once both have been in the game a second; the client keeps its tick times; a view whose two ticks are not both held meets no one; view and lead sent as a bit when they follow. Corrections 0 in every run, a driver 3.83 to 3.98 KB a second. test_players adds a run at a clock five days on: main 13.7 mm, here 0.
2 zombie_street critique (Vulkan): the reflection under the feet read 0.22, wanted under 0.20 Lighting the scene as light (#655, #656, bc42f4a) raised the road's own reflection. With the figure hidden, the same cells read 0.02 then and 0.15 now; the figure's own part read 0.05 then and 0.08 now. 913754c: the check holds the figure's own part, over the reflection with the figure gone, to the same 0.20.
3 zombie_street frame cost (OpenGL): 10 program binds, recorded 5 #656 runs the post chain every frame (5 to 7) and #655 adds more (to 10). The counts are portable: at e69f3a5 the M6 matched the 4070 Ti. 744d2e1: program_binds 10. The ten are listed in the commit. The 4070 Ti's times are kept.
4 zombie_street frame cost (Vulkan): 52 material binds, recorded 53 #799's quoted sky binds its own set and was not counted. a6abec2: draw_sky counts its material bind. Nothing drawn changes.
5, 6 zombie_city at 20,000, both near bands: 696 draws against 285, 11 program binds against 5 Not a regression: e69f3a5, the commit that recorded the budget, reads the same on the M6. The 4070 Ti traces shadows by ray. MoltenVK has no ray query, so the map draws every caster: 410 of the frame's draws are in the shadow pass, and 291 are outside it. 0292392: shadow-map budgets recorded on the M6 under ci_scenes' exact settings. frame.stats now reports render.ray_shadows, and ae3d_bench --shadow-map-baseline picks the right budget.
7 ae3d_editor divider drag (Vulkan, and OpenGL too) aether-ui on AppKit: canvas_size followed by fill_width kept the colour picker exactly 240 wide. Its stretch to the parent edge (750) then held the inspector at 324 against the splitter (250). Found by turning off each of the 6,749 constraints in turn. aether-ui 4e5f6a5d (pushed to main): fill_width after a width makes the width a floor, as GTK and Win32 already behave. split_demo has a new spec for this: 9 checks fail on the old backend, and all 8 pass on macOS and on GTK 4.22.
8 ae3d_editor drive (OpenGL): five colour-picker checks failed picker_handle used the picker's rank among the canvases as its canvas id. On AppKit the OpenGL GPU view is listed as a canvas with no id. 16e6cfd: the drive takes the canvas id that /widgets gives.

Verification, Aether 0.834.0, M6:

Left for Paul and Nic:

  • The 4070 Ti's times in zombie_street's OpenGL budget are from before A linear HDR scene target, with tone mapping in post #656. Re-record them on that card when convenient; only program_binds moved here.
  • Fix 2 changes how the critique measures. Nic may want to look at the frames: the figure's part is 0.08, and the road's own reflection there is 0.15.

🤖 Generated with Claude Code

paul-hammant and others added 6 commits October 20, 2026 10:57
net_street failed ./ci.sh on the M6 at main a1aad1d: a driver sent
5.2 KB a second (held under 4), and behind that check every walker's
reconciliation moved it up to 1.9 m (held to 0). Bisected over the
first-parent history with each tree's Aether: 267d5ad and bae11e8
good (driver 3.7, corrections 0), 5289409 bad; inside #739, 06b4776
good and 62def79 ("Networked players meet each other where each client
saw them", #664) bad. Players became solid there, and each move meets
the others where the command's view had them; four things made the
host's placing and the client's differ, found by tracing both sides'
placements command by command:

- The host's times were 32-bit floats on the wire, of a monotonic clock
  counting from boot. This machine was up five days: 467,000 s, a float
  31 ms coarse, so every client placed the others a step off the host.
  A CI runner's clock is minutes from zero, which is why nothing else saw
  it. Snapshot, previous-tick, gone, create and destroy times now go as
  f64 (protocol 8; the budget counts the 4 bytes). test_players walks its
  two players into each other again five days from the clock's zero: on
  main corrected 13.7 mm (its check is a centimetre), here 0.
- The host met a player spawned on a walker at its first sample, which
  the client did not have yet: pushed 0.37 m. Two players now meet once
  both have been in the game a second (MEET_AFTER, from the tick each
  joined at, which the spawn message now carries).
- A held walk's view a step on landed on ticks whose snapshot the client
  had lost or no longer kept, so the client placed no one and the host
  did. A client keeps the times of its last 64 snapshots' ticks and only
  steps a view onto one of them.
- A snapshot lost around the view left the client's two samples of a
  player around it not the host's, placed 4 to 30 cm apart. Such a
  command now meets no one on either side (it goes with a lead of 0).

And the bytes: each command's view and lead cost three bytes, and a
driver sends every command. They are now a bit where they follow from
the command before's, in a mask sent only when a bit is set (input flag
4), the first command's from the one before the message, one input in
eight giving it its own; a host lacking that command keeps none that
follow from it and asks once a later one comes.

net_street now: corrections 0 in every run, commands asked again 0, a
driver 3.83 to 3.98 KB a second up, a walker 2.60 to 3.04, down 10.9 at
most (its join). The thirteen net suites and test_players pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en one's was

zombie_street (frame cost, vulkan) failed ./ci.sh on the M6: material
binds 52, recorded 53. Bisected over the first-parent history with each
tree's Aether: e69f3a5 (where the budget was last checked here) through
dafd5ff read 53; 6478e3a, #799 (the sky family written with quote),
reads 52, and so does every commit after.

The hand-written sky went through draw_pipeline, which counts a material
bind whenever the texture behind the scene's set changes, and the sky's
always does. The quoted sky binds its own set (vkquote.pair_new's) and
counted nothing, then reset the bound texture so the next draw counts
its own. One bind went uncounted; nothing drawn changed. draw_sky now
counts its material as the scene draws do: 53 again, draw calls 88 and
program binds 5 as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zombie_street (frame cost, opengl) failed ./ci.sh on the M6: program
binds 10, recorded 5. The count is the same on every machine (at
e69f3a5 the M6 read the 87 draws and 4 binds the 4070 Ti recorded), so
this is the frame, not the card. Bisected over the first-parent history
with each tree's Aether: f2969f1 reads 5, 6fa827b (#666) 9, later 10;
inside #666, 3e7fb12 ("Render the frame as light, tone mapped once in
the composite", #656) takes it from 5 to 7 and 9763e5d ("Light the scene
by its sky, a physical one", #655) to 10. Neither re-recorded the budget;
the scenes run only on a developer's machine.

The ten, one frame of it on GL, in order: shadow, default, sky,
colour_resolve, bloom_down (the reflection's blur), ssr, bloom_down (the
bloom's), bloom_up, composite, fxaa. #656 made the post chain run every
frame (resolve, reflection, bloom, one composite, FXAA after it), each
pass a program; each is needed where it stands (the reflection reads its
blurred input, the bloom the reflection's result). The sky's capture and
its irradiance and filter programs are not in it: they run when the sky
changes, not every frame. The same at 1280x720, where it was recorded.

Only program_binds moves. The times stay the 4070 Ti's: they are
compared only on that card, and are its to re-record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ad's

zombie_street (critique, vulkan) failed ./ci.sh on the M6: "what lies
just under the feet is the feet mirrored, not the torso smeared", 0.22
against 0.20. Bisected over the first-parent history with each tree's
Aether: f2969f1 reads 0.07, 6fa827b (#666) 0.20, main 0.22; inside
#666, the scene lit as light (#656 HDR and one composite, #655 a physical
sky, bc42f4a a real bloom and the street re-lit) took it to 0.17 and
then 0.20.

The check takes the most the reflection changes a cell in each of the six
rows under the feet, across the figure's columns. Reproduced it from the
frame at 320x180 and measured the same cells with the figure's two models
hidden (the road's own reflection of the street behind): 0.02 at f2969f1,
0.15 on main. The figure's part, over that: 0.05 then, 0.08 now. Lit as
light, the wet road under the feet reflects the street as every other
stretch of it does (beside the figure, columns 40 to 47, 0.18 then and
0.23 now), and that alone took the whole measure over the line; no torso
was smeared there.

So the check now holds the figure's part, the rows' changes less the
road's own with the figure gone, to the same 0.20, and the report says
both. Main passes at 0.08. A reflection four times as strong (strength
6), which read 0.59 whole and failed, reads 0.09 the figure's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ys are absent

Both "zombie_city at 20,000" near bands failed ./ci.sh on the M6: draw
calls 696 against 285, program binds 11 against 5, material binds 216
against 215. Not a regression: e69f3a5, the commit that recorded the
budgets on an RTX 4070 Ti, reads 696, 8 and 216 on the M6 too.

The 4070 Ti traces rays: zombie_city turns on ray-traced shadows where the
device has ray query, and the shadow map then has no city to draw. The
M6's MoltenVK has none ("ae3d: no ray query on this device; the shadow map
stands"), so every cascade's casters and every lamp face's are drawn into
the map. Counted at every draw site in vkframe at the held frame: 701 draws,
410 of them inside the shadow pass (274 through draw_pipeline, 136 through
draw_indirect), 291 outside it, about the 4070 Ti's 285 (my count is
at the frame drawn after the held one, the bench's 696 at it); the shadow, skinned, point and crowd shadow
pipelines are the six more. Triangles agree (316,996): they count the
models drawn, not the passes. A different frame on a different path, so
a budget of its own, not the old one loosened.

The shadow-map budgets are recorded with tools/ae3d_bench --record under
exactly what ci_scenes runs (320x180, AE3D_TICK=60, AE3D_HOLD=90, near
bands 12 and 28 m), on the M6, after the sky's material bind is counted
again: 696 draws, 11 program binds, 217 material binds both bands. Three
runs of the 12 m band and two of the 28 m: the same counts, the passes
within 0.05 ms (6.61, 25.35 and 0.15 ms; 6.83, 31.63 and 0.15). The times
are compared on the M6 at that size; on any other card only reported.

frame.stats now says whether the frame's shadows were traced
(render.ray_shadows, engine_ray_shadows), and ae3d_bench takes
--shadow-map-baseline, compared (or recorded) in place of --baseline when
the frame says they were not; ci_scenes passes the *.shadowmap file for
both near bands. (A first try grepped the scene's log for its "no ray
query" line; the line was not yet flushed when the bench started, and the
full ./ci.sh compared against the 4070 Ti's budgets all the same.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its rank

The OpenGL editor drive failed five checks in every local ./ci.sh on the
M6: "and the picker's square is painted at its hue (top right pixel -1)",
the strip and square picks, the hex beside the chip and the drag's one
Undo, with every /canvas/3/... route answering an HTTP error. (Runners
drive Vulkan alone, so only the local gate ever ran it.)

picker_handle counted the canvases in /widgets up to the picker and used
the count as the canvas id. On AppKit's OpenGL path the viewport is a GPU
view, which /widgets lists as a canvas too but which has no canvas id;
the picker came out 3 when there were two canvases, the viewport's
overlay (1) and the picker (2). /widgets says each canvas's own id
("canvas"), so the drive takes it, and the rank only where none is given.

Both drives on the M6 now pass every check (the divider drag with
aether-ui's AppKit fix for fill_width after a fixed width).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@paul-hammant
paul-hammant merged commit 39cc868 into main Oct 20, 2026
10 checks passed
@paul-hammant
paul-hammant deleted the wip/m6-ci-green branch October 20, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant