Align stellar lighting and gravity field axes

This commit is contained in:
Meta Vens 2026-07-16 18:50:41 +01:00
commit db35250d09
9 changed files with 136 additions and 58 deletions

View file

@ -727,7 +727,26 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
{ ItemKey = "test-instant", Quality = 1, Durability = 1, Enabled = true }
}];
var target = Entity(1, 80, "raider");
var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] };
var zone = new AetheriaRuntimeZoneSnapshotCommit
{
ZoneIndex = 0,
Entities = [source, target],
Bodies =
[
new AetheriaRuntimeBodySnapshotCommit
{
Kind = "sun",
Mass = 16,
SunVisual = new AetheriaRuntimeSunVisualCommit
{
LightColorX = 0.5,
LightColorY = 0.75,
LightColorZ = 1,
LightRadiusMultiplier = 1
}
}
]
};
var run = new AetheriaRuntimeRunCheckpointCommit
{
RunId = "instant-trigger-smoke", CurrentZoneIndex = 0,
@ -797,7 +816,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
world.Props["cameraNearClipPlane"] == "1" &&
world.Props["cameraFarClipPlane"] == "4096",
"undocked world must translate the authored ARPG Third Person Rig into bottom-origin Eve viewport coordinates");
Require(world.Props["skyboxAssetRef"] == "material.environment.skybox" &&
Require(!world.Props.ContainsKey("skyboxAssetRef") &&
world.Props["ambientLightColor"] == "0.5,0.75,1" &&
world.Props["reflectionAssetRef"] == "texture.environment.reflection" &&
world.Props["postProcessProfileAssetRef"] == "profile.environment.flight" &&
world.Props["cameraReconstruction"] == "temporal-reprojection.v1" &&
@ -810,7 +830,7 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
!world.Props.ContainsKey("keyLightDirection") &&
!world.Props.ContainsKey("keyLightColor") &&
!world.Props.ContainsKey("keyLightIntensity"),
"playable world must advertise the provider-owned fossil skybox and reflection without inventing a scene light");
"playable world must light from stellar tint, use the studio HDRI only for reflections, and leave the raymarch pass in charge of the frame");
Require(aim.Props["controlledEntityIndex"] == "0" &&
aim.Props["convergenceTargetEntityId"] == run.EntityRecordKey(0, 1) &&
aim.Props["minimumConvergenceDistance"] == "50",
@ -1085,13 +1105,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
RequireNear(30, behavior.Fields.Single(field => field.Key == 27).Value.NumberValue, 0.000001,
"catalog mine must retain thirty-second lifetime");
var manifest = AetheriaRuntimeAssets.ProjectManifest(catalog);
var skybox = manifest.Assets.Single(value => value.Ref.AssetKey == "material.environment.skybox");
RequireEqual(AetheriaRuntimeAssetKinds.Material, skybox.Ref.Kind,
"provider must type the fossil skybox as a native material asset");
RequireEqual("environment.skybox", skybox.Ref.Metadata["presentationRole"],
"provider must advertise the generic skybox presentation role");
RequireEqual("Assets/Materials/Skybox.mat", skybox.Ref.Metadata["unityAssetPath"],
"provider bundle must own the fossil skybox material");
Require(!manifest.Assets.Any(value => value.Ref.AssetKey == "material.environment.skybox"),
"provider catalog must not retain the obsolete skybox authority when raymarched fog owns the frame");
var reflection = manifest.Assets.Single(value => value.Ref.AssetKey == "texture.environment.reflection");
RequireEqual(AetheriaRuntimeAssetKinds.Texture, reflection.Ref.Kind,
"provider must type the pre-generated environment map as a texture asset");

View file

@ -62,6 +62,8 @@ namespace Aetheria.Editor
var kernel = program.FindKernel("UpdateParticles");
using var particles = new ComputeBuffer(Span * Span, 7 * sizeof(float), ComputeBufferType.Structured);
var field = ConstantTexture(new Color(0.5f, 0.5f, 0.5f, 1f));
var emptyField = ConstantTexture(Color.clear);
var positiveXField = PositiveXTexture();
var tint = ConstantTexture(Color.white);
var hue = ConstantTexture(Color.white);
try
@ -92,6 +94,15 @@ namespace Aetheria.Editor
program.SetFloat("_DynamicLodLow", 2f);
program.SetInt("span", Span);
program.SetFloat("_FlowAmplitude", 0f);
program.SetTexture(kernel, "_NebulaSurfaceHeight", emptyField);
var flat = DispatchAndRead(program, kernel, particles, 0f, 0f);
program.SetTexture(kernel, "_NebulaSurfaceHeight", positiveXField);
var oriented = DispatchAndRead(program, kernel, particles, 0f, 0f);
AssertPositiveWorldXSamplesPositiveTextureX(flat, oriented);
program.SetTexture(kernel, "_NebulaSurfaceHeight", field);
program.SetFloat("_FlowAmplitude", 15f);
var before = DispatchAndRead(program, kernel, particles, 0f, 0f);
AssertWholeBlockRemapIsInvisible(
before,
@ -114,6 +125,8 @@ namespace Aetheria.Editor
finally
{
UnityEngine.Object.DestroyImmediate(field);
UnityEngine.Object.DestroyImmediate(emptyField);
UnityEngine.Object.DestroyImmediate(positiveXField);
UnityEngine.Object.DestroyImmediate(tint);
UnityEngine.Object.DestroyImmediate(hue);
}
@ -126,7 +139,11 @@ namespace Aetheria.Editor
float gridCenterX,
float gridCenterY)
{
program.SetVector("_GridTransform", new Vector4(gridCenterX, gridCenterY, 1536f, 1536f));
program.SetVector("_GridTransform", new Vector4(
gridCenterX,
gridCenterY,
Span * Spacing,
Span * Spacing));
program.Dispatch(kernel, Mathf.CeilToInt((float)(Span * Span) / GroupSize), 1, 1);
var result = new Particle[Span * Span];
particles.GetData(result);
@ -162,6 +179,20 @@ namespace Aetheria.Editor
}
}
private static void AssertPositiveWorldXSamplesPositiveTextureX(
Particle[] flat,
Particle[] oriented)
{
const int positiveWorldCellX = 3;
const int worldCellY = 0;
var index = (worldCellY + Span / 2) * Span + positiveWorldCellX + Span / 2;
var displacement = oriented[index].Position.y - flat[index].Position.y;
if (displacement > -0.75f)
throw new InvalidOperationException(
$"Stardust positive-world X sampled the wrong side of the typed gravity viewport; " +
$"expected at least one unit of downward displacement, observed {displacement:R}.");
}
private static bool BitwiseEqual(Particle left, Particle right) =>
Bits(left.Position.x) == Bits(right.Position.x) &&
Bits(left.Position.y) == Bits(right.Position.y) &&
@ -187,5 +218,23 @@ namespace Aetheria.Editor
texture.Apply(false, false);
return texture;
}
private static Texture2D PositiveXTexture()
{
const int size = 8;
var texture = new Texture2D(size, size, TextureFormat.RGBA32, false, true)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp,
hideFlags = HideFlags.HideAndDontSave
};
var pixels = new Color[size * size];
for (var y = 0; y < size; y++)
for (var x = 0; x < size; x++)
pixels[y * size + x] = x >= size / 2 ? Color.white : Color.clear;
texture.SetPixels(pixels);
texture.Apply(false, false);
return texture;
}
}
}

