This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: cmp-ios-metal-cadisplaylink-vsync-lock

Compose Multiplatform iOS CAMetalLayer CADisplayLink V-Sync Cadence Lock

Aligns Kotlin Multiplatform / Compose Multiplatform iOS rendering loop passes strictly with CoreAnimation CADisplayLink ticks on ProMotion (120Hz) and variable refresh rate displays. Prevents thread contention, main thread stalls, and dropped frame pacing by synchronizing CAMetalLayer drawable presentation calls with NSRunLoopCommonModes ticks rather than unbounded Kotlin coroutine dispatchers.

Prohibited Anti-Pattern: Free-Running Coroutine Render Dispatches and Unsynchronized CAMetalLayer NextDrawable Calls Leading to ProMotion Jitter

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Compose Multiplatform iOS CAMetalLayer CADisplayLink V-Sync Cadence Lock - Aligns Kotlin Multiplatform / Compose Multiplatform iOS rendering loop passes strictly with CoreAnimation CADisplayLink ticks on ProMotion (120Hz) and variable refresh rate displays. Prevents thread contention, main thread stalls, and dropped frame pacing by synchronizing CAMetalLayer drawable presentation calls with NSRunLoopCommonModes ticks rather than unbounded Kotlin coroutine dispatchers.
globs: **/iosMain/**/*.kt, **/iosApp/**/*.swift, **/metal/**/*.kt, **/ui/**/*.kt
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/cmp-ios-metal-cadisplaylink-vsync-lock"
---

# Compose Multiplatform iOS CAMetalLayer CADisplayLink V-Sync Cadence Lock

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Free-Running Coroutine Render Dispatches and Unsynchronized CAMetalLayer NextDrawable Calls Leading to ProMotion Jitter

## System Constraints
You are a Principal Mobile Architect enforcing rock-solid frame cadence between Compose Multiplatform on iOS and Apple Metal. On Apple devices (particularly 120Hz ProMotion screens), Compose rendering passes must strictly align to CADisplayLink callbacks registered on NSRunLoopCommonModes. You must eliminate unbounded render dispatches, block unsynchronized CAMetalLayer.nextDrawable() invocations on background threads, and enforce correct CAFrameRateRange configurations to eradicate hitching and thread contention.

## Strict Guidelines
- Engineers MUST ALWAYS hook the Compose Multiplatform frame clock to a platform-native CADisplayLink attached to NSRunLoop.mainRunLoop in NSRunLoopCommonModes.
- Engineers MUST NOT trigger software-driven re-draws or metal render passes via unconstrained Kotlin Coroutine dispatchers (e.g., Dispatchers.Default or fixed-delay delay(16) loops).
- Code MUST set `CAFrameRateRange(minimum: 60.0, preferred: 120.0, maximum: 120.0)` on CADisplayLink when targeting high refresh rate ProMotion devices to avoid adaptive rate down-stepping hitch.
- Services MUST acquire CAMetalDrawable instances only during the CADisplayLink callback or within a strictly bounded draw pass with `presentsWithTransaction = false` and non-blocking timeout handling.
- Components NEVER block the main RunLoop waiting for GPU command buffers; frame submission must use `MTLCommandBuffer.addCompletedHandler` or double/triple buffering dispatch semaphores with a depth of 2 or 3.

## Bad vs Good Implementations
### V-Sync Cadence Alignment for Compose Multiplatform iOS View Controller

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Using Timer or coroutine-based delay loops introduces severe frame drift, ignores Apple ProMotion (caps execution arbitrarily at 60Hz or drops frames under load), and pauses completely during touch tracking because it is not scheduled in NSRunLoopCommonModes.
```swift
// Anti-Pattern: Coroutine or Timer driven tick independent of CoreAnimation runloop
import UIKit
import MetalKit

final class ComposeMetalViewController: UIViewController {
    private var timer: Timer?
    private var metalView: MTKView!

    override func viewDidLoad() {
        super.viewDidLoad()
        metalView = MTKView(frame: view.bounds)
        view.addSubview(metalView)
        
        // BAD: Standard Timer runs on default mode, drifts, causes frame drops on scroll,
        // and cannot negotiate 120Hz ProMotion dynamic frame rates.
        timer = Timer.scheduledTimer(withTimeInterval: 0.016, repeats: true) { [weak self] _ in
            self?.metalView.setNeedsDisplay()
        }
    }
}
```

