Toolingcom.zoa.workbench · v0.4.0

ZOA Workbench

The editor shell every ZOA package hangs its authoring surface on.

Workbench is one EditorWindow that hosts the authoring tools of the entire stack. The shell itself is small: it lays out a tab rail, a capability strip, a content host and a progress overlay, and discovers everything it shows through TypeCache at load. A package that wants a page in the Workbench writes one class, tags it with an attribute, and appears. Workbench never references that package, and the package never has to be listed anywhere.

The dependency arrow only ever points outward. com.zoa.workbench depends on exactly two things, com.zoa.foundation and com.zoa.ui.theming, yet the window it opens contains pages contributed by inventory, armory, nucleon, content, AI, rendering and everything else installed. Adding a package adds tabs. Removing one removes them, with no dangling menu entries and no compile errors.

Alongside the shell, Workbench owns the pieces that make authoring surfaces feel like one product rather than forty separate windows: the wizard primitives every wizard is built from, the authoring services that wizards delegate to so UI and asset emission never drift apart, the project configurator that lays down layers, tags and the collision matrix, and the validators that gate a build.

How it works

Concepts

Modules are discovered at load

A Workbench page is a class implementing IZOAWorkbenchModule, decorated with ZOAWorkbenchModuleAttribute. On every rebuild the shell walks TypeCache.GetTypesDerivedFrom<IZOAWorkbenchModule>(), instantiates each concrete type with its parameterless constructor, and sorts the result by Order then Title. A module that throws during construction is logged and skipped rather than taking the window down with it.

The interface is thin. Id, Title, Subtitle and Order describe the tab; SearchText is the blob the shell's search field matches against, so a module can be found by words that never appear in its title. Build(ZOAWorkbenchContext) returns the page's VisualElement, and Refresh(context, root) is called when the shell wants the already-built view updated in place.

FoundryModuleManifest is the read-only view of the same discovery, useful when you want to enumerate what is installed without opening the window. It also derives a package hint from each module's assembly name, mapping ZOA.Equipment.Unity.Editor back to com.zoa.equipment, so diagnostics can attribute a page to the package that shipped it.

Workflow hubs group modules without coupling them

Setting WorkflowId on the module attribute attaches a module to one of the canonical hubs: workflow.character, workflow.ai, workflow.items-economy, workflow.weapons-armory, workflow.environment, workflow.rendering, or workflow.system-configuration. The hub surfaces its children from a capability strip; the module still exists as itself and behaves exactly as it did before.

Leaving WorkflowId null, which is the default, keeps the module as a top-level tab. The grouping is additive by construction, so a package that has never heard of workflow hubs is not broken by their existence.

Above the hubs, the shell builds subsystems: Start Here, System & Configuration, Character, AI, Items & Economy, Weapons & Armory, Environment, Rendering. A subsystem is a predicate over discovered module ids, and a subsystem with no matching modules is not shown at all. That is how the window shrinks honestly on a partial install.

Capabilities: three ways to put a tool on a page

Most modules present a strip of related tools rather than one screen, and CapabilityStripBuilder is the shared implementation of that pattern. Each entry has an id, a label, a tooltip, and one of three modes.

AddEmbedded takes the full type name of an EditorWindow as a string and asks WorkbenchCapabilityHost to instantiate it and mount its root visual element inside the Workbench. Because the type is named by string rather than referenced, a module can embed a window from a package it does not compile against. AddNative takes a Func<ZOAWorkbenchContext, VisualElement> and builds the surface directly, which is the right choice for anything authored for the Workbench in the first place. AddFallback takes a menu path and an action label for capabilities that cannot be embedded, and the strip renders a button that runs EditorApplication.ExecuteMenuItem on that path.

Every entry can also declare an Owner, the canonical service or window responsible for that capability. Cards route into the owner rather than reimplementing it, which is the rule that stopped the stack accumulating four slightly different player builders.

Wizards are UI over services

Every Workbench wizard splits in two. The EditorWindow owns layout, step order, validation display and nothing else. The asset emission lives behind an interface in ZOA.Workbench.Editor.Services: IWorkbenchConfigAuthoringService, IWorkbenchPlayerAuthoringService, IWorkbenchSceneAuthoringService, IWorkbenchSceneValidationService.