View file

@ -576,9 +576,6 @@ namespace Aetheria.Editor
var assetNames = bundle.GetAllAssetNames();
foreach (var assetPath in assetNames.Where(path => path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)))
VerifyPrefab(bundle.LoadAsset<GameObject>(assetPath), assetPath);
var skybox = LoadAuthoredAsset<Material>(bundle, assetNames, "Assets/Materials/Skybox.mat");
if (skybox == null || skybox.shader == null || !skybox.shader.isSupported)
throw new InvalidOperationException("Aetheria Eve bundle has no supported provider skybox material.");
var reflection = LoadAuthoredAsset<Cubemap>(bundle, assetNames, "Assets/Textures/studio2.hdr");
if (reflection == null)
throw new InvalidOperationException("Aetheria Eve bundle has no provider reflection cubemap.");

View file

@ -80,7 +80,10 @@ float parabola( float x, float k )
float2 getUV(float2 pos)
{
return -(pos-_GridTransform.xy)/_GridTransform.z + float2(.5,.5);
// Eve Fields rasterizes its typed viewport with positive world axes.
// The fossil gravity camera reversed both axes and required a leading
// minus here; that camera is no longer an authority in the daemon path.
return (pos-_GridTransform.xy)/_GridTransform.z + float2(.5,.5);
}
float2 getUVFluid(float2 pos)

