This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: kmp-skiko-frame-budget-recomposition-guardrail

Compose Multiplatform Metal/Vulkan 8.33ms Frame Budget & Recomposition Guardrail

Enforces deterministic microtask thresholds and zero-allocation rendering paths across Kotlin Multiplatform Compose UI trees targeting Skiko (Metal on iOS, Vulkan on Android). Guarantees consistent 120Hz frame pacing (8.33ms budget) by eliminating recomposition loops, layout-pass allocations, and un-deferred state reads on mobile render pipelines.

Prohibited Anti-Pattern: Reading Unstable State in Composition Passes and Allocating Canvas Objects Inside Skiko Render Loops

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Compose Multiplatform Metal/Vulkan 8.33ms Frame Budget & Recomposition Guardrail - Enforces deterministic microtask thresholds and zero-allocation rendering paths across Kotlin Multiplatform Compose UI trees targeting Skiko (Metal on iOS, Vulkan on Android). Guarantees consistent 120Hz frame pacing (8.33ms budget) by eliminating recomposition loops, layout-pass allocations, and un-deferred state reads on mobile render pipelines.
globs: **/commonMain/**/*.kt, **/iosMain/**/*.kt, **/androidMain/**/*.kt
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/kmp-skiko-frame-budget-recomposition-guardrail"
---

# Compose Multiplatform Metal/Vulkan 8.33ms Frame Budget & Recomposition Guardrail

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Reading Unstable State in Composition Passes and Allocating Canvas Objects Inside Skiko Render Loops

## System Constraints
You are a Principal Mobile Architect enforcing ultra-low latency rendering budgets across Kotlin Multiplatform Compose (Skiko Metal/Vulkan backends). Your mission is to eliminate UI thread hitches on 120Hz displays by enforcing strict 8.33ms per-frame computation limits, deferring state reads to the draw/layout phase via lambda modifiers, mandating @Immutable/@Stable type contracts, and banning heap allocations within Custom Canvas draw scopes.

## Strict Guidelines
- Engineers MUST defer dynamic observable state reads directly to the Draw or Layout phase using Modifier.graphicsLayer { ... }, Modifier.drawWithCache { ... }, or Modifier.offset { ... } rather than accessing state directly in the Composable body.
- Engineers MUST annotate cross-module UI models and domain transfer objects with @Immutable or @Stable, or wrap unstable multiplatform primitives (such as standard Kotlin collections) into kotlinx.collections.immutable persistent structures.
- Engineers MUST NOT instantiate paint objects, paths, shaders, or create memory allocations inside DrawScope, Canvas, or Skiko render loops; all graphics primitives MUST be retained across frames via remember { Path() } or drawWithCache { ... }.
- Derived calculations involving multiple reactive states MUST be wrapped inside derivedStateOf with structural mutation boundaries to prevent cascading re-executions across the compositional subtree.
- Components NEVER execute blocking operations, synchronous disk/keychain access, or complex sequence processing inside Composable execution scopes; background offloading via produceState or LaunchedEffect(Dispatchers.Default) is mandatory.

## Bad vs Good Implementations
### Deferring High-Frequency Animation State Reads via graphicsLayer Lambda

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Directly reading observable state (scrollState.value) in the root Composable scope forces Compose Multiplatform to invalidate and execute the entire recomposition tree on every tick. This introduces severe micro-stutter and frame drops below 120 FPS under Metal/Vulkan Skiko pipelines.
```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.ScrollState

@Composable
fun ParallaxHeader(scrollState: ScrollState) {
    // ANTI-PATTERN: Reading scrollState.value directly during Composition triggers 
    // full-tree recomposition on every single sub-pixel gesture event.
    // On iOS (Metal) and Android (Vulkan), this starves Skiko's 8.33ms frame window.
    val offsetPx = scrollState.value * 0.5f
    Box(
        modifier = Modifier.offset(y = offsetPx.dp)
    )
}
```

#### Production Standard (Correct)
> **Standard**: By deferring the state read into the graphicsLayer execution block, the Composable skips the Composition and Measure phases entirely. The GPU transformation matrix is mutated directly during the draw phase, preserving the 8.33ms frame budget.
```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.ScrollState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer

@Composable
fun ParallaxHeader(scrollState: ScrollState) {
    // PRODUCTION FIX: Read state strictly within the graphicsLayer draw lambda.
    // Skips both Recomposition and Layout phases; directly updates the Skiko layer matrix
    // inside the 8.33ms Metal/Vulkan command buffer pipeline.
    Box(
        modifier = Modifier.graphicsLayer {
            translationY = scrollState.value * 0.5f
        }
    )
}
```