A Workbench card and a wizard invoked from the menu therefore emit identical assets, because both call the same service with the same request object. The services are also testable without a window, which the package's WizardServicePurityTests assert.

IWorkbenchPlayerAuthoringService carries the split one step further: BuildPlayerDefinitions and BuildPlayerPrefab are separately callable, so a wizard can offer independent step controls and a caller that already has definitions can skip straight to the prefab.

Scene provisioning is delegated to whoever owns the scene

Workbench can scaffold a plain gameplay scene itself, but a package with a richer pipeline can take over. IWorkbenchSceneProvisioningProvider declares an Id, a Priority, a CanProvision(request) predicate and TryGenerateScene. Providers register themselves into the static WorkbenchSceneProvisioningRegistry, which sorts by descending priority and runs the first provider that accepts the request.

This is how the demo package's generated showcase scenes end up behind the Workbench's Create Game Scene action rather than living in a separate menu. Workbench still handles final save and open targeting; the provider owns everything about what goes in the scene.

The shell knows what is in your open scenes

IWorkbenchSceneContextService publishes a SceneSnapshot describing the open scene set, rebuilt on hierarchy change behind a debounce. Current is never null, falling back to SceneSnapshot.Empty before the first rebuild, and SnapshotChanged hands subscribers the new snapshot directly so they never race a concurrent rebuild by reading Current.

The snapshot is filled by ISceneContextProvider implementations, one per package, discovered the same way modules are. A provider declares a Category, an Order, and a Scan over the open scenes that yields SceneEntry values. Canonical categories live on SceneEntryCategory: players, weapon-racks, installers, interactables, inventories, active-content. A provider may invent its own key and the Workbench renders it under an Other bucket.

SceneEntry carries a display name, an optional GameObject target, a list of key/value facts rendered as a compact grid, and an optional primary action with a label. That is enough for a hub card to say what is in the scene and offer the one thing you would want to do with it. SceneContextStatus distinguishes Unknown, Pending, Ready and NoScene, so a hub can show a scanning hint instead of falsely claiming the scene is empty.

Progress has one route out

IFoundryProgressReporter is a three-message contract: Begin(headline, canCancel), Report(fraction, detail) and End, plus an IsCancelled flag. A fraction below zero means indeterminate, so the same call site drives a determinate bar or a spinner.

FoundryProgressRouter covers the code that predates the contract. It intercepts progress calls and routes them into the Workbench's overlay when one is registered, falling back to Unity's modal progress popup when a generator is invoked from the menu with no window open. Long generators therefore report into a cancellable overlay inside the shell without knowing the shell exists.

In the editor

Screens

Screenshot pending

/screenshots/workbench-shell.png

Full window with the subsystem rail on the left, the module tab strip across the top, a capability strip beneath it, and a populated page in the content host. Status line visible at the bottom showing the GameDatabase name and definition count.

The Workbench shell

Screenshot pending

/screenshots/workbench-start-here.png

The start-here module in Demo workflow mode, showing project state checks and the full system builder with its seven phases listed.

Start Here

Screenshot pending

/screenshots/workbench-wizard-shell.png

FoundryWizardShell three-column layout: numbered step rail on the left with one completed and one active step, main content in the middle, preview dock on the right with a key/value summary, and the issue tray expanded at the bottom showing a warning with a quick-fix button.

A wizard in the shell

Screenshot pending

/screenshots/workbench-capability-strip.png

A module page whose strip has five or six capabilities, one selected, with an embedded EditorWindow from a different package mounted inside the Workbench rather than floating free.

Capability strip

Screenshot pending

/screenshots/workbench-validation.png

The Validation module listing clean-install checks with pass/fail state, evidence text, and Fix buttons on the failures.

Validation dashboard

Setup

