graphics: equal-z images stack in a different order from kitty and can reorder on reattach #155

Closed
opened 2026-10-18 12:18:00 +09:00 by natsukium · 0 comments
Owner

Overlapping images at the same z stack in a different order from kitty, and the order can change on reattach.

kitty sorts visible image refs by (z_index, image internal_id, ref internal_id) (kitty/graphics.c, the lt comparator before the QSORT of render_data). Internal ids increase in creation order, with three properties a client mirror cannot reconstruct today:

  • Re-transmission keeps the image's position. A same-id re-transmit calls free_image_resources on the existing Image and keeps its internal_id (handle_add_command).
  • Placeholder refs are minted at scan time. Each Unicode-placeholder run creates a real ref with a new internal id when the cells are rendered, so its order against a direct placement of the same image depends on when the placeholder was printed, not when the virtual placement was created.
  • Reattach loses creation order. compose_image_rehydrate (crates/felis-daemon/src/serve/streaming.rs) replays images in ImageStore::iter_ids() order and all direct placements before all virtual ones, so a fresh client shadow sees a different arrival order from the live one.

felis instead orders equal-z draws by client-shadow arrival order: direct placements first, then placeholder tiles in cell row-major order. Differing z stacks correctly; only exact ties diverge.

Matching kitty needs creation-order metadata on the wire (or a rehydrate order that reconstructs it), so it is a protocol change rather than a renderer fix. Once the placement layout is shared in felis-client-core, the fix lands in that one function for every frontend.

A test should render two overlapping equal-z images live and after reattach, and after re-transmitting the older one, and compare against kitty.

Overlapping images at the same z stack in a different order from kitty, and the order can change on reattach. kitty sorts visible image refs by `(z_index, image internal_id, ref internal_id)` (`kitty/graphics.c`, the `lt` comparator before the `QSORT` of `render_data`). Internal ids increase in creation order, with three properties a client mirror cannot reconstruct today: - **Re-transmission keeps the image's position.** A same-id re-transmit calls `free_image_resources` on the existing `Image` and keeps its `internal_id` (`handle_add_command`). - **Placeholder refs are minted at scan time.** Each Unicode-placeholder run creates a real ref with a new internal id when the cells are rendered, so its order against a direct placement of the same image depends on when the placeholder was printed, not when the virtual placement was created. - **Reattach loses creation order.** `compose_image_rehydrate` (`crates/felis-daemon/src/serve/streaming.rs`) replays images in `ImageStore::iter_ids()` order and all direct placements before all virtual ones, so a fresh client shadow sees a different arrival order from the live one. felis instead orders equal-z draws by client-shadow arrival order: direct placements first, then placeholder tiles in cell row-major order. Differing z stacks correctly; only exact ties diverge. Matching kitty needs creation-order metadata on the wire (or a rehydrate order that reconstructs it), so it is a protocol change rather than a renderer fix. Once the placement layout is shared in `felis-client-core`, the fix lands in that one function for every frontend. A test should render two overlapping equal-z images live and after reattach, and after re-transmitting the older one, and compare against kitty.
Sign in to join this conversation.
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#155
No description provided.