ZOA Diagnostics Contracts
Define diagnostic checks and reports shared by Foundry tools.
Diagnostics Contracts supplies the interfaces and result types that diagnostic producers and viewers use to exchange findings. A package can describe a check without depending on the diagnostics window that displays it.
Use these contracts when adding a package-specific health check or probe. Keep the check close to the system it understands, and let the diagnostics tooling collect its results.
Declared ZOA dependencies (0)
Referenced by ZOA manifests (1)
How it works
Concepts
The namespace does not match the package name
The types live in ZOA.Nucleon.Debugging, not ZOA.Diagnostics.Contracts. That preserves source compatibility: the contributors that already shipped under com.zoa.nucleon keep resolving their existing using directive without a source change, and the assembly's rootNamespace is set to match.
External consumers that referenced the types through the old assembly name are covered by a [TypeForwardedTo] emitted from the nucleon runtime assembly, so the move is invisible on both sides.
How a contributor is found
Discovery is reflection-driven and editor-only, and it lives in com.zoa.nucleon rather than here. DebugContributorRegistry walks every assembly in the AppDomain for types carrying AutoRegisterDebugContributorAttribute, checks that each one implements IDebugContributor and is neither abstract nor an interface, and logs a warning and skips anything that fails those checks rather than throwing. An assembly that fails to load its full type list contributes whatever types it could resolve.
Results are sorted by the attribute's Order ascending and tie-broken by the type's full name, so the tab layout is stable regardless of assembly load order. The registry caches its result and invalidates the cache after every assembly reload.
DebugContributorBridge listens for play-mode transitions. On entering play mode it instantiates every discovered contributor and registers it with the debug UI controller; on exiting it clears the registrations it owns so the registry stays authoritative.
What an implementation owes the host
RegisterUI is called once at attach time and builds the contributor's surface into the supplied VisualElement. It must be idempotent under repeated attach, because the preview renderer can re-attach the same contributor to a new panel.
UpdateUI runs on the Workbench refresh tick. It should avoid allocation and early-exit when the underlying state has not changed, because it runs whether or not the tab is the one on screen.
TabName groups contributors. Two contributors returning the same TabName render into one tab, which is how several packages can contribute to a single subject area without coordinating.
Setup
Workflow
- 01
Reference the package
Add com.zoa.diagnostics.contracts to your package.json dependencies. It has no dependencies of its own, so the edge pulls nothing else into your graph.
- 02
Author the contributor in your own Editor directory
Implement IDebugContributor in your package's editor assembly, next to the system it inspects. The contributor then sits with the code it reports on, and ships and versions with it.
- 03
Decorate it
Add [AutoRegisterDebugContributor] with an Order that places your tab sensibly relative to the others. Contributors sharing a TabName merge into one tab.
- 04
Enter play mode
The bridge in com.zoa.nucleon discovers, instantiates and registers the contributor on entering play mode, and clears it on exit. There is nothing to wire by hand.
Reference
Key types
IDebugContributor
interface
The contract a package implements to publish a diagnostic tab into the Workbench. It has three members, all editor-facing.
- string TabName { get; }
- void RegisterUI(VisualElement root)
- void UpdateUI()
AutoRegisterDebugContributorAttribute
class
Marks a contributor for auto-registration at play-mode entry. Order gives stable layout; Label optionally overrides the tab placement name.
- AutoRegisterDebugContributorAttribute(int order = 0, string label = null)
- int Order { get; }
- string Label { get; }
Usage
Examples
using UnityEngine.UIElements;
using ZOA.Nucleon.Debugging;
namespace MyStudio.Ballistics.Editor.Diagnostics
{
// Order gives the tab a stable position: discovery sorts ascending
// and ties break on full type name, so layout never depends on
// assembly load order.
[AutoRegisterDebugContributor(order: 40, label: "Ballistics")]
public sealed class BallisticsDebugContributor : IDebugContributor
{
// Contributors sharing a TabName render into one tab.
public string TabName => "Combat";
private Label _lastShot;
private int _lastRenderedShotId = -1;
public void RegisterUI(VisualElement root)
{
// Must be idempotent: the preview renderer can re-attach
// this contributor to a fresh panel.
root.Clear();
root.Add(new Label("Ballistics"));
_lastShot = new Label("No shots this session.");
root.Add(_lastShot);
}
public void UpdateUI()
{
// Runs on the refresh tick whether or not this tab is visible,
// so early-exit when nothing has changed and allocate nothing.
var shotId = BallisticsProbe.LastShotId;
if (shotId == _lastRenderedShotId) return;
_lastRenderedShotId = shotId;
_lastShot.text = BallisticsProbe.DescribeLastShot();
}
}
}Integration
Behavior to account for
See also