Workflow

  1. 01

    Open the shell

    Tools > ZOA > Start Here on a fresh project, or Tools > ZOA > Advanced > Workbench > Open once you know your way round. The top-level Tools > ZOA menu carries workflow entries that deep-link straight into a hub: Character, Items & Economy, Weapons & Armory, AI, Environment, Rendering. Utilities live under Tools > ZOA > Advanced, which keeps the first menu down to the workflow entries.

  2. 02

    Let it bootstrap

    Opening the window runs a staged bootstrap across several delayCall frames: build the definition registry from the active GameDatabase, discover modules, then resolve whatever route was requested. If no GameDatabase is found the shell reports degraded mode and keeps going, so a project that has not been configured yet can still reach Start Here.

  3. 03

    Configure the project

    Tools > ZOA > Advanced > Validate > Project > Validate Project Settings reports missing layers, tags and collision-matrix entries. Tools > ZOA > Advanced > Build > Project > Apply Required Project Settings writes them. Both go through ZOAProjectConfigurator, and both pick up whatever downstream packages registered at load.

  4. 04

    Build the baseline

    From Start Here, run the full system builder. It walks core configuration, inventory definitions, equipment definitions, the player prefab, UI and prefabs, scene scaffolding, and a final GameDatabase rebuild. Individual steps are also available as wizards: Tools > ZOA > Advanced > Build > Quick Start > Initialize Configuration, Create Default Player, Create Game Scene.

  5. 05

    Author a scene

    Tools > ZOA > Advanced > Build > World > Create Gameplay Scene... opens the unified scene wizard with three modes. From Scratch creates a new scene and scaffolds every essential. Augment Existing adds only what the open scene is missing. Validate is a read-only readiness checklist with a one-click Augment to Fix. If a package has registered a provisioning provider that accepts the request, it generates the scene instead.

  6. 06

    Contribute your own page

    Add a class implementing IZOAWorkbenchModule in your package's editor assembly, tag it with ZOAWorkbenchModuleAttribute, reference ZOA.Workbench.Unity.Editor, and rebuild. Give it a WorkflowId if it belongs to an existing hub. Nothing else registers it.

  7. 07

    Gate the build

    Tools > ZOA > Advanced > Validate > Run Asmdef Governance Check, Run Release Gates, and Clean Install cover assembly boundaries, the release checklist, and whether a fresh install is correctly wired. Each has a batch-mode entry point, so the same checks run in CI through CIBuildPipeline.

Surface

Key types

IZOAWorkbenchModule

interface

The contract a package implements to contribute a page. Discovered via TypeCache; needs a public parameterless constructor.

  • string Id { get; }
  • string Title { get; }
  • string Subtitle { get; }
  • int Order { get; }
  • string SearchText { get; }
  • VisualElement Build(ZOAWorkbenchContext context)
  • void Refresh(ZOAWorkbenchContext context, VisualElement root)

ZOAWorkbenchModuleAttribute

class

Declares a class as a Workbench module and optionally attaches it to a workflow hub. Class-only, not inherited, single use.

  • ZOAWorkbenchModuleAttribute(string id, string title, int order)
  • string WorkflowId { get; set; }

ZOAWorkbenchContext

class

Everything a module is handed at Build time: the host window, the database and registry, navigation callbacks, the current mode, and the live scene snapshot service.

  • EditorWindow HostWindow { get; set; }
  • GameDatabase GameDatabase { get; set; }
  • IDefinitionRegistry DefinitionRegistry { get; set; }
  • Action<string> SetStatus { get; set; }
  • Action<string> SelectModule { get; set; }
  • Action<string, string> Navigate { get; set; }
  • Action<string> SelectCapability { get; set; }
  • bool IsExpertMode { get; set; }
  • WorkbenchWorkflowMode WorkflowMode { get; set; }
  • IWorkbenchSceneContextService SceneContext { get; set; }

FoundryModuleManifest

class

Static registry of discovered modules with metadata. Use it to enumerate what is installed without opening the window.

  • static IReadOnlyList<ModuleEntry> All { get; }
  • static void Refresh()
  • static ModuleEntry FindById(string id)
  • static int Count { get; }

CapabilityStripBuilder

class