#### Production Standard (Correct)
> **Standard**: Attaches CADisplayLink directly to .common RunLoop modes, unlocking uninterrupted renders during drag/scroll gestures. Configures CAFrameRateRange for 120Hz ProMotion support and passes target presentation timestamps directly to the Compose frame clock.
```swift
// Production Invariant: Synchronized CADisplayLink with ProMotion FrameRateRange
import UIKit
import MetalKit
import QuartzCore

final class ComposeMetalViewController: UIViewController {
    private var displayLink: CADisplayLink?
    private var metalLayer: CAMetalLayer!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let layer = view.layer as? CAMetalLayer else {
            fatalError("Root layer must be CAMetalLayer")
        }
        self.metalLayer = layer
        self.metalLayer.presentsWithTransaction = false
        self.metalLayer.framebufferOnly = true
        
        setupDisplayLink()
    }
    
    private func setupDisplayLink() {
        displayLink = CADisplayLink(target: self, selector: #selector(onVsyncTick(_:)))
        
        // Support ProMotion dynamic refresh cadence (60 - 120 FPS)
        displayLink?.preferredFrameRateRange = CAFrameRateRange(
            minimum: 60.0,
            preferred: 120.0,
            maximum: 120.0
        )
        
        // Ensure ticks continue through gesture tracking and table scrolling
        displayLink?.add(to: .main, forMode: .common)
    }
    
    @objc private func onVsyncTick(_ link: CADisplayLink) {
        let targetTimestamp = link.targetTimestamp
        // Forward synchronized timestamp into Compose MonotonicFrameClock
        KotlinBridge.shared.dispatchComposeFrame(targetTimestamp: targetTimestamp)
    }
    
    deinit {
        displayLink?.invalidate()
    }
}
```

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Compose Multiplatform iOS CAMetalLayer CADisplayLink V-Sync Cadence Lock

Compose Multiplatform iOS CAMetalLayer CADisplayLink V-Sync Cadence Lock defines an industrial-grade behavioral standard for mobile engineering. Aligns Kotlin Multiplatform / Compose Multiplatform iOS rendering loop passes strictly with CoreAnimation CADisplayLink ticks on ProMotion (120Hz) and variable refresh rate displays. Prevents thread contention, main thread stalls, and dropped frame pacing by synchronizing CAMetalLayer drawable presentation calls with NSRunLoopCommonModes ticks rather than unbounded Kotlin coroutine dispatchers. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Free-Running Coroutine Render Dispatches and Unsynchronized CAMetalLayer NextDrawable Calls Leading to ProMotion Jitter Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftFree-Running Coroutine Render Dispatches and Unsynchronized CAMetalLayer NextDrawable Calls Leading to ProMotion JitterCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS hook the Compose Multiplatform frame clock to a platform-native CADisplayLink attached to NSRunLoop.mainRunLoop in NSRunLoopCommonModes.

Engineers MUST ALWAYS hook the Compose Multiplatform frame clock to a platform-native CADisplayLink attached to NSRunLoop.mainRunLoop in NSRunLoopCommonModes.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST NOT trigger software-driven re-draws or metal render passes via unconstrained Kotlin Coroutine dispatchers (e.g., Dispatchers.Default or fixed-delay delay(16) loops).

Engineers MUST NOT trigger software-driven re-draws or metal render passes via unconstrained Kotlin Coroutine dispatchers (e.g., Dispatchers.Default or fixed-delay delay(16) loops).

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Code MUST set `CAFrameRateRange(minimum

Code MUST set `CAFrameRateRange(minimum: 60.0, preferred: 120.0, maximum: 120.0)` on CADisplayLink when targeting high refresh rate ProMotion devices to avoid adaptive rate down-stepping hitch.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Services MUST acquire CAMetalDrawable instances only during the CADisplayLink callback or within a strictly bounded draw pass with `presentsWithTransaction = false` and non-blocking timeout handling.

Services MUST acquire CAMetalDrawable instances only during the CADisplayLink callback or within a strictly bounded draw pass with `presentsWithTransaction = false` and non-blocking timeout handling.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Components NEVER block the main RunLoop waiting for GPU command buffers

Components NEVER block the main RunLoop waiting for GPU command buffers; frame submission must use `MTLCommandBuffer.addCompletedHandler` or double/triple buffering dispatch semaphores with a depth of 2 or 3.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is Compose Multiplatform iOS CAMetalLayer CADisplayLink V-Sync Cadence Lock critical for AI coding assistants?

Aligns Kotlin Multiplatform / Compose Multiplatform iOS rendering loop passes strictly with CoreAnimation CADisplayLink ticks on ProMotion (120Hz) and variable refresh rate displays. Prevents thread contention, main thread stalls, and dropped frame pacing by synchronizing CAMetalLayer drawable presentation calls with NSRunLoopCommonModes ticks rather than unbounded Kotlin coroutine dispatchers. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Free-Running Coroutine Render Dispatches and Unsynchronized CAMetalLayer NextDrawable Calls Leading to ProMotion Jitter) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

Related MOBILE Rules

View all rules →
1 Rule in Stack

Compose