Skip to content

Stop per-frame whole-board re-evaluation - #71

Merged
austin-smith merged 3 commits into
mainfrom
stop-per-frame-board-reevaluation
Jul 15, 2026
Merged

austin-smith merged 3 commits into
mainfrom
stop-per-frame-board-reevaluation

Conversation

@austin-smith

Copy link
Copy Markdown
Owner

What Changed

  • Removed the AnyView decoration chain in ContentView: sceneDecorations/applyToolbar/applySheets/applyObservers are generic over their content now, so SwiftUI can structurally diff the scene instead of comparing through an opaque box. Helper bodies are unchanged.
  • Extracted the drag gesture's fast-changing state into an observable DragInteractionController read only by DragOverlayView. Per-frame translation writes now re-render just the overlay instead of the entire board; activeTarget keeps its crossing-only invalidation through an equality-guarded setter. The waste-return anchor frame fusion moved into the overlay with the state it reads, and the overlay's unused cardTilts parameter is gone.
  • WinCascadeOverlayView reads the celebration controller itself, so the cascade's per-frame card mutations re-render only the flying cards, not the board underneath.

Why

Dragging a card wrote dragTranslation and activeTarget into ContentView state on every gesture frame, and the whole board tree lives in ContentView.body — so every drag frame re-evaluated every pile, foundation, and card, through an AnyView wrapper that defeated diffing. The win cascade had the same shape at ~60 fps. This is the container half of the follow-up to #70; the value-slicing half (per-pile pruning) comes next as its own PR.

Validation

  • macOS and iOS builds clean; full unit suite green after each commit's stage.
  • Simulator interaction sweep: invalid-drop return (card tracks the pointer, springs back, no ghost), waste-fan invalid drop returning to the exact fanned slot, FreeCell drag-to-cell valid drop, tap auto-move flight, Klondike draw-3 fan, game switching, Statistics sheet round-trip.
  • Win cascade verified in motion via an injected one-move-from-win fixture: Auto Finish flew all four kings, the cascade and win overlay rendered through the new observation path, and Play Again reset cleanly.
  • Zero visible behavior change; drag feel, drop targeting, and all animation timings are unchanged.

body wrapped the board in anyview and threaded it through toolbar,
sheet, and observer helpers that each took and returned anyview,
erasing the tree's structural identity and forcing swiftui to diff the
whole scene through an opaque box on every update. the helpers are
generic over their content now; their bodies are unchanged.
the drag gesture wrote dragtranslation and activetarget into
contentview state on every frame, re-evaluating the entire board tree
at gesture rate. the drag cluster now lives in an observable
draginteractioncontroller read only by dragoverlayview, so per-frame
writes re-render just the overlay; activetarget keeps its
crossing-only invalidation through a guarded setter. the waste-return
anchor frame fusion moves into the overlay with the state it reads,
and the overlay's unused cardtilts parameter is dropped.
the celebration task mutates its cards array every frame while the
cascade flies, and contentview's body read it — re-evaluating the
whole board per tick. the overlay reads the controller itself now, so
the ticks re-render only the flying cards.
@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@austin-smith
austin-smith merged commit aec652d into main Jul 15, 2026
2 checks passed
@austin-smith
austin-smith deleted the stop-per-frame-board-reevaluation branch July 15, 2026 03:26
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