Fluent builder for the capability strip. Three ways to attach a tool: an embedded EditorWindow named by string, a native VisualElement builder, or a menu-path fallback.

  • CapabilityStripBuilder(string moduleId)
  • CapabilityStripBuilder AddEmbedded(string id, string label, string tooltip, string windowTypeName, WorkbenchCapabilityRenderMode renderMode, string owner = null)
  • CapabilityStripBuilder AddNative(string id, string label, string tooltip, Func<ZOAWorkbenchContext, VisualElement> nativeBuilder, string owner = null)
  • CapabilityStripBuilder AddFallback(string id, string label, string tooltip, string fallbackMenuPath, string fallbackActionLabel, string owner = null)
  • VisualElement Build(ZOAWorkbenchContext context)
  • void SelectCapability(string id, ZOAWorkbenchContext context)
  • string SelectedId { get; }

WorkbenchCapabilityHost

class

Resolves an EditorWindow type by name, instantiates it, and mounts its view inside the Workbench. Disposes embedded handles on play-mode change and before assembly reload.

  • static bool TryCreateEmbeddedCapability(string windowTypeName, WorkbenchCapabilityRenderMode renderMode, out VisualElement view, out IDisposable handle, out string error)
  • static bool TryMountNativeCapability(...)
  • static void DisposeMountedCapability(VisualElement owner)

WorkbenchCapabilityRenderMode

enum

UiToolkit or Imgui. Tells the host how to mount an embedded window's content.

FoundryWizardShell

class

The three-column wizard layout every Foundry wizard is built in: step rail, main content, preview dock, with an issue tray beneath. A UxmlElement, so it can also be declared in UXML.

  • FoundryWizardShell AddStep(string title, string description, Func<VisualElement> contentBuilder, Func<List<FoundryIssue>> validator = null)
  • void Begin()
  • void GoToStep(int index)
  • List<FoundryIssue> RunStepValidation()
  • FoundryIssueTray IssueTray { get; }
  • FoundryPreviewDock PreviewDock { get; }
  • FoundryStepRail StepRail { get; }
  • IFoundryProgressReporter Progress { get; }
  • Task ShowProgressAsync(...)
  • event Action Completed
  • event Action Cancelled
  • event Action<int> StepChanged

FoundryIssueTray

class

Collapsible validation tray. Issues carry an optional quick fix, and the tray offers a Fix All button that runs every available one.

  • void AddIssue(FoundryIssue issue)
  • void AddIssues(IEnumerable<FoundryIssue> issues)
  • void Clear()
  • IReadOnlyList<FoundryIssue> Issues { get; }
  • bool HasErrors { get; }
  • bool HasIssues { get; }

FoundryIssue

class

One validation result. The static factories are the normal way to build them, and every mechanical remedy should be attached as a quick fix rather than only described.

  • static FoundryIssue Error(string message, Action quickFix = null, string fixLabel = "Fix")
  • static FoundryIssue Warning(string message, Action quickFix = null, string fixLabel = "Fix")
  • static FoundryIssue Info(string message)
  • FoundryIssueSeverity Severity { get; }
  • string Message { get; }
  • Action QuickFix { get; }
  • string QuickFixLabel { get; }

FoundryStepRail

class

Vertical numbered step indicator with click-to-navigate for completed and current steps. Arrow-key navigable.

  • void AddStep(string title, string description = null)
  • void SetActiveStep(int index)
  • void MarkCompleted(int index)
  • event Action<int> StepClicked

FoundryPreviewDock

class

Collapsible right-hand panel for live previews and summaries. Takes custom content, a text summary, or a key/value grid.

  • void SetTitle(string title)
  • void SetContent(VisualElement content)
  • void SetSummary(string title, string body)
  • void SetKeyValueSummary(string title, params (string key, string value)[] entries)
  • void ShowPlaceholder(string message = null)
  • void SetCollapsed(bool collapsed)

IFoundryProgressReporter

interface

UI-neutral progress contract handed to long-running work. A negative fraction means indeterminate; tests substitute the no-op implementation.

  • void Begin(string headline, bool canCancel = false)
  • void Report(float fraction, string detail = null)
  • void End()
  • bool IsCancelled { get; }

FoundryProgressRouter

class

