graphics: stack equal-z Kitty images in kitty's order, live and across reattach #168

Merged
natsukium merged 2 commits from graphics/equal-z-order into main 2026-10-18 17:15:58 +09:00
Owner

Closes #155.

kitty stacks equal-z images by (z, image internal id, ref internal id). felis stacked them by the order the client heard of each placement. That inverts kitty's order when a newer image is placed first, and a reattach could reorder them again.

The order can't be rebuilt from message order. The live outbox collapses a re-sent image to its last transmission. A put under the p= of a placement of the other kind keeps kitty's ref, but felis removes and re-adds it. So the daemon now ships both numbers at protocol minor 2:

  • ImageNew.serial: the store's existing creation serial. A re-send keeps it; a delete gives it up.
  • ImagePlacement.serial: the sequence number a placement was first inserted under. It is kept through a same-key replace, an anonymous rekey, and a virtual↔direct conversion. A dump without it backfills from the dumped sequence numbers, so DUMP_VERSION is unchanged.

The shared layout sorts by (z, image rank, direct before placeholder, placement serial). A minor-1 daemon sends neither number; the client then falls back to first-arrival image rank and shadow order.

Two choices were made on purpose:

  • Placeholder vs. direct placement of the same image: placeholders stay above. In kitty this pair is ordered by render timing.
  • Placeholder cells with placement id 0: which virtual placement they resolve to is unchanged. kitty's "first" is hash-map iteration order.

Both are explained in the explanation doc and the commit body.

Tests: layout ordering units, Placements serial invariants plus the proptest model, client-shadow rank, live put serials through the dispatcher, and an end-to-end check that live and rehydrated shadows draw the same kitty order. Mutating either serial path makes the end-to-end check fail. just check and just check-windows pass locally.

Reviewed with pi (sol): the design passed at round 4 and the implementation at round 1. The two non-blocking implementation notes (CHANGELOG wording, conversion-round-trip test plus fuzz seeds) are addressed in the second commit.

Closes #155. kitty stacks equal-z images by `(z, image internal id, ref internal id)`. felis stacked them by the order the client heard of each placement. That inverts kitty's order when a newer image is placed first, and a reattach could reorder them again. The order can't be rebuilt from message order. The live outbox collapses a re-sent image to its last transmission. A put under the `p=` of a placement of the other kind keeps kitty's ref, but felis removes and re-adds it. So the daemon now ships both numbers at **protocol minor 2**: - `ImageNew.serial`: the store's existing creation serial. A re-send keeps it; a delete gives it up. - `ImagePlacement.serial`: the sequence number a placement was first inserted under. It is kept through a same-key replace, an anonymous rekey, and a virtual↔direct conversion. A dump without it backfills from the dumped sequence numbers, so `DUMP_VERSION` is unchanged. The shared layout sorts by `(z, image rank, direct before placeholder, placement serial)`. A minor-1 daemon sends neither number; the client then falls back to first-arrival image rank and shadow order. Two choices were made on purpose: - **Placeholder vs. direct placement of the same image:** placeholders stay above. In kitty this pair is ordered by render timing. - **Placeholder cells with placement id 0:** which virtual placement they resolve to is unchanged. kitty's "first" is hash-map iteration order. Both are explained in the explanation doc and the commit body. Tests: layout ordering units, `Placements` serial invariants plus the proptest model, client-shadow rank, live put serials through the dispatcher, and an end-to-end check that live and rehydrated shadows draw the same kitty order. Mutating either serial path makes the end-to-end check fail. `just check` and `just check-windows` pass locally. Reviewed with pi (sol): the design passed at round 4 and the implementation at round 1. The two non-blocking implementation notes (CHANGELOG wording, conversion-round-trip test plus fuzz seeds) are addressed in the second commit.
kitty draws overlapping equal-z images by the internal ids it gives an
image and a ref at creation, so the older image sits under the newer
one. felis stacked them by the order the client heard of each placement,
which inverts that whenever a newer image is placed first, and a
reattach could reorder them again.

The order cannot be rebuilt from message order. The live outbox
collapses an image sent twice in one drain to its last transmission, so
a re-sent older image's header arrives after a newer one; and a put
under the p= of a placement of the other kind keeps kitty's ref while
felis removes and re-adds it, which a rehydrate replaying every direct
placement before every virtual one cannot restore. So the daemon ships
both numbers at protocol minor 2: ImageNew.serial is the store's
existing creation serial, which a re-send keeps and a delete forfeits,
and ImagePlacement.serial is the sequence number a placement was first
inserted under, kept through a same-key replace, an anonymous rekey,
and a virtual/direct conversion. Virtual placements keep their own
serial daemon-side so a later conversion inherits it; the client never
sorts them. A dump without placement serials backfills them from the
sequence numbers every released dump carries, so DUMP_VERSION stays.

