Quick answer: Assembly definitions must form a DAG. Break A↔B cycles by extracting shared interfaces/events into a third assembly both depend on.

Adding an asmdef reference fails: “Cyclic assembly references detected.” Gameplay references UI, UI references Gameplay — a cycle the compiler can’t resolve.

Why Cycles Are Forbidden

Each asmdef compiles to a separate DLL. To compile A, the compiler needs B already built; to build B it needs A — impossible. Dependencies must be acyclic.

Extract a Shared Contract Assembly

Create a third assembly, e.g. Game.Contracts, holding only interfaces, events, and data types both sides need:

Game.Contracts        (interfaces, ScriptableObject events — no deps)
  ↑            ↑
Game.Gameplay   Game.UI

Gameplay and UI both reference Contracts. They communicate through interfaces/events — no direct dependency between them. Cycle broken.

Event-Based Decoupling

ScriptableObject-based events in the Contracts assembly let Gameplay raise events that UI listens to, with neither knowing the other’s concrete types.

Or Merge

If two assemblies are genuinely tightly coupled and splitting them adds no value, merge them into one. Asmdefs are for genuine module boundaries, not arbitrary folders.

Verifying

Project compiles. The dependency graph is a DAG (visualize via the asmdef inspector’s reference list). Compile times improve as modules are isolated.

“Asmdefs must be acyclic. A shared contracts assembly is the standard cycle-breaker.”

Design the assembly graph top-down: Contracts at the bottom, feature modules above, app/bootstrap at the top. Cycles become impossible by construction.