Routes legacy editor progress calls into the active Workbench overlay when one is registered, and to Unity's modal popup when the caller is running standalone from a menu.

  • static void RegisterReporter(IFoundryProgressReporter reporter)
  • static void UnregisterReporter(IFoundryProgressReporter reporter)
  • static void DisplayProgressBar(string title, string info, float progress)
  • static bool DisplayCancelableProgressBar(string title, string info, float progress)
  • static void ClearProgressBar()
  • static bool HasActiveWorkbenchReporter { get; }

IWorkbenchConfigAuthoringService

service

Creates the core configuration assets: GameDatabase, NetworkSettings, SaveBackendSettings. Skips what already exists and populates the database.

  • WorkbenchConfigAuthoringResult InitializeConfiguration(WorkbenchConfigAuthoringRequest request)
  • bool AllConfigurationAssetsExist()
  • string GetSaveBackendSettingsPath()

IWorkbenchPlayerAuthoringService

service

Builds the default-player definition bundle and the player prefab as two independently callable operations.

  • WorkbenchPlayerDefinitionsResult BuildPlayerDefinitions(WorkbenchPlayerDefinitionsRequest request)
  • WorkbenchPlayerPrefabResult BuildPlayerPrefab(WorkbenchPlayerPrefabRequest request)

IWorkbenchSceneAuthoringService

service

Creates and scaffolds a gameplay scene: environment essentials, manager hierarchy, then save to disk.

  • WorkbenchSceneAuthoringResult BuildScene(WorkbenchSceneAuthoringRequest request)

IWorkbenchSceneValidationService

service

The read-only inverse of the authoring service. Reports which scene essentials are present, and never mutates the scene or the asset database.

  • WorkbenchSceneValidationResult ValidateActiveScene()

IWorkbenchSceneProvisioningProvider

interface

Extension point letting a package take over scene generation with its own pipeline. Highest priority provider that accepts the request wins.

  • string Id { get; }
  • int Priority { get; }
  • bool CanProvision(WorkbenchSceneAuthoringRequest request)
  • bool TryGenerateScene(WorkbenchSceneAuthoringRequest request, out string error)

WorkbenchSceneProvisioningRegistry

class

Static, lock-guarded provider list sorted by descending priority. Duplicate provider types are ignored on register.

  • static void Register(IWorkbenchSceneProvisioningProvider provider)
  • static void Unregister<TProvider>()
  • static bool TryGenerateScene(WorkbenchSceneAuthoringRequest request, out string providerId, out string error)

IWorkbenchSceneContextService

service

Live view of the open scene set. Read Current for a snapshot, subscribe to SnapshotChanged to stay current without polling.

  • SceneSnapshot Current { get; }
  • event Action<SceneSnapshot> SnapshotChanged
  • void RequestImmediateRebuild()
  • void RequestRebuild()

ISceneContextProvider

interface

Per-package scene scanner. Needs a public parameterless constructor and must be allocation-aware, since Scan can run several times a second during hierarchy churn.

  • string Category { get; }
  • int Order { get; }
  • IEnumerable<SceneEntry> Scan(IReadOnlyList<Scene> openScenes)

SceneSnapshot

class

Immutable result of one rebuild: status, active and open scene names, the entries, and the capture time.

  • static readonly SceneSnapshot Empty
  • SceneContextStatus Status { get; }
  • string ActiveSceneName { get; }
  • string ActiveScenePath { get; }
  • IReadOnlyList<string> OpenSceneNames { get; }
  • IReadOnlyList<SceneEntry> Entries { get; }
  • DateTime CapturedUtc { get; }
  • IEnumerable<SceneEntry> EntriesInCategory(string category)
  • int CountInCategory(string category)

SceneEntry

class

One thing found in the scene: a category, a stable id, a display name, an optional GameObject, a fact grid, and optional ping and primary actions.

  • string Category { get; }
  • string Id { get; }
  • string DisplayName { get; }
  • GameObject Target { get; }
  • IReadOnlyList<SceneEntryFact> Facts { get; }
  • Action PingAction { get; }
  • Action PrimaryAction { get; }
  • string PrimaryActionLabel { get; }

SceneEntryCategory

class

