Make EveUnity CultLib dependencies explicit

This commit is contained in:
Meta Vens 2026-07-09 16:38:39 +01:00
commit 5bf4d0f665
8 changed files with 168 additions and 16 deletions

View file

@ -986,6 +986,14 @@ Recently cut:
advertised `worldInteraction` and `requiresPlugins` for Aetheria and Sai
while keeping fixture-local loading paths, and both `web/surface.js` and the
layout probe consume that shared merge path.
- EveUnity's managed dependency story is now explicit machine-readable
lifecycle data instead of only prose. Each CultLib-owned Unity precompiled
dependency under `testContract.managedAssemblyDependencies[]` carries a
nested `dependencyContract` naming CultLib as source authority, NuGet as the
package source, the package/assembly identity, version policy, Unity
incubation resolution, and the EveUnity split handoff requirement. The
runtime capability schema, parity comparator, lifecycle smoke, and
runtime-owner conformance smoke all verify that boundary.
- Sai, Norn, and TeX now have plugin ABI fixtures for describe, validate,
project, lower, measure, and apply behavior. The parity harness validates
those fixtures against plugin manifests, so Eve proves the ABI shape without

View file

@ -10,12 +10,13 @@ component kinds degrade to inert containers instead of gaining local semantics.
Plugin semantics enter through plugin ABI sidecars and advertisements, not
through Unity. This package includes first-party projection adapters for the
`sai.vn` projection surface (`vn.stage`, dialogue panels, action rails, and
story command requests) and the `norn.graph` projection surface (`embed.norn`).
Sai still owns story state and command semantics. Norn still owns graph layout
and graph semantics. Unity only owns native projection of already-declared
plugin capabilities and emits `gamecult.eve.command.v1` requests. `tex.math`
still passes through as generic Eve component structure until it gains an
explicit Unity projection adapter.
story command requests), the `norn.graph` projection surface (`embed.norn`),
and the `tex.math` source-fallback projection surface (`embed.tex`,
`tex.inline`, and `tex.block`). Sai still owns story state and command
semantics. Norn still owns graph layout and graph semantics. TeX still owns
parsing, typesetting, baseline metrics, and cached render semantics. Unity only
owns native projection of already-declared plugin capabilities and emits
`gamecult.eve.command.v1` requests.
This package lives in the Eve repository as the shared Unity lowering target.
Aetheria and other Unity consumers should import it from Eve instead of
@ -62,6 +63,12 @@ under `artifacts/aetheria-unity-editmode/{stamp}`, and temporarily adds
Unity package consumption proof. The test contract names the CultLib-owned
NuGet/precompiled assembly inputs Unity needs during incubation; CultLib still
owns the .NET/NuGet dependency story for its assemblies.
Each managed dependency record carries a nested `dependencyContract` naming
CultLib as `sourceAuthority`, `nuget` as `packageSource`, the package id,
assembly name, version policy, Unity incubation resolution mode, and the
handoff requirement for EveUnity. This is not Eve inventing a second dependency
system. It is EveUnity declaring the assemblies it consumes while CultLib keeps
the package authority.
The capture stage declares `gamecult.eve.runtime_capture_request.v1` request
construction through `tools/eveunity/eveunity-capture-contract.mjs`. That

View file