View file

@ -33,7 +33,6 @@ namespace GameCult.Aetheria.State.Verse
string baseUri = "cultmesh://aetheria.local/assets")
{
var entries = new Dictionary<string, AetheriaRuntimeAssetManifestEntry>(StringComparer.Ordinal);
Add(entries, EnvironmentSkyboxMaterial());
Add(entries, EnvironmentReflectionCubemap());
Add(entries, EnvironmentPostProcessProfile());
Add(entries, EnvironmentGravityFogShader());
@ -199,26 +198,6 @@ namespace GameCult.Aetheria.State.Verse
};
}
public static AetheriaRuntimeAssetManifestEntry EnvironmentSkyboxMaterial()
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = "material.environment.skybox",
Kind = AetheriaRuntimeAssetKinds.Material,
Uri = CultMeshAssetUri("material.environment.skybox"),
MimeType = "application/vnd.unity.material",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = "Assets/Materials/Skybox.mat",
["presentationRole"] = "environment.skybox"
}
},
Tags = new[] { "presentation", "environment", "skybox" }
};
}
public static AetheriaRuntimeAssetManifestEntry EnvironmentReflectionCubemap()
{
return new AetheriaRuntimeAssetManifestEntry

View file

@ -621,6 +621,7 @@ namespace GameCult.Aetheria.State.Verse
var cameraTargetEntityId = isDocked
? run.EntityRecordKey(zone.ZoneIndex, dockParent!.EntityIndex)
: playerEntityId;
var stellarAmbient = ResolveStellarAmbient(zone);
var props = new Dictionary<string, string>(StringComparer.Ordinal)
{
["label"] = string.IsNullOrWhiteSpace(zone.Name) ? "Aetheria World" : zone.Name,
@ -645,9 +646,8 @@ namespace GameCult.Aetheria.State.Verse
["cameraPositionDamping"] = isDocked ? "2" : "0",
["cameraNearClipPlane"] = isDocked ? "0.3" : "1",
["cameraFarClipPlane"] = isDocked ? "2048" : "4096",
["ambientLightColor"] = "0.2,0.2,0.2",
["ambientLightColor"] = string.Join(",", F(stellarAmbient.X), F(stellarAmbient.Y), F(stellarAmbient.Z)),
["ambientLightIntensity"] = "1.46",
["skyboxAssetRef"] = "material.environment.skybox",
["reflectionAssetRef"] = "texture.environment.reflection",
["reflectionIntensity"] = "1",
["postProcessProfileAssetRef"] = "profile.environment.flight",
@ -1374,6 +1374,31 @@ namespace GameCult.Aetheria.State.Verse
};
}
private static (double X, double Y, double Z) ResolveStellarAmbient(
AetheriaRuntimeZoneSnapshotCommit zone)
{
var suns = (zone?.Bodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>())
.Where(body => body != null &&
string.Equals(body.Kind, "sun", StringComparison.OrdinalIgnoreCase) &&
body.SunVisual != null)
.Select(body => new
{
Visual = body.SunVisual,
Weight = Math.Pow(Math.Max(0, body.Mass), 0.25) *
Math.Max(0.01, body.SunVisual.LightRadiusMultiplier)
})
.Where(value => value.Weight > 0)
.ToArray();
if (suns.Length == 0)
return (0.2, 0.2, 0.2);
var weight = suns.Sum(value => value.Weight);
return (
suns.Sum(value => Math.Max(0, value.Visual.LightColorX) * value.Weight) / weight,
suns.Sum(value => Math.Max(0, value.Visual.LightColorY) * value.Weight) / weight,
suns.Sum(value => Math.Max(0, value.Visual.LightColorZ) * value.Weight) / weight);
}
private static string PlayableWorldEntityId(
AetheriaRuntimeRunCheckpointCommit run,
AetheriaRuntimeZoneSnapshotCommit zone,

View file

@ -1067,28 +1067,34 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
### Native Environment Lighting Contract
- Owner: Aetheria owns the fossil scene's skybox, ambient intensity, custom
reflection cubemap, and each provider material's lit/unlit semantics. EveUnity
owns only the reversible application of those advertised environment facts.
- Inputs: `ARPG.unity` render settings, `Assets/Materials/Skybox.mat`,
`Assets/Textures/studio2.hdr`, and source shader lighting models. The fossil
scene contains no Unity `Light` component.
- Outputs: the world surface advertises skybox ambient intensity `1.46`, custom
reflection intensity `1`, and no key light. Provider material conversion maps
- Owner: Aetheria owns stellar ambient tint, the gravity-fog frame fill, the
custom reflection cubemap, and each provider material's lit/unlit semantics.
EveUnity owns only the reversible application of those advertised environment
facts.
- Inputs: daemon-owned sun `LightColor`, sun mass and light-radius multiplier,
`Assets/Textures/studio2.hdr`, the gravity-fog field, and source shader lighting
models. Multiple stellar tints blend by their authored light-radius weight.
- Outputs: the world surface advertises stellar-tinted flat ambient lighting at
fossil intensity `1.46`, the studio HDRI as custom reflection at intensity `1`,
no skybox, and no invented directional key light. Provider material conversion maps
particle sources to URP Particles/Unlit, unlit or sprite sources to URP/Unlit,
and genuinely lit sources to URP/Lit.
- Derived state: Unity ambient probes, custom-reflection state, runtime material
instances, and shader keywords are client presentation state.
- Derived state: Unity ambient state, custom-reflection state, runtime material
instances, and shader keywords are client presentation state. The studio HDRI
textures reflected ambient light; it does not choose its color or own the
visible background. The raymarch pass fills the frame.
- Forbidden writers: the daemon cannot invent a directional light to compensate
for broken material conversion. EveUnity cannot add product lighting or infer
shader models from Aetheria asset names.
- Cut line: the invented `0.4,-1,0.25` warm key light is deleted. The embedded
for broken material conversion. EveUnity cannot promote the reflection HDRI to
skybox/ambient authority or infer shader models from Aetheria asset names.
- Cut line: the obsolete provider skybox catalog entry and world binding are
deleted, as is the invented `0.4,-1,0.25` warm key light. The embedded
fossil shield renderer is removed from ship presentation prefabs because its
dithered collision response is already externalized as the provider-owned,
receipt-driven shield-impact effect; it cannot remain permanently visible
after generic material conversion.
- Verification layer: daemon smoke negatively proves that no key-light fields
are published. The released cold witness records zero directional intensity,
- Verification layer: daemon smoke proves the stellar color projection and
negatively proves that neither skybox nor key-light fields are published. The released witness records flat
ambient mode, the exact lowered color, zero directional intensity,
no player `Shield Visual`, twelve remaining player renderers, and a map-channel
material using `Universal Render Pipeline/Unlit`; visual inspection shows ship
hulls instead of opaque shield ellipsoids and readable map glyphs.
@ -1145,7 +1151,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
bundle verification dispatches the bundled HLSL and reindexes by world cell;
705 overlapping cells across X, Y, and diagonal whole-buffer remaps must keep
all seven particle floats bit-identical while the same buffer slot changes
identity. The released witness must record 65,536 particles, one compute
identity. An asymmetric gravity texture also proves positive-world X samples
positive texture X; the deleted fossil gravity camera's two-axis reversal is
not part of the typed Eve Fields viewport. The released witness must record 65,536 particles, one compute
dispatch and one pilot-camera draw while the map camera remains isolated.
The released `0.3.57` witness additionally requires the live fog and particle
grid centers to be equal and the leased pilot camera to report URP temporal

View file

@ -35,7 +35,7 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity.
| Render channels | Unity layers/culling masks | provider surface policy + runtime asset variant | semantic channel exclusions | EveUnity native layer mapping | wired, exclusive, and lowered: the pilot surface excludes the semantic `map` channel; the Unity asset variant maps `map` to its authored native layer; EveUnity subtracts that layer from the configured camera mask; portable state contains no Unity layer or mask; provider conversion preserves the source icon shader's unlit lighting model; the released witness proves the pilot camera excludes map glyphs while the map camera retains readable white glyphs without a scene light | preserve the channel proof while world presentation and transport continue to evolve |
| Minimap/tactical map | `ZoneRenderer`, map cameras | daemon world facts | explicit map projections | Unity and Electron | facts fragmented; generic lowerers absent | same state in both runtimes |
| Celestial world | `ZoneRenderer`, body/asteroid prefab scripts | daemon render projection plus provider assets | ordinary generic Eve entity SoA rows | EveUnity generic entity/prefab lowerer | wired, exclusive, projected, and lowered: daemon orbit and terrain queries emit stable presentation-only identities for suns, planets, gas giants, and live asteroid instances; provider packaging extracts only each advertised visual child and strips fossil scripts/physics; released EveUnity `0.3.51` applies semantic scale around provider-authored local scale and contains no celestial or Aetheria branch. The released warm witness records eleven bodies and eighteen asteroids with provider refs and enabled renderers, including seven celestial instances intersecting the pilot frustum | body material/light readability, close-range composition matching the fossil, asteroid variation, and Electron lowering |
| Fields/fog/gravity | zone renderer and shaders | daemon/plugin semantics | Fields plugin documents | EveUnity fields lowerer | daemon publishes canonical gravity/fog splat layers through `field.volume3d`; Eve Fields `0.2.3` owns portable `PowerPulse` and `AnimatedRadialCosine` semantics; Aetheria emits the fossil global animated simplex/cellular/ambient stack, body-owned radial waves, and separate negative gameplay `gravity.height` and positive visual `fog.surface_height`; the shared camera-relative lattice spans 1536 world units as 256 six-unit Stardust cells, rasterizes gravity at eight texels per cell into 2048 texels, and snaps the viewport by one six-unit cell; daemon entities follow fossil terrain height plus hull grid offset after Ymir XZ motion; released EveUnity `0.3.57` lowers the authored topology, one shared snapped viewport frame for fog and Stardust, provider shader ABI, pre-postprocess URP pass, provider-requested TAA, provider-material temporal coverage inputs, authoritative flow time, explicit camera matrix, and float32 packed-output diagnostics without provider code. The provider bundle runs the actual Stardust compute shader across X, Y, and diagonal one-cell remaps and proves 705 overlapping world cells remain bit-identical after buffer reassignment; its source gate rejects replacement of temporal dithering with a fixed alpha threshold. The released warm witness proves identical fog/particle grid centers and live dither/frame bindings drive 65,536 rendered particles while the map camera remains isolated. The flight post-process bundle gate verifies serialized ACES, exposure/contrast, bloom, vignette, and film-grain components | restore readable patch structure, gravity waves, surrounding world geometry, and final luminance/composition parity; visual parity remains explicitly open |
| Fields/fog/gravity | zone renderer and shaders | daemon/plugin semantics | Fields plugin documents | EveUnity fields lowerer | daemon publishes canonical gravity/fog splat layers through `field.volume3d`; Eve Fields `0.2.3` owns portable `PowerPulse` and `AnimatedRadialCosine` semantics; Aetheria emits the fossil global animated simplex/cellular/ambient stack, body-owned radial waves, and separate negative gameplay `gravity.height` and positive visual `fog.surface_height`; the shared camera-relative lattice spans 1536 world units as 256 six-unit Stardust cells, rasterizes gravity at eight texels per cell into 2048 texels, and snaps the viewport by one six-unit cell; daemon entities follow fossil terrain height plus hull grid offset after Ymir XZ motion; released EveUnity `0.3.57` lowers the authored topology, one shared snapped viewport frame for fog and Stardust, provider shader ABI, pre-postprocess URP pass, provider-requested TAA, provider-material temporal coverage inputs, authoritative flow time, explicit camera matrix, and float32 packed-output diagnostics without provider code. The provider bundle runs the actual Stardust compute shader across X, Y, and diagonal one-cell remaps and proves 705 overlapping world cells remain bit-identical after buffer reassignment; an asymmetric GPU fixture proves positive world X samples positive texture X instead of retaining the deleted fossil gravity camera's two-axis reversal; its source gate rejects replacement of temporal dithering with a fixed alpha threshold. The released warm witness proves identical fog/particle grid centers and live dither/frame bindings drive 65,536 rendered particles while the map camera remains isolated. The flight post-process bundle gate verifies serialized ACES, exposure/contrast, bloom, vignette, and film-grain components | restore readable patch structure, gravity waves, surrounding world geometry, and final luminance/composition parity; visual parity remains explicitly open |
| HUD/schematic | ARPG HUD and `SchematicDisplay` | Eve composition from daemon facts | semantic status/components | EveUnity UI | provider-authored transparent cockpit is wired, projected, and lowered through generic UI Toolkit panes, metrics, and progress components for hull, shield, capacitor, temperature, cargo, weapon cooldown/groups, selected target, target lock, target shield, and target hull; the original armor/equipment schematic and transient warning choreography remain reference-only | schematic topology, warning timelines, dynamic action state, and composited live capture |
| Hit/lock/warnings | `ActionGameManager` feedback | daemon event facts | transient feedback stream | VFX/UI/audio | bounded deduplicated game-event ledger and Eve `feedback.stream` wired for pickup outcomes, projectile launch/impact, entity damage, alive-to-dead destruction, authoritative weapon reload chronology, lock start/acquired/lost chronology, and episode-deduplicated `weapon.fire.refused` facts for insufficient energy or missing ammunition; every lock transition carries authored weapon identity, target, reason, and prior/current progress, while reconnect priming prevents retained events from replaying as new feedback | add thermal and docking event families, negative transaction fixtures, provider lock audio/effect roles, and broader runtime effect consumption |
| Assets | `Assets/Resources` | Aetheria provider | asset catalog + CultMesh CDN | runtime cache/load | provider manifests name deterministic bundle-internal paths and now project each typed hull prefab through the same asset key in Eve scene and SoA identity state; the authoring build derives presentation-only prefabs, strips non-Eve gameplay scripts, externalizes embedded effects, preserves source material semantics, and verifies the loaded bundle ABI; released EveUnity `0.3.55` resolves Unity's lowercase bundle names against authored-case advertised keys and the warm cache witness lowers the verified provider body through local file mapping | cold CDN transfer is failing: the bundle still times out while transported through batched snapshot records instead of mapped/network body transport; provider-to-client network zero-copy, shared-memory delivery, and GPU zero-copy remain open |

View file

@ -92,9 +92,11 @@ Material profiles describe intent such as opaque PBR, transparent, additive,
field, shield, and celestial. They expose typed parameters and texture slots;
they do not publish Unity shader names as portable authority.
The current native environment seed packages the authored procedural skybox
material at `Assets/Materials/Skybox.mat` and the pre-generated Radiance image
at `Assets/Textures/studio2.hdr`. The HDR source is 4096 by 2048 pixels with
The current native environment seed packages the pre-generated Radiance image
at `Assets/Textures/studio2.hdr` strictly as the reflection texture for
stellar-tinted ambient lighting. Gravity-fog raymarching fills the visible
frame; the studio image is not a skybox or an ambient-color authority. The HDR
source is 4096 by 2048 pixels with
SHA-256 `A2886F1024F67DCB082DA3C97741B4B0824A2E784A697807DBE5DCEB597F85E0`;
it entered the reference project in commit `b53e4aec` on 2021-02-26. The
repository contains no external license attribution for that file, so its