Canonical category keys as string constants rather than an enum, so downstream packages can add categories without touching this assembly.

  • const string Players = "players"
  • const string WeaponRacks = "weapon-racks"
  • const string Installers = "installers"
  • const string Interactables = "interactables"
  • const string Inventories = "inventories"
  • const string ActiveContent = "active-content"

SceneContextStatus

enum

Unknown, Pending, Ready, NoScene. Lets a hub tell an empty scene apart from one that has not been scanned yet.

WorkbenchWorkflowMode

enum

Demo or Production. Demo routes new users into Start Here and biases generated assets toward evaluation content; Production is the clean authoring path.

IFeatureCompletenessValidator

interface

A per-package completeness check surfaced on the Workbench dashboard. Returns FoundryIssue values, so every mechanical remedy arrives as a quick fix.

  • string ValidatorId { get; }
  • string DisplayName { get; }
  • string SystemArea { get; }
  • List<FoundryIssue> Validate()

FeatureCompletenessService

class

Discovers and runs every validator with exception isolation, so one broken validator becomes its own error row rather than killing the sweep.

  • static List<IFeatureCompletenessValidator> DiscoverValidators()
  • static List<FeatureCompletenessResult> RunAll()

ZOAProjectConfigurator

class

Owns the project-level settings ZOA requires: layers, tags and the physics collision matrix. Other packages register their own requirements from an InitializeOnLoadMethod rather than editing this list.

  • static ConfigResult Apply()
  • static ValidationReport Validate()
  • static void RegisterTags(params string[] tags)
  • static void RegisterLayers(params string[] layers)
  • static void RegisterIgnoredCollisionPairs(params (string LayerA, string LayerB)[] pairs)
  • static readonly string[] RequiredLayers
  • static readonly string[] RequiredTags

FoundrySystemBuilder

class

The one-click full-stack builder behind Start Here. Runs seven phases and returns a BuildResult listing created assets, warnings, and the emitted prefabs and scene path.

  • static readonly BuildPhase[] Phases
  • static bool IsCleanInstall()
  • class BuildResult { bool Success; int PhasesCompleted; List<string> CreatedAssets; List<string> Warnings; GameObject PlayerPrefab; string ScenePath; }

ZOAEditorMenu

class

The canonical menu contract. Every ZOA menu path is composed from these constants, and the menu-hierarchy tests assert against IsCanonicalPath.

  • const string Root = "Tools/ZOA"
  • const string StartHere, Player, AI, ItemsEconomy, Weapons, Environment, Rendering, Advanced
  • const string Workbench, Define, Build, Generate, Validate, Maintain, Help
  • static bool IsCanonicalPath(string menuPath)
  • static bool IsWorkbenchShortcutPath(string menuPath)

Usage

Examples

Contributing a Workbench page from your packagecsharp
using UnityEngine.UIElements;
using ZOA.Workbench.Unity.Editor.Utilities;
using ZOA.Workbench.Unity.Editor.Workbench;

namespace MyStudio.Ballistics.Editor
{
    [ZOAWorkbenchModule("ballistics", "Ballistics", 220,
        WorkflowId = "workflow.weapons-armory")]
    public sealed class BallisticsWorkbenchModule : IZOAWorkbenchModule
    {
        private CapabilityStripBuilder _strip;

        public string Id => "ballistics";
        public string Title => "Ballistics";
        public string Subtitle => "Tune drag curves and penetration tables.";
        public int Order => 220;

        // The shell matches this blob, so list the words an author would
        // actually type: none of them have to appear in the title.
        public string SearchText => "ballistics drag penetration muzzle velocity falloff";

        public VisualElement Build(ZOAWorkbenchContext context)
        {
            _strip = new CapabilityStripBuilder("ballistics")
                .AddNative(
                    "drag-curves",
                    "Drag Curves",
                    "Edit per-calibre drag curves.",
                    BuildDragCurveEditor)
                .AddEmbedded(
                    "penetration",
                    "Penetration Tables",
                    "Author material penetration tables.",
                    "MyStudio.Ballistics.Editor.PenetrationTableWindow",
                    WorkbenchCapabilityRenderMode.UiToolkit);

            return _strip.Build(context);
        }

