ZOA Foundry
The umbrella you install. One manifest that pulls in the platform, plus the tooling that keeps its boundaries honest.
`com.zoa.foundry` is the package a customer installs, and it is almost empty. Its runtime contains exactly one type, `FoundryVersion`, holding three constants. Everything a game actually runs on lives in the individual `com.zoa.*` packages underneath. What the umbrella contributes is a manifest, a version number that means something, and the publisher-side tooling that proves the stack still holds together.
Installing Foundry resolves a dependency graph. The manifest names twelve `com.zoa.*` packages directly, and those twelve pull the rest in transitively, so a clean install lands roughly forty first-party packages in the project's `Packages/` folder as embedded UPM packages. You get inventory, equipment, weapon modding, armory, the player controller, persistence, platform services, and the whole gameplay layer that hangs off the demo package, in one operation, at one version.
The Unity side of the manifest matters too. Foundry pins Input System 1.18.0, TextMeshPro 3.0.6, AI Navigation 1.1.7, Cinemachine 3.1.6, and the Test Framework 1.6.0, and requires Unity 6000.4.0f1 with URP as the rendering baseline. Those are hard requirements: the player controller, the camera rig, and the AI navigation paths are all built against them.
After import the Start Here experience opens automatically in a clean project. One click on Build Complete System scaffolds a wired, playable runtime through six build phases: core config, inventory, equipment, player prefab, scene, and a database rebuild. The alternative path, for people who would rather assemble it themselves, drops straight into the Workbench with every module and capability exposed.
Depends on (12)
Depended on by (0)
How it works
Concepts
What the umbrella actually contains
The umbrella holds one runtime file. `FoundryVersion` is a static class with `Version`, `MinUnityVersion`, and `PackageCount`, and its own summary says so plainly: it is the sole runtime content of the umbrella package, and all gameplay logic lives in the sub-packages. Nothing in your game should reference it except a build stamp or a support diagnostic.
The editor assembly is where the package earns its place. It ships two architectural linters, a package and asset inventory service, a profile-driven exporter, a pre-export cleanup tool, and a help menu. All of that is publisher and integrator tooling: it validates and packages the stack rather than running any part of the game.
For a shipping project the umbrella is safe to keep installed. It adds no components, no services, no scene requirements, and no runtime cost: it is the manifest, the version, and the tools.
What installing it pulls in
The twelve direct dependencies are the stack's spine plus the two ends of the product. `com.zoa.foundation`, `com.zoa.common`, and `com.zoa.ui.theming` are the base layer. `com.zoa.inventory`, `com.zoa.equipment`, `com.zoa.weaponmodding`, `com.zoa.armory`, and `com.zoa.nucleon` are the systems most integrators touch directly. `com.zoa.persistence`, `com.zoa.platform`, and `com.zoa.platform.steam` cover save and platform services. `com.zoa.demo` is the last one and the widest.
`com.zoa.demo` is the reason the resolved graph is much larger than twelve. It depends on nearly thirty first-party packages of its own, including AI, animation, audio, combat, objectives, progression, loot, economy, minimap, terrain, VFX, and the frontend. `com.zoa.content` in turn brings crafting and quantities. So the gameplay-tier systems arrive transitively through the demo and content packages rather than by being named on the umbrella.
Resolve the whole closure and you land at around forty `com.zoa.*` packages. The repository carries a little over fifty, so a handful sit outside the umbrella's graph and are opt-in additions rather than part of a default install.
Plan a trim with that in mind. Removing the demo package from the manifest takes out an entire tier of the graph, and anything you were using from that tier has to be re-declared explicitly.
What version 1.0.0 means
The umbrella is at 1.0.0 while the packages beneath it sit at 0.4.0 and 0.1.0. The 1.0.0 is the release contract with the customer: this specific resolved set of packages, at these specific versions, was built and validated together and is what the 1.0.0 documentation describes.
Sub-package versions move on their own cadence underneath that line. A package at 0.1.0 is telling you its own API surface is still settling; a package at 0.4.0 has been through more revisions. Neither number is a promise to you directly. The number you pin, ship against, and file a bug against is the umbrella's.
`FoundryVersion.Version` carries the same string in code so a build can stamp it, and the About dialog reads the live version out of the package manifest at runtime rather than hard-coding it, so the dialog cannot drift out of sync with what is installed.
The 1.0.0 release is a first release: its changelog is a single Added section covering the Start Here experience, the system builder, the Workbench, and every sub-package from foundation through demo. Read it as the inventory of what shipped in the box rather than as a diff.
Two linters hold the boundaries
Two editor linters check the modular claim. `AsmdefBoundaryValidator` scans every assembly definition under `Packages/` and reports any runtime assembly that references an editor-only one, which is the failure that compiles fine in the editor and breaks the player build.
`AsmdefDependencyLinter` builds the directed graph of assembly references and does two passes. A depth-first search with node colouring finds circular dependencies and reports the full cycle path. A rule pass finds forbidden edges: armory referencing equipment or the reverse, inventory referencing nucleon, and anything whose assembly name contains Rendering referencing armory, equipment, inventory, nucleon, or weapon modding.
Those specific rules encode the architecture's load-bearing separations. Armory and equipment must stay independent so a weapon can exist without a slot system and a slot system without ballistics. Rendering must never reach into gameplay, which keeps the render-pipeline packages swappable.
Both linters log every violation to the console with the offending pair and then summarise in a dialog, so they are usable both interactively and as a step in a build script.
Export profiles: shipping a subset
Not every downstream project wants all forty packages, and `FoundryExportProfileCatalog` names four profiles that carve the graph differently: `swarms-host-client`, `swarms-authoring`, `steam-demo-reference`, and `full-foundry`. A profile declares seed packages, excluded packages, which asset roots and asset roles to include, and whether samples, tests, and generated demo output come along.
`FoundryPackageInventory.Discover` walks `Packages/` and builds a record per package: id, display name, version, path, dependencies split into local and external, assembly definitions, and role tags. `ComputeClosure` then resolves a profile's seeds into the full transitive set, flagging a seed that is missing from disk or that the profile also excludes as an error finding rather than silently dropping it.
`FoundryExportPipeline.BuildPlan` combines the inventory, the closure, the asset scan, and the profile's third-party dependency list into a `FoundryExportPlan`, whose `HasBlockingFindings` is the gate. `GenerateReadinessReport` and `GenerateExportArtifacts` write markdown reports and import plans, and both have CI entry points so the same check that runs in the editor runs in a pipeline.
This is the machinery behind the changelog's note that UPM publishing for paid Asset Store products is still in beta, so 1.0.0 ships as a hybrid unitypackage. The profile system is how a deterministic subset gets mirrored regardless of which distribution channel is available.
When to install individual packages instead
Take the umbrella when you are building the kind of game it was built for: a first-person action, shooter, survival, or RPG project that will use most of the stack, and where a wired starting point beats assembling one. That is the overwhelmingly common case and the one every wizard, validator, and default asset path assumes.
Take individual packages when you want one primitive and not a platform. `com.zoa.messaging` on its own is a bus and a service registry with no dependencies. `com.zoa.quantities` is a named ledger that needs only foundation and messaging. `com.zoa.crafting` needs only foundation. Each has its own manifest and its own dependency list, so a targeted install resolves a handful of packages rather than forty.
The mixed approach is also legitimate and is what the export profiles exist to support: a project that already has its own player controller and rendering stack but wants Foundry's inventory, equipment, and weapon modding can seed on those three and let the closure resolve what they actually need.
The cost of going piecemeal is that the Workbench's guided flows, the Start Here scaffolder, and the clean-install validator all assume the full set. You keep the runtime systems and give up some of the on-ramp, which is the right trade when you are integrating into an existing project and the wrong one when you are starting fresh.
Cleaning up before you ship
A project built through the Start Here flow accumulates generated content: definition assets under `Assets/ZOA/Generated`, imported samples and content packs, feature-set installer output, test output, demo content, local save state, and third-party assets that must never be redistributed.
`FoundryCleanupTool` scans for all of that and presents it as selectable categories with per-category counts, so a publisher strips a project back to a distributable state from a list instead of from memory. The third-party category is pre-selected by default, because that is the one where shipping the wrong file is a licensing problem rather than a size problem.
`FoundryAssetInventory` is the service underneath, classifying every asset root by a `FoundryAssetRole`: first-party runtime, first-party docs, generated output, generated demo output, test output, third-party dependency, project setting, review-required, or unknown. The Review Required and Unknown roles are the interesting ones, because they are the tool admitting it cannot decide and asking a human to look.
In the editor
Screens
Screenshot pending
/screenshots/foundry-cleanup-tool.png
The Cleanup Tool window with several categories listed and expanded, each showing its item count, the Third-Party Assets category checked by default, and the status line and Clean button visible at the bottom.
Screenshot pending
/screenshots/foundry-export-tool.png
The Export Package window with the profile dropdown showing full-foundry, the output directory and unitypackage path fields populated, the info help box visible, and the Rescan, Open Cleanup Tool, and Write Readiness Report buttons in a row.
Screenshot pending
/screenshots/foundry-dependency-lint-result.png
The Dependency Lint dialog reading All checks passed with the assembly count in the summary, in front of a Unity console showing the single [ZOA][Foundry] Dependency Lint Complete log line.
Setup
Workflow
- 01
Import into a clean Unity 6 project
Requires Unity 6000.4.0f1 or later with URP. All the sub-packages arrive as embedded UPM packages under Packages/, and the Input System, TextMeshPro, AI Navigation, Cinemachine, and Test Framework versions the manifest pins come with them.
- 02
Let Start Here scaffold the project
On a clean project the Start Here experience opens automatically. Build Complete System runs six phases (core config, inventory, equipment, player prefab, scene, database rebuild) and leaves you with something you can press Play on. Choosing the expert path instead drops you into the full Workbench.
- 03
Validate the install
Run the Workbench's clean-install validator, which covers ten checks and offers an automatic fix for each. This is the step that catches a half-imported project before you start blaming a system for something the import did.
- 04
Pick your backends
Select a networking backend in NetworkSettings; the matching sync components are used from then on. Choose a persistence backend from the built-in set: local JSON file, PlayerPrefs, SQLite, MySQL, Redis, or generic ADO.NET. Both are build-time choices, not runtime switches.
- 05
Keep the graph honest as you extend it
Once you start adding your own assemblies, run Lint Package Dependencies and Validate Assembly Boundaries. Cycles and runtime-to-editor references are cheap to fix the day they appear and expensive to unwind a month later.
- 06
Clean before you distribute
Run the Cleanup Tool, review the categories, and pay particular attention to the third-party group. Then write an export readiness report and resolve any blocking findings before producing artifacts.
Surface
Key types
FoundryVersion
class
The umbrella's only runtime type. Three constants describing the release, intended for build stamps and support diagnostics rather than for gameplay code to branch on.
- const string Version = "1.0.0"
- const string MinUnityVersion = "6000.4.0f1"
- const int PackageCount = 9
AsmdefBoundaryValidator
class
Editor validator that scans every assembly definition under Packages and reports runtime assemblies referencing editor-only ones. The check that catches a player-build break before the player build.
- static BoundaryResult RunFullValidation()
- BoundaryResult.Violations : List<(string RuntimeAssembly, string EditorAssembly)>
- BoundaryResult.IsValid
- BoundaryResult.Summary
AsmdefDependencyLinter
class
Editor linter that builds the assembly dependency graph, finds cycles by depth-first search with node colouring, and reports edges that violate the architecture's separation rules.
- static LintResult RunFullLint()
- LintResult.Cycles : List<List<string>>
- LintResult.ForbiddenEdges : List<(string Source, string Target)>
- LintResult.IsClean
FoundryPackageInventory
class
Discovers every package under a packages root and resolves a seed set into its transitive closure. The data layer both the export tool and the readiness report sit on.
- static FoundryPackageInventoryResult Discover(string packagesRoot = "Packages")
- static FoundryDependencyClosureResult ComputeClosure(FoundryPackageInventoryResult inventory, IEnumerable<string> seedPackageIds, IEnumerable<string> excludedPackageIds = null)
- static string ToMarkdown(FoundryPackageInventoryResult inventory)
- static string ToJson(FoundryPackageInventoryResult inventory)
FoundryPackageRecord
class
One package as the inventory sees it: id, display name, version, path, dependencies split into local and external, assembly definitions, role tags, and flags for samples, tests, runtime, and editor content.
- string packageId
- string version
- List<FoundryNameValue> localDependencies
- List<FoundryNameValue> externalDependencies
- List<string> asmdefs
- bool hasSamples
- bool hasTests
FoundryExportProfile
class
A declarative slice of the stack: seed packages, exclusions, asset roots and roles to include, third-party dependencies, and whether samples, tests, and generated demo output travel with it.
- List<string> seedPackages
- List<string> excludedPackages
- List<FoundryAssetRole> includedAssetRoles
- List<FoundryThirdPartyDependency> thirdPartyDependencies
- bool includeAllFoundryPackages
- bool includeSamples
- bool includeTests
FoundryExportProfileCatalog
class
The four shipped profiles, by id: swarms-host-client, swarms-authoring, steam-demo-reference, and full-foundry. Get throws on an unknown id rather than falling back to a default.
- const string SwarmsHostClient = "swarms-host-client"
- const string SwarmsAuthoring = "swarms-authoring"
- const string SteamDemoReference = "steam-demo-reference"
- const string FullFoundry = "full-foundry"
- static IReadOnlyList<FoundryExportProfile> All { get; }
- static FoundryExportProfile Get(string profileId)
FoundryExportPipeline
class
Turns a profile id into a plan, a readiness report, and export artifacts. Has CI entry points so the same gate runs in the editor and in a build pipeline.
- static FoundryExportPlan BuildPlan(string profileId)
- static int GenerateReadinessReport(string profileId = FoundryExportProfileCatalog.FullFoundry, string outputDirectory = DefaultReadinessReportDirectory)
- static int GenerateExportArtifacts(string profileId, string outputDirectory, bool exportUnityPackage = false, string unityPackagePath = null)
- static void ExportReadinessCi()
- static void ExportProfileCi()
FoundryExportPlan
class
The resolved answer for one profile: the package inventory, the dependency closure, the asset inventory, the assets to export, third-party dependencies, and the findings. HasBlockingFindings is the go or no-go.
- string profileId
- FoundryDependencyClosureResult packageClosure
- List<FoundryAssetRootRecord> exportAssets
- List<FoundryFinding> findings
- bool HasBlockingFindings
FoundryAssetInventory
class
Scans an assets root and classifies each root record by role, which is how the cleanup tool decides what is safe to delete and what needs a human to look at it.
- static FoundryAssetInventoryResult Scan(string assetsRoot = "Assets")
- static bool IsDeleteCandidate(FoundryAssetRootRecord record)
- static string ToMarkdown(FoundryAssetInventoryResult inventory)
FoundryAssetRole
enum
FirstPartyRuntime, FirstPartyDocs, GeneratedOutput, GeneratedDemoOutput, TestOutput, ThirdPartyDependency, ProjectSettingConfiguration, ReviewRequired, Unknown. The last two are the classifier declining to guess.
FoundryCleanupTool
class
The pre-export editor window. Scans for generated content, imported samples, feature-set installer output, test output, demo content, local save state, and third-party assets, and removes the categories you select. Third-party is pre-selected.
FoundryExportTool
class
The editor window over the export pipeline. Pick a profile, rescan, write a readiness report, and produce the mirror and import-plan artifacts, with the unitypackage export available as a compatibility path.
Usage
Examples
using UnityEngine;
using ZOA.Foundry;
// The only runtime type in the umbrella package. Useful for a
// support overlay or a crash report; nothing gameplay-facing
// should branch on it.
Debug.Log(
"ZOA Foundry " + FoundryVersion.Version +
" (requires Unity " + FoundryVersion.MinUnityVersion + ")");using UnityEditor;
using UnityEngine;
using ZOA.Foundry.Editor;
public static class ArchitectureGate
{
// Same checks the two Tools > ZOA > Advanced > Validate > Foundry
// menu items run, without the dialogs.
public static bool Run()
{
var lint = AsmdefDependencyLinter.RunFullLint();
var boundaries = AsmdefBoundaryValidator.RunFullValidation();
Debug.Log("Dependency lint: " + lint.Summary);
Debug.Log("Boundary validation: " + boundaries.Summary);
foreach (var cycle in lint.Cycles)
Debug.LogError("Cycle: " + string.Join(" -> ", cycle));
foreach (var (source, target) in lint.ForbiddenEdges)
Debug.LogError("Forbidden edge: " + source + " -> " + target);
foreach (var (runtime, editor) in boundaries.Violations)
Debug.LogError("Runtime -> editor: " + runtime + " -> " + editor);
return lint.IsClean && boundaries.IsValid;
}
}using ZOA.Foundry.Editor;
// Discover every package on disk, then resolve one profile's
// seeds into its full transitive closure.
var inventory = FoundryPackageInventory.Discover();
var profile = FoundryExportProfileCatalog.Get(FoundryExportProfileCatalog.SwarmsHostClient);
var closure = FoundryPackageInventory.ComputeClosure(
inventory,
profile.seedPackages,
profile.excludedPackages);
// A seed that is missing from disk, or that the profile also
// excludes, is reported as an error finding rather than
// silently dropped.
foreach (var finding in closure.findings)
Debug.Log(finding.severity + " " + finding.code + ": " + finding.message);using ZOA.Foundry.Editor;
var plan = FoundryExportPipeline.BuildPlan(FoundryExportProfileCatalog.FullFoundry);
if (plan.HasBlockingFindings)
{
foreach (var finding in plan.findings)
Debug.LogWarning(finding.severity + " " + finding.code + ": " + finding.message);
return;
}
// Writes the mirror and import-plan artifacts; the unitypackage
// export stays available as a compatibility artifact.
int exitCode = FoundryExportPipeline.GenerateExportArtifacts(
FoundryExportProfileCatalog.FullFoundry,
outputDirectory: "Logs/FoundryExports/full-foundry",
exportUnityPackage: false);Tooling
Editor tools
Lint Package Dependencies
Tools > ZOA > Advanced > Validate > Foundry > Lint Package Dependencies
Builds the assembly dependency graph, reports every cycle as a full path and every forbidden edge as a source and target pair, logs the detail to the console, and summarises in a dialog.
Validate Assembly Boundaries
Tools > ZOA > Advanced > Validate > Foundry > Validate Assembly Boundaries
Scans every assembly definition under Packages and reports any runtime assembly that references an editor-only one, with counts of runtime and editor assemblies in the summary.
Cleanup Tool
Tools > ZOA > Advanced > Maintain > Foundry > Cleanup Tool
A UI Toolkit window listing removable categories with per-category counts: generated content, imported samples and content packs, feature-set installer output, test output, demo content, local save state, third-party assets, and miscellaneous. Third-party is selected by default.
Export Readiness Report
Tools > ZOA > Advanced > Maintain > Foundry > Export Readiness Report
Writes a markdown readiness report to Logs/FoundryExportReadiness and reports whether any blocking findings stand between the project and a clean export.
Export Package
Tools > ZOA > Advanced > Maintain > Foundry > Export Package
The profile-driven export window. Choose a profile, rescan, open the Cleanup Tool, write the readiness report, and generate deterministic package mirror and import-plan artifacts, with an optional unitypackage.
Help menu
Tools > ZOA > Advanced > Help
Open Documentation resolves the project's Docs/index.html and falls back to revealing the Docs folder. Open Plan Docs reveals Docs/Refactor. Open GitHub Repository and About ZOA Foundry round it out, with About reading the live version and description from the installed package manifest.
Read this
Notes and caveats
See also