@ -126,21 +126,48 @@
"packageId": "GameCult.Caching",
"packageManager": "nuget",
"ownerRepo": "CultLib",
"resolutionMode": "Unity precompiled reference during EveUnity incubation."
"resolutionMode": "Unity precompiled reference during EveUnity incubation.",
"dependencyContract": {
"sourceAuthority": "CultLib",
"packageSource": "nuget",
"packageId": "GameCult.Caching",
"assemblyName": "GameCult.Caching.dll",
"versionPolicy": "CultLib-owned package version stream.",
"unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.",
"handoffRequirement": "EveUnity split must resolve this through CultLib's NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve."
}
},
{
"assemblyName": "GameCult.Mesh.dll",
"packageId": "GameCult.Mesh",
"packageManager": "nuget",
"ownerRepo": "CultLib",
"resolutionMode": "Unity precompiled reference during EveUnity incubation."
"resolutionMode": "Unity precompiled reference during EveUnity incubation.",
"dependencyContract": {
"sourceAuthority": "CultLib",
"packageSource": "nuget",
"packageId": "GameCult.Mesh",
"assemblyName": "GameCult.Mesh.dll",
"versionPolicy": "CultLib-owned package version stream.",
"unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.",
"handoffRequirement": "EveUnity split must resolve this through CultLib's NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve."
}
},
{
"assemblyName": "MessagePack.Annotations.dll",
"packageId": "MessagePack.Annotations",
"packageManager": "nuget",
"ownerRepo": "CultLib",
"resolutionMode": "Unity precompiled reference during EveUnity incubation."
"resolutionMode": "Unity precompiled reference during EveUnity incubation.",
"dependencyContract": {
"sourceAuthority": "CultLib",
"packageSource": "nuget",
"packageId": "MessagePack.Annotations",
"assemblyName": "MessagePack.Annotations.dll",
"versionPolicy": "CultLib-owned transitive package version stream.",
"unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.",
"handoffRequirement": "EveUnity split must resolve this through CultLib's NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve."
}
}
]
},

View file

