search

suspend fun search(determinize: () -> State, legal: List<Move>, viewer: Actor, rolloutHorizon: Int, elapsedMillis: () -> Long, onSearchError: (Throwable) -> Unit = {}): SearchNode<Move>(source)

Runs the search and returns the populated root node (one child per element of legal, plus whatever the tree expanded into). determinize supplies one fresh determinized State per iteration; a thrown exception from it is treated as a free retry (does not consume an iteration) — the caller is expected to advance its own RNG before rethrowing, mirroring a failed-sample retry. elapsedMillis is polled against SearchBudget.maxMillis.

A suspend function: cancelling the calling coroutine (a player moves on, a screen leaves composition) stops the loop at the next iteration boundary via ensureActive instead of burning the full budget. onSearchError receives every determinization/iteration failure that isn't a cancellation — a rules bug that always throws is now visible instead of silently producing a near-empty root; the default is a no-op so existing callers that don't care still compile.

determinize, iterate and the rules callbacks are all caller-supplied, which is why the catches below are broad: a search shell whose job is to survive a bad sample cannot narrow to types it does not own. Nothing is swallowed — every non-cancellation failure goes to onSearchError, and CancellationException is rethrown first so cancellation still works.