        public void Refresh(ZOAWorkbenchContext context, VisualElement root)
        {
            if (_strip != null && !string.IsNullOrEmpty(_strip.SelectedId))
                _strip.SelectCapability(_strip.SelectedId, context);
        }

        private VisualElement BuildDragCurveEditor(ZOAWorkbenchContext context)
        {
            var root = new VisualElement();
            root.Add(new Label($"Definitions loaded: {context.GameDatabase?.Count ?? 0}"));
            return root;
        }
    }
}
AddEmbedded names the window by string, so the strip can host a window from a package it does not compile against.
A wizard built on the shell primitivescsharp
using System.Collections.Generic;
using UnityEditor;
using UnityEngine.UIElements;
using ZOA.Workbench.Unity.Editor.Primitives;

public sealed class DragCurveWizard : EditorWindow
{
    private FoundryWizardShell _shell;

    private void CreateGUI()
    {
        _shell = new FoundryWizardShell();

        _shell.AddStep("Identity", "Name and calibre",
            contentBuilder: BuildIdentityStep,
            validator: ValidateIdentity);

        _shell.AddStep("Curve", "Sample the drag curve",
            contentBuilder: BuildCurveStep);

        _shell.Completed += Emit;
        _shell.Begin();

        rootVisualElement.Add(_shell);
    }

    private List<FoundryIssue> ValidateIdentity()
    {
        var issues = new List<FoundryIssue>();

        if (string.IsNullOrWhiteSpace(_calibre))
        {
            // Every mechanical remedy ships as a quick fix, so the tray's
            // Fix All button can resolve it without the author leaving the step.
            issues.Add(FoundryIssue.Error(
                "Calibre is required.",
                quickFix: () => _calibre = "9x19",
                fixLabel: "Use 9x19"));
        }

        return issues;
    }

    private void Emit()
    {
        // Long work reports through the shell's overlay, which is cancellable
        // and lives inside the Workbench when the wizard is hosted there.
        var progress = _shell.Progress;
        progress.Begin("Emitting drag curves", canCancel: true);
        for (int i = 0; i < _curves.Count; i++)
        {
            if (progress.IsCancelled) break;
            progress.Report(i / (float)_curves.Count, _curves[i].Name);
            WriteCurveAsset(_curves[i]);
        }
        progress.End();
        AssetDatabase.SaveAssets();
    }

    private string _calibre;
    private readonly List<CurveDraft> _curves = new();
    private VisualElement BuildIdentityStep() => new VisualElement();
    private VisualElement BuildCurveStep() => new VisualElement();
    private void WriteCurveAsset(CurveDraft draft) { }

    private sealed class CurveDraft { public string Name; }
}
Taking over scene generationcsharp
using UnityEditor;
using ZOA.Workbench.Editor.Services;

[InitializeOnLoad]
internal static class ArenaSceneProvisioningBootstrap
{
    static ArenaSceneProvisioningBootstrap()
    {
        WorkbenchSceneProvisioningRegistry.Register(new ArenaSceneProvisioningProvider());
    }
}

internal sealed class ArenaSceneProvisioningProvider : IWorkbenchSceneProvisioningProvider
{
    public string Id => "mystudio.arena";

    // Providers are sorted by descending priority; the first one whose
    // CanProvision returns true wins, so a higher number pre-empts the
    // generic Workbench scaffold.
    public int Priority => 100;

    public bool CanProvision(WorkbenchSceneAuthoringRequest request) =>
        request != null;

    public bool TryGenerateScene(WorkbenchSceneAuthoringRequest request, out string error)
    {
        error = null;
        // Build the scene with your own pipeline and leave it active.
        // Workbench handles save and open targeting from here.
        return true;
    }
}
Publishing scene entries into the hubscsharp
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using ZOA.Workbench.Editor.Services.SceneContext;

public sealed class TurretSceneContextProvider : ISceneContextProvider
{
    // Bespoke keys are legal; the Workbench renders unknown categories
    // under an "Other" bucket rather than dropping them.
    public string Category => "turrets";

    public int Order => 50;