@ -396,7 +396,8 @@
"packageId",
"packageManager",
"ownerRepo",
"resolutionMode"
"resolutionMode",
"dependencyContract"
],
"properties": {
"assemblyName": {
@ -418,6 +419,49 @@
"resolutionMode": {
"type": "string",
"minLength": 1
},
"dependencyContract": {
"type": "object",
"required": [
"sourceAuthority",
"packageSource",
"packageId",
"assemblyName",
"versionPolicy",
"unityResolution",
"handoffRequirement"
],
"properties": {
"sourceAuthority": {
"type": "string",
"minLength": 1
},
"packageSource": {
"type": "string",
"minLength": 1
},
"packageId": {
"type": "string",
"minLength": 1
},
"assemblyName": {
"type": "string",
"minLength": 1
},
"versionPolicy": {
"type": "string",
"minLength": 1
},
"unityResolution": {
"type": "string",
"minLength": 1
},
"handoffRequirement": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": true
}
},
"additionalProperties": true

View file

@ -180,6 +180,20 @@ foreach ($dependency in @(
if ($record.packageId -ne $dependency.packageId -or $record.packageManager -ne "nuget" -or $record.ownerRepo -ne "CultLib") {
throw "EveUnity test contract has unexpected managed dependency record for $($dependency.assemblyName)"
}
if ($null -eq $record.dependencyContract) {
throw "EveUnity test contract missing dependency contract for $($dependency.assemblyName)"
}
if ($record.dependencyContract.sourceAuthority -ne "CultLib" -or $record.dependencyContract.packageSource -ne "nuget") {
throw "EveUnity test contract has unexpected dependency source for $($dependency.assemblyName): $($record.dependencyContract.sourceAuthority)/$($record.dependencyContract.packageSource)"
}
if ($record.dependencyContract.packageId -ne $dependency.packageId -or $record.dependencyContract.assemblyName -ne $dependency.assemblyName) {
throw "EveUnity test contract dependency contract does not match managed dependency identity for $($dependency.assemblyName)"
}
foreach ($field in @("versionPolicy", "unityResolution", "handoffRequirement")) {
if (-not $record.dependencyContract.$field) {
throw "EveUnity test contract dependency contract missing $field for $($dependency.assemblyName)"
}
}
if (-not (@($testAssemblyDefinition.precompiledReferences) -contains $dependency.assemblyName)) {
throw "EveUnity test asmdef missing managed dependency precompiled reference: $($dependency.assemblyName)"
}

View file

@ -179,7 +179,13 @@ node $consumerScript $consumerExport `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.testPlatform:EditMode" `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.ownerRepo:CultLib" `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.packageManager:nuget" `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.dependencyContract.sourceAuthority:CultLib" `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.dependencyContract.packageSource:nuget" `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.dependencyContract.packageId:GameCult.Caching" `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.1.packageId:GameCult.Mesh" `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.1.dependencyContract.sourceAuthority:CultLib" `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.1.dependencyContract.packageSource:nuget" `
--expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.1.dependencyContract.packageId:GameCult.Mesh" `
--expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.captureKind:unity-editor-or-batchmode-png" `
--expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.artifactKind:png" `
--expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.targetId:unity-uitoolkit" `

View file

@ -1625,21 +1625,48 @@
"packageId": "GameCult.Caching",
"packageManager": "nuget",
"ownerRepo": "CultLib",
"resolutionMode": "Unity precompiled reference during EveUnity incubation."
"resolutionMode": "Unity precompiled reference during EveUnity incubation.",
"dependencyContract": {
"sourceAuthority": "CultLib",
"packageSource": "nuget",
"packageId": "GameCult.Caching",
"assemblyName": "GameCult.Caching.dll",
"versionPolicy": "CultLib-owned package version stream.",
"unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.",
"handoffRequirement": "EveUnity split must resolve this through CultLib's NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve."
}
},
{
"assemblyName": "GameCult.Mesh.dll",
"packageId": "GameCult.Mesh",
"packageManager": "nuget",
"ownerRepo": "CultLib",
"resolutionMode": "Unity precompiled reference during EveUnity incubation."
"resolutionMode": "Unity precompiled reference during EveUnity incubation.",
"dependencyContract": {
"sourceAuthority": "CultLib",
"packageSource": "nuget",
"packageId": "GameCult.Mesh",
"assemblyName": "GameCult.Mesh.dll",
"versionPolicy": "CultLib-owned package version stream.",
"unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.",
"handoffRequirement": "EveUnity split must resolve this through CultLib's NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve."
}
},
{
"assemblyName": "MessagePack.Annotations.dll",
"packageId": "MessagePack.Annotations",
"packageManager": "nuget",
"ownerRepo": "CultLib",
"resolutionMode": "Unity precompiled reference during EveUnity incubation."
"resolutionMode": "Unity precompiled reference during EveUnity incubation.",
"dependencyContract": {
"sourceAuthority": "CultLib",
"packageSource": "nuget",
"packageId": "MessagePack.Annotations",
"assemblyName": "MessagePack.Annotations.dll",
"versionPolicy": "CultLib-owned transitive package version stream.",
"unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.",
"handoffRequirement": "EveUnity split must resolve this through CultLib's NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve."
}
}
]
},

View file

@ -1574,7 +1574,20 @@ function compareTestContract(expected, actual, label) {
expected.managedAssemblyDependencies || [],
actual.managedAssemblyDependencies || [],
`${label}.managedAssemblyDependencies`,
["assemblyName", "packageId", "packageManager", "ownerRepo", "resolutionMode"],
[
"assemblyName",
"packageId",
"packageManager",
"ownerRepo",
"resolutionMode",
"dependencyContract.sourceAuthority",
"dependencyContract.packageSource",
"dependencyContract.packageId",
"dependencyContract.assemblyName",
"dependencyContract.versionPolicy",
"dependencyContract.unityResolution",
"dependencyContract.handoffRequirement",
],
));
return errors;
}
@ -1615,14 +1628,20 @@ function compareDependencyRecords(expectedRecords, actualRecords, label, keys) {
continue;
}
for (const key of keys) {
if ((expected[key] || "") !== (actual[key] || "")) {
errors.push(`${label}:${expected[identityKey]}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`);
const expectedValue = getByPath(expected, key) || "";
const actualValue = getByPath(actual, key) || "";
if (expectedValue !== actualValue) {
errors.push(`${label}:${expected[identityKey]}.${key}:expected ${expectedValue} got ${actualValue}`);
}
}
}
return errors;
}
function getByPath(record, dottedPath) {
return dottedPath.split(".").reduce((current, segment) => current?.[segment], record);
}
function comparePluginCapabilityClaims(expectedPlugins, actualPlugins, label) {
const errors = [];
const actual = new Map(actualPlugins.map(plugin => [plugin.pluginId, new Set(plugin.capabilities || [])]));