walk

fun SurfaceModel.walk(resolver: ChildResolver, limit: Int = DEFAULT_WALK_LIMIT, maxDepth: Int = DEFAULT_MAX_DEPTH): List<Pair<Component, EvaluationScope>>

Every component instance reachable from SurfaceModel.root, depth first, each paired with the scope it renders in.

References that name a component the surface has not received are skipped rather than raised: the specification requires renderers to "handle missing references gracefully by rendering placeholders (progressive rendering)". A reference that revisits a component already on the current path is also skipped, so a cycle in the adjacency list ends the walk instead of hanging it — reporting that cycle is the validator's job, not the renderer's.

A component may be emitted more than once, and that is why the bounds exist. The adjacency list is a graph, not a tree: two containers may both reference the same child, and each reference is a separate rendering. Deduplicating by id would drop the second one, so the walk follows every path — which means n layers of components that each reference the same two children produce 2^n instances from 2n components. The cycle guard does not bound that, because none of those paths repeats an id.

limit and maxDepth do, by raising A2uiStateException rather than by truncating: a silently shortened walk is a wrong UI drawn without complaint, which is worse than a surface that refuses to draw. The traversal also keeps its own stack rather than recursing, so maxDepth is what bounds nesting rather than the call stack — which on Kotlin/Native is small enough to matter first.