@@ -403,10 +403,25 @@ static void dirty_mark(struct Clayterm *ct, int x, int y, int w, int h) {
403403
404404/* ── Removal, denial, resize (§7.2/§11.2/§10.5) ───────────────────── */
405405
406+ /* Reconcile (and clear) the front-table records of one id — or every id
407+ * when only_id is 0 — without emitting bytes. The terminal's cells revert
408+ * to the blank state the moment the pixels are gone. */
409+ static void reconcile_and_clear (struct Clayterm * ct , uint32_t only_id ) {
410+ struct Graphics * g = ct -> gfx ;
411+ for (int i = 0 ; i < PLACEMENT_CAP ; i ++ ) {
412+ struct ImagePlacement * p = & g -> placements_front [i ];
413+ if (p -> used && (only_id == 0 || p -> registry_id == only_id )) {
414+ reconcile_blank (ct , p );
415+ p -> used = 0 ;
416+ }
417+ g -> placements_back [i ].used = 0 ;
418+ }
419+ g -> placement_count = 0 ;
420+ }
421+
406422/* Deletion bytes for every image with live front placements (one d=I per
407- * registry id, deduplicated), transmission-state reset, table clear, and
408- * cell reconciliation. Shared by removeImage (scoped to one id), denial,
409- * and resize (all images). Returns bytes written. */
423+ * registry id, deduplicated) plus the reconcile/clear above. The denial
424+ * (§11.2) and resize (§10.5) paths. Returns bytes written. */
410425static int emit_deletions_and_clear (struct Clayterm * ct , uint32_t only_id ) {
411426 struct Graphics * g = ct -> gfx ;
412427 int wrote = 0 ;
@@ -440,16 +455,7 @@ static int emit_deletions_and_clear(struct Clayterm *ct, uint32_t only_id) {
440455 }
441456 wrote = 1 ;
442457 }
443- /* reconcile + clear: both tables die with their placements */
444- for (int i = 0 ; i < PLACEMENT_CAP ; i ++ ) {
445- struct ImagePlacement * p = & g -> placements_front [i ];
446- if (p -> used && (only_id == 0 || p -> registry_id == only_id )) {
447- reconcile_blank (ct , p );
448- p -> used = 0 ;
449- }
450- g -> placements_back [i ].used = 0 ;
451- }
452- g -> placement_count = 0 ;
458+ reconcile_and_clear (ct , only_id );
453459 return wrote ;
454460}
455461
@@ -459,15 +465,18 @@ int image_remove(struct Clayterm *ct, uint32_t id) {
459465 if (e == NULL || !e -> live ) {
460466 return - IMG_ERR_UNKNOWN ;
461467 }
462- /* bytes whenever the id was live (§7.2), regardless of evidence */
468+ /* bytes whenever the id was live (§7.2) — a never-placed image's d=I is
469+ * a harmless no-op; a placed image's must flow even when the evidence
470+ * has since been denied (the terminal may still hold the pixels) */
463471 ct -> out .length = 0 ;
464- emit_deletions_and_clear (ct , id );
472+ kitty_delete_image (ct , id );
473+ reconcile_and_clear (ct , id );
465474 e -> live = 0 ;
466475 e -> id = 0 ; /* frees the slot for entry_free_slot */
467476 return ct -> out .length ;
468477}
469478
470- void graphics_capability (struct Clayterm * ct , int kitty_graphics ) {
479+ int graphics_capability (struct Clayterm * ct , int kitty_graphics ) {
471480 struct Graphics * g = ct -> gfx ;
472481 int was = g -> kitty_graphics ;
473482 g -> kitty_graphics = (uint8_t )(kitty_graphics != 0 );
@@ -476,6 +485,7 @@ void graphics_capability(struct Clayterm *ct, int kitty_graphics) {
476485 ct -> out .length = 0 ;
477486 emit_deletions_and_clear (ct , 0 );
478487 }
488+ return ct -> out .length ;
479489}
480490
481491int graphics_resize_prepare (struct Clayterm * ct ) {
@@ -733,6 +743,21 @@ void graphics_paint_ascii(struct Clayterm *ct, const struct ImageEntry *e,
733743
734744/* ── Frame phases (§9.2.6 ordering) ───────────────────────────────── */
735745
746+ /* Any covered cell differing front-vs-back? (§10.2's repair trigger.) */
747+ static int box_differs (struct Clayterm * ct , const struct ImagePlacement * bp ) {
748+ for (int y = bp -> y ; y < bp -> y + bp -> h ; y ++ ) {
749+ for (int x = bp -> x ; x < bp -> x + bp -> w ; x ++ ) {
750+ if (x < 0 || x >= ct -> w || y < 0 || y >= ct -> h ) {
751+ continue ;
752+ }
753+ if (cell_cmp (cell_at (ct , ct -> front , x , y ), cell_at (ct , ct -> back , x , y ))) {
754+ return 1 ; /* one write is enough to heal the whole box */
755+ }
756+ }
757+ }
758+ return 0 ;
759+ }
760+
736761/* Phase 0, before any emission: the repair precheck (§10.2) — a placement
737762 * whose covered cells will receive writes (front ≠ back anywhere in its
738763 * box) must be re-placed this frame; run before any dirty-marking mutates
@@ -744,18 +769,7 @@ static void graphics_precheck_repairs(struct Clayterm *ct) {
744769 if (!bp -> used || bp -> tier != IMG_TIER_KITTY ) {
745770 continue ;
746771 }
747- for (int y = bp -> y ; y < bp -> y + bp -> h ; y ++ ) {
748- for (int x = bp -> x ; x < bp -> x + bp -> w ; x ++ ) {
749- if (x < 0 || x >= ct -> w || y < 0 || y >= ct -> h ) {
750- continue ;
751- }
752- if (cell_cmp (cell_at (ct , ct -> front , x , y ),
753- cell_at (ct , ct -> back , x , y ))) {
754- bp -> repair = 1 ;
755- return ; /* one write is enough to heal the whole box */
756- }
757- }
758- }
772+ bp -> repair = (uint8_t )box_differs (ct , bp );
759773 }
760774}
761775
@@ -871,7 +885,10 @@ void graphics_emit_placements(struct Clayterm *ct, int row) {
871885 struct ImagePlacement * fp = front_match (g , p -> registry_id , p -> placement_id );
872886 int moved = fp == NULL || fp -> x != p -> x || fp -> y != p -> y || fp -> w != p -> w ||
873887 fp -> h != p -> h ;
874- if (!moved && !p -> repair ) {
888+ /* a re-transmitted image lost its terminal placements (the protocol's
889+ * delete-before-re-transmit) — the same frame must re-place it */
890+ int retransmitted = fp != NULL && fp -> data_version != p -> data_version ;
891+ if ((!moved && !retransmitted ) && !p -> repair ) {
875892 continue ;
876893 }
877894 emit_cursor (ct , p -> x , p -> y , row );
0 commit comments