    public IEnumerable<SceneEntry> Scan(IReadOnlyList<Scene> openScenes)
    {
        var entries = new List<SceneEntry>();

        foreach (var scene in openScenes)
        {
            if (!scene.isLoaded) continue;

            foreach (var root in scene.GetRootGameObjects())
            {
                foreach (var turret in root.GetComponentsInChildren<Turret>(true))
                {
                    entries.Add(new SceneEntry(
                        category: Category,
                        id: turret.GetInstanceID().ToString(),
                        displayName: turret.name,
                        target: turret.gameObject,
                        facts: new[]
                        {
                            new SceneEntryFact("Calibre", turret.Calibre),
                            new SceneEntryFact("Arc", $"{turret.ArcDegrees}°"),
                        }));
                }
            }
        }

        return entries;
    }
}
Scan can be called several times a second during hierarchy churn, so keep it allocation-aware and cheap.

Tooling

Editor tools

Platform Workbench

Tools > ZOA > Advanced > Workbench > Open

The shell itself. Subsystem rail, module tabs, capability strips, search, an expert/beginner experience slider, and three chrome densities.

Start Here

Tools > ZOA > Start Here

Switches the shell into Demo workflow mode, clears expert mode, and lands on the start-here module: project state, setup checks, and the one-click full system builder.

Workflow deep links

Tools > ZOA > Character, Items & Economy, Weapons & Armory, AI, Environment, Rendering

Top-level menu entries that open the Workbench directly on a workflow hub. Mirrored under Tools > ZOA > Advanced > Workbench > Workflows, which additionally carries System & Configuration.

Create Gameplay Scene

Tools > ZOA > Advanced > Build > World > Create Gameplay Scene...

Unified scene wizard with From Scratch, Augment Existing, and Validate modes. Delegates to the scene authoring and validation services, and yields to a registered provisioning provider when one accepts the request.

Initialize Configuration

Tools > ZOA > Advanced > Build > Quick Start > Initialize Configuration

Creates GameDatabase, NetworkSettings and SaveBackendSettings, skipping assets that already exist, then populates the database.

Create Default Player

Tools > ZOA > Advanced > Build > Quick Start > Create Default Player

Demo-mode player wizard. Emits the default definition bundle and a wired player prefab, with optional squad presentation for top-down profiles.

Create Game Scene

Tools > ZOA > Advanced > Build > Quick Start > Create Game Scene

Scaffolds an evaluation scene with floor, lighting, camera, spawn point, save system and GameDatabase loader.

Apply Required Project Settings

Tools > ZOA > Advanced > Build > Project > Apply Required Project Settings

Writes the layers, tags and ignored collision pairs ZOA requires, including anything downstream packages registered through ZOAProjectConfigurator.

Validate Project Settings

Tools > ZOA > Advanced > Validate > Project > Validate Project Settings

Reports missing layers, tags and collision ignores without changing anything.

Run Asmdef Governance Check

Tools > ZOA > Advanced > Validate > Run Asmdef Governance Check

Scans every assembly definition under Packages for runtime/editor/test split consistency, adapter isolation, legacy hard references in core assemblies, and forbidden cross-boundary references.

Run Release Gates

Tools > ZOA > Advanced > Validate > Run Release Gates

Runs the full release checklist and reports pass or fail per gate with a P0/P1 priority. Also available headless as ReleaseGateValidator.RunCI.

Clean Install Validation

Tools > ZOA > Advanced > Validate > Clean Install

Checks that a fresh install is correctly wired: GameDatabase present and populated, network and save settings, player prefab, inventory definitions, equipment loadout, weapon definitions and operation profiles. Failed checks carry a fix action.

Theme Studio

Tools > ZOA > Advanced > Define > UI > Themes > Theme Studio

Select, preview, duplicate and edit the canonical project-wide UI theme.

Create Genre Bundle

Tools > ZOA > Advanced > Generate > Content > Create Genre Bundle…

Authors a GenreBundleDefinition through Identity, Content, Look and Review steps, writing the asset through a SerializedObject so Undo records correctly.

Reset First Run Detection

Tools > ZOA > Advanced > Maintain > Workbench > Reset First Run Detection

Clears the EditorPrefs keys that stop the Workbench auto-opening on a clean project, so the first-run experience can be re-tested.

Read this

Notes and caveats

See also