Guaranteeing an arrival order instead was rejected: it would move a
collapsed transmission back to its first position, where frame and
show-frame markers queued between the two transmissions name frames the
moved one does not carry, and it makes message order an untyped
contract.

The layout sorts by (z, image rank, direct before placeholder,
placement serial). kitty orders a placeholder against a direct
placement of the same image by when the row last rendered, which no
mirror can know; placeholders stay above, as before. A minor-1 daemon
sends neither number, and the client then falls back to first-arrival
image rank and shadow order.

Which virtual placement a cell naming no placement resolves to is left
alone: kitty's "first" is the iteration order of a hash map, not
creation order.

Closes #155

Assisted-by: Claude Code
daemon, fuzz: pin the serial a placement keeps through a round trip to virtual
Some checks failed
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m10s
windows / detect relevant changes (pull_request) Successful in 25s
windows / windows docs-only no-op (pull_request) Has been skipped
bench / instruction-count regression gate (pull_request) Successful in 1m4s
windows / cargo nextest (Windows) (pull_request) Has started running
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 2m57s
pr / nix flake check (pull_request) Successful in 1m15s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m51s
pr / wire schema is compatible with the base (pull_request) Successful in 13s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m11s
windows / cargo clippy (Windows cross) (pull_request) Successful in 21s
windows / frontend smoke (Windows) (pull_request) Has been cancelled
7ec7bd0782
The equal-z change kept a placement's serial when a put turns it to the
other kind, but only the virtual-to-direct direction went through the
dispatcher in a test. A direct placement turned virtual and back must
keep its first serial too, as kitty keeps the ref the whole time. The
two seeds give the body fuzzer an ImageNew and an ImagePlacement that
carry the new fields.

The changelog entry now says images stack by creation rather than
transmission order, since a re-sent image keeps its place.

Assisted-by: Claude Code
Merge remote-tracking branch 'origin/main' into graphics/equal-z-order
Some checks failed
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m12s
bench / instruction-count regression gate (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Has been cancelled
pr / nix flake check (pull_request) Has been cancelled
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
pr / wire schema is compatible with the base (pull_request) Has been cancelled
pr / frontend smoke (x86_64-linux) (pull_request) Has been cancelled
windows / detect relevant changes (pull_request) Has been cancelled
windows / cargo clippy (Windows cross) (pull_request) Has been cancelled
windows / cargo nextest (Windows) (pull_request) Has been cancelled
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / windows docs-only no-op (pull_request) Has been cancelled
d56b1f1039
# Conflicts:
#	CHANGELOG.md
#	docs/reference/ipc.md
natsukium force-pushed graphics/equal-z-order from d56b1f1039
Some checks failed
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m12s
bench / instruction-count regression gate (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Has been cancelled
pr / nix flake check (pull_request) Has been cancelled
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
pr / wire schema is compatible with the base (pull_request) Has been cancelled
pr / frontend smoke (x86_64-linux) (pull_request) Has been cancelled
windows / detect relevant changes (pull_request) Has been cancelled
windows / cargo clippy (Windows cross) (pull_request) Has been cancelled
windows / cargo nextest (Windows) (pull_request) Has been cancelled
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / windows docs-only no-op (pull_request) Has been cancelled
to fcacefd6d1
All checks were successful
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m10s
bench / instruction-count regression gate (pull_request) Successful in 1m6s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m5s
pr / nix flake check (pull_request) Successful in 50s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m56s
pr / wire schema is compatible with the base (pull_request) Successful in 4s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 36s
windows / cargo clippy (Windows cross) (pull_request) Successful in 18s
windows / detect relevant changes (pull_request) Successful in 1m15s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 6m53s
windows / frontend smoke (Windows) (pull_request) Successful in 2m42s
2026-10-18 15:17:29 +09:00
Compare
natsukium scheduled this pull request to auto merge when all checks succeed 2026-10-18 15:20:27 +09:00
natsukium deleted branch graphics/equal-z-order 2026-10-18 17:15:58 +09:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
felis-terminal/felis!168
No description provided.