### Eliminating Per-Frame Path Allocation via drawWithCache in Skiko Pipelines

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Creating a new Path instance on every draw frame causes high heap allocation rates. The Kotlin/Native GC (on iOS) or ART GC (on Android) forces collections on the UI thread, causing frame times to spike far beyond the 8.33ms target.
```kotlin
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke

@Composable
fun WaveformVisualizer(amplitudes: List<Float>) {
    // ANTI-PATTERN: Instantiating Path objects and allocations per-frame inside onDraw.
    // Skiko Metal/Vulkan surfaces incur heavy GC pauses on the main thread.
    Canvas(modifier = Modifier.fillMaxSize()) {
        val path = Path()
        amplitudes.forEachIndexed { index, amp ->
            val x = index * (size.width / amplitudes.size)
            val y = size.height * (1f - amp)
            if (index == 0) path.moveTo(x, y) else path.lineTo(x, y)
        }
        drawPath(path = path, color = Color.Green, style = Stroke(width = 2f))
    }
}
```

#### Production Standard (Correct)
> **Standard**: Using drawWithCache and Path.reset() guarantees zero allocations per frame during continuous rendering. Immutable inputs prevent recompositions while maintaining deterministic rendering on 120Hz display pipelines.
```kotlin
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke
import kotlinx.collections.immutable.ImmutableList

@Composable
fun WaveformVisualizer(amplitudes: ImmutableList<Float>) {
    // PRODUCTION FIX: Pre-allocate and reuse the native path instance within drawWithCache.
    // Rebuilds path geometry only when dimensions or immutable inputs mutate.
    Modifier.fillMaxSize().then(
        Modifier.drawWithCache {
            val cachedPath = Path()
            onDrawBehind {
                cachedPath.reset()
                val step = size.width / amplitudes.size
                amplitudes.forEachIndexed { index, amp ->
                    val x = index * step
                    val y = size.height * (1f - amp)
                    if (index == 0) cachedPath.moveTo(x, y) else cachedPath.lineTo(x, y)
                }
                drawPath(path = cachedPath, color = Color.Green, style = Stroke(width = 2f))
            }
        }
    )
}
```

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Compose Multiplatform Metal/Vulkan 8.33ms Frame Budget & Recomposition Guardrail

Compose Multiplatform Metal/Vulkan 8.33ms Frame Budget & Recomposition Guardrail defines an industrial-grade behavioral standard for mobile engineering. Enforces deterministic microtask thresholds and zero-allocation rendering paths across Kotlin Multiplatform Compose UI trees targeting Skiko (Metal on iOS, Vulkan on Android). Guarantees consistent 120Hz frame pacing (8.33ms budget) by eliminating recomposition loops, layout-pass allocations, and un-deferred state reads on mobile render pipelines. 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: Reading Unstable State in Composition Passes and Allocating Canvas Objects Inside Skiko Render Loops 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 DriftReading Unstable State in Composition Passes and Allocating Canvas Objects Inside Skiko Render LoopsCritical
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 defer dynamic observable state reads directly to the Draw or Layout phase using Modifier.graphicsLayer { ... }, Modifier.drawWithCache { ... }, or Modifier.offset { ... } rather than accessing state directly in the Composable body.

Engineers MUST defer dynamic observable state reads directly to the Draw or Layout phase using Modifier.graphicsLayer { ... }, Modifier.drawWithCache { ... }, or Modifier.offset { ... } rather than accessing state directly in the Composable body.

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 annotate cross-module UI models and domain transfer objects with @Immutable or @Stable, or wrap unstable multiplatform primitives (such as standard Kotlin collections) into kotlinx.collections.immutable persistent structures.

Engineers MUST annotate cross-module UI models and domain transfer objects with @Immutable or @Stable, or wrap unstable multiplatform primitives (such as standard Kotlin collections) into kotlinx.collections.immutable persistent structures.

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

Engineers MUST NOT instantiate paint objects, paths, shaders, or create memory allocations inside DrawScope, Canvas, or Skiko render loops

Engineers MUST NOT instantiate paint objects, paths, shaders, or create memory allocations inside DrawScope, Canvas, or Skiko render loops; all graphics primitives MUST be retained across frames via remember { Path() } or drawWithCache { ... }.

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

Derived calculations involving multiple reactive states MUST be wrapped inside derivedStateOf with structural mutation boundaries to prevent cascading re-executions across the compositional subtree.

Derived calculations involving multiple reactive states MUST be wrapped inside derivedStateOf with structural mutation boundaries to prevent cascading re-executions across the compositional subtree.

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 execute blocking operations, synchronous disk/keychain access, or complex sequence processing inside Composable execution scopes

Components NEVER execute blocking operations, synchronous disk/keychain access, or complex sequence processing inside Composable execution scopes; background offloading via produceState or LaunchedEffect(Dispatchers.Default) is mandatory.

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 Metal/Vulkan 8.33ms Frame Budget & Recomposition Guardrail critical for AI coding assistants?

Enforces deterministic microtask thresholds and zero-allocation rendering paths across Kotlin Multiplatform Compose UI trees targeting Skiko (Metal on iOS, Vulkan on Android). Guarantees consistent 120Hz frame pacing (8.33ms budget) by eliminating recomposition loops, layout-pass allocations, and un-deferred state reads on mobile render pipelines. 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 (Reading Unstable State in Composition Passes and Allocating Canvas Objects Inside Skiko Render Loops) 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