diff --git a/docs/aetheria-fauna-map.md b/docs/aetheria-fauna-map.md index b35bab9d3..f3e6563fd 100644 --- a/docs/aetheria-fauna-map.md +++ b/docs/aetheria-fauna-map.md @@ -379,3 +379,89 @@ evidence) and `aetheria-fauna:question:habitat-no-pop` (how a member is carried 23. `flow-moves-matter`: floating items, mines and vented clouds drift with the flow (`flow-moves-what-ruled`). 24. `flow-slope-gradient-parity`: the brush-rendered heightfield and `Zone.GetHeight` agree (`flow-slope-terms`). 25. `orbit-sync-helper`: the generic flying aid that holds a ship on the local orbital flow. Waits on an operator yes. + +## r4: placement is a trophic DAG + +Written by Imagination (agent imagination-aeth-trophic-dag, session self-2026-10-14-ag) against `origin/master` +`7633408f` (the flow owner `FlowField`/`Zone.Flow` is on master) and CultLib `main` `6c902fd4`. The operator ruled +`habitat-trophic-dag`: none of the r3 density drivers gives "the filament feeling"; placement is "a few layers of +ecological abstraction, all deterministic, to author the various species with", whose trophic levels "form a DAG +which we can evaluate in either shader passes or in C#". `voronoi-primitive` adds the weighted Voronoi she likes. +Self relayed a correction on 2026-10-14, her words: "Parity between shader and C# is CultMath's whole raison +d'etre". The target is revised to r4 ("Placement law"). The r3 rows B24-B27 and the habitat probe stand; B25's +candidates are superseded as designs. Prior art: `docs/research/2026-10-14-trophic-dag-filaments-prior-art.md` +(`TD:`, tags [V] read, [S] search summary, [M] memory or derivation). The species the graph authors come from +the lore branch `eureka/aetheria-fauna-lore` `74c7e212`, note `Open Water` (`OW`). + +### Body facts (r4) + +| # | Question | Result | +|---|---|---| +| B28 | What does CultMath already give a node library? | Integer PCG hashes `pcg`, `pcg3d`, `pcg4d` (Jarzynski and Olano), "integer-only, so dxc and C# agree bit for bit" (`packages/cultmath/src/CultMath/math.cs:393-426`, `shaders/CultMath.hlsl:396-426`). `cellular(float3)`: Worley F1 and F2 - F1 with analytic gradients, jitter from `pcg3d` of the integer cell, 3D, unweighted (`math.cs:849-975`, `CultMath.hlsl:440-503`). `smin_grad` (`math.cs:831-847`, `CultMath.hlsl:428-437`). `distance_to_segment` in C# only (`math.cs:319`; no HLSL twin). No weighted cellular, no diagram over explicit weighted sites, no site-graph gate. | +| B29 | How does CultMath own parity? | Two legs, one owner each (`packages/cultmath/docs/design.md:202-206`): HLSL against C# by the mirror test, which compiles the text of `CultMath.hlsl` as C# and compares float32 bit patterns (`design.md:118-124`), plus a clean `dxc -T lib_6_3` compile (`design.md:114-116`); GLSL against C# by golden fixtures run on a device. The mirror proves the composition in the text, not that a GPU agrees (`design.md:119-121`); device HLSL runs are consumers' today (Asura `gpu-snoise-parity`; Aetheria `FlowFieldParityVerify`). CultMath ships a Unity compute smoke (`packages/cultmath/scripts/unity-smoke/`). Aetheria pins `cultmath-unity-v0.4.1` (`Packages/manifest.json`). | +| B30 | Does Aetheria already keep a math twin outside CultMath? | Yes. `FlowField` uses CultMath's `math`, but its `Tri`, `Tri3` and `Tri3D` are Aetheria functions (`FlowField.cs:66-72`) whose HLSL twin is `Volumetric.cginc`, pinned by Aetheria's own `FlowFieldParityVerify` (`RelativeTolerance` 1.5e-4). That is a second parity owner beside CultMath. | +| B31 | What is parked in CultMath that a DAG would use? | The tape: steps 2-5 (expression graph lowered to bytecode with C# evaluators, pruning, an HLSL interpreter, meshing) are parked until Asura is stable (`docs/cultmath-tape-target.md:3-8`, ruling `tape-target-unparks-when-asura-stable`). "Truth and lowerings" (`:84-92`): the field is a typed CultCache document, and the CPU evaluator and the HLSL interpreter are lowerings of it. Step 1 (intervals) and affine forms are on main. | +| B32 | Do the candidates make filaments? (`docs/research/2026-10-14-trophic-dag-probe/`, README there; wells orbit, slope terms on, t = 0) | `skein` draws strands between neighbouring wells, bent by the flow (`density_skein.png`); `lattice` draws a fine cellular web, dense on the shoulders, dim elsewhere, with no long links (`density_lattice.png`); `skein-web` draws both: strands from well to well with a web hanging off them (`density_skein-web.png`, `layer_L2-krill.png`). Correlation with the B20 Physarum map at 64x32 blocks (this measures the envelope, not filaments: a block is 62 u, wider than a strand): band .512, lattice .359, skein-web .296, krill .277, skein .225, slack .171. Hosted krill at 16 u: band 2,075, skein-web 1,475, skein 639, lattice 419. | +| B33 | Does the site graph pop as wells orbit? | The hard Gabriel graph over the six bodies changes its edge set 29 times in 1,400 s (one gas-giant orbit), each change a strand appearing or vanishing whole (TD:33 [M], now measured). The soft gate (empty-disc margin through smoothstep(-.15, .15)) moves by at most .00075 per 1/60 s. | +| B34 | Do the candidates pop under the krill carriage? (`run1.log`, `win.log`, `cyc.log`; fade .1, 60 steps of 1/60 s from t = 100, flow read at the member's cycle start, cycle turns excluded) | Largest presence step: band .0035, slack .013, skein .054, lattice .124 (8 steps over .1), skein-web .493 (8 steps). The lattice steps are not discontinuities: a traced member's web value rises .03 per step for 5 steps, a thin weighted border sweeping a fixed candidate. Widening the search window to 7x7 changes nothing; lowering the weight boost to .6 removes the lattice steps (.047); reading the lattice drift at the member's cycle start, as the flow is read, removes them at full boost: lattice .011, skein-web .066, 0 steps over .1 (`cyc.log`). | +| B35 | How fast do hosted sets turn over? | Members changed in 1 s, all terms / flow frozen at t's cycle (wells only): band .032 / .032; skein .244 / .106; lattice .594 / .449 (.262 with the drift at cycle start); skein-web .305 / .186 (.216 with the drift at cycle start); slack .089 / .089. The strands ride their wells at about 6 u/s across 16 u candidates, so a tenth of strand members change each second through the fade: motion, not popping (B34). | +| B36 | What does a stencil predator cost? (Python, one evaluation) | krill .26 ms, minnows 3.19 ms (9 krill), sharks 29.3 ms (81 krill): each ring-stencil level multiplies the cost by 9. Hosted: minnows 274 of 5,432 candidates at 48 u; sharks 19 of 755 at 128 u; lanterns 36 of 3,063 at 64 u. | + +### Decisions made here, not forks + +- **The layers are the lore's web.** `OW` gives energy budgets (starlight, heat-light, gravity waves, mind-light), + a current with fronts, shear and slack, and the Skein: "a slime-mould web that grows toward food along the gravity + slope, linking well to well". The filament layer is therefore the Skein, a species of its own, and the layers + above read it. The DAG is donor-controlled: each level is a function of the levels below and never regulates + them (TD:56 [M]); a predator that thins its prey closes a cycle, which needs iteration (TD:56), so it is out. +- **Strands link wells; the wells' Voronoi borders are slack.** In the adhesion model, filaments are borders between + void cells and the mass sits at the vertices (TD:38 [V]). Here the mass is the wells, so filaments between wells + are the dual: edges of a site graph, the graphs the Physarum agents approximate (TD:9, prior doc). The wells' + weighted Voronoi borders are where pulls cancel, which `OW` gives to the ambushers (lanterns, void spiders). + With multiplicative weights the border round a small well is an Apollonius circle (TD:20 [M]; + `density_slack.png` shows the circles). +- **The site graph's gate is soft.** B33: a hard Gabriel graph pops a whole strand 29 times per gas-giant orbit; + the soft gate is continuous. The graph is computed per tick from the orbit positions `Zone` already caches per + tick (B24), over the zone's bodies only; wormhole mouths and belts join as sites in their own cuts. +- **Hunting radius is a stencil, or a widened kernel.** A predator reads its prey's density as the mean over a + fixed ring of taps: pointwise, exact by definition, the same in C# and HLSL. It costs 9x per level (B36), so a + stencil reads at most two levels down. Where the prey is a sum of Gaussian kernels (the Skein's strands) a + predator reads the same kernels widened instead, the exact blur of a Gaussian profile (TD:78 [M]). Grid-only + operators (blur of arbitrary fields, distance transforms, eikonal paths, jump flooding: TD:19, TD:78) are + presentation-only, never sim-visible. +- **Fast drift is read at the member's cycle start.** `habitat-no-pop-ruled` reads the krill's flow at its cycle + start. B34 extends it, measured: the flow warp and the lattice web's drift are read at the member's cycle start; + the slope band and the site graph, which move slowly and continuously, are read now and carried by the fade. +- **Nodes are CultMath functions; Aetheria composes.** Per Self's relay of the operator. Each primitive the DAG + needs is a CultMath function with an HLSL twin in `CultMath.hlsl`, pinned by CultMath's mirror test and dxc + compile (B29). Aetheria's `Habitat` is a composition of those calls and owns no math twin. Gaps to fill in + CultLib, not helpers in Aetheria: (1) a 2D weighted lattice cellular with the smooth-min blend (her node) on + `pcg3d` cells and zone time; (2) a weighted diagram over an explicit span of sites (F1, F2 and first-order border + distance, multiplicative and power weights); (3) the HLSL twin of `distance_to_segment`; (4) the soft Gabriel + gate over a site span. Gap (5): CultMath has no parity path for a consumer's composition, only for its own text + (B29), so a presenter that draws a composed layer (Skein glow, glim haze) waits on a CultMath-owned composition + check rather than a second `FlowFieldParityVerify`. B30's `Tri3D` twin is the existing instance of the same split. +- **The search window follows the weight range.** A multiplicatively weighted lattice node needs its window to + cover the weight ratio (TD:20 [M]); the probe's 1.6x boost needed no more than 5x5 (B34). The CultMath node states + the weight range it supports instead of trusting the caller. + +### Questions (admitted in the mind) + +`aetheria-fauna:question:skein-filament-construction` (which construction is the Skein; images per option in +`docs/research/2026-10-14-trophic-dag-probe/`) and `aetheria-fauna:question:trophic-graph-tape` (hand-written CultMath +compositions now and the tape later, or unpark the tape for this). + +### Cut sequence (r4, one line each; each is mapped when it is about to run; r3 lines 4-25 stand after line 7) + +1. `fauna-flow-field`: on master (`FlowField`, `Zone.Flow`, `FlowFieldParityVerify`). +2. `cultmath-trophic-nodes` (CultLib): gaps (1)-(4) above with HLSL twins, mirror tests and stated weight ranges; + a CultMath Unity tag. Waits on both questions. +3. `fauna-trophic-habitat` (Aetheria): the CultMath pin bump; `Habitat` composes environment, Skein, glim and krill + in C# at candidates; per-species settings records; the r3 candidates and fade; tests for the r4 invariants; the + per-candidate cost measured. Replaces r3 line 2. Waits on 2. +4. `fauna-krill-swarms`: as r3 line 3, on 3's krill hosting. +5. `cultmath-composition-parity` (CultLib): gap (5), a CultMath-owned check that a consumer's C# composition and its + HLSL twin agree on a device; `FlowField`'s `Tri3D` moves under it. Waits on Self's scoping. +6. `fauna-trophic-presenter`: the Skein and glim drawn in the nebula from the same composition. Waits on 3 and 5. +7. `fauna-minnow-schools`, then predators by stencil and ambushers on slack (r3 lines 6-8), re-mapped as species of + the DAG per `fauna-bestiary-open` and `OW`. diff --git a/docs/aetheria-fauna-target.md b/docs/aetheria-fauna-target.md index ce78364c9..fecfa982a 100644 --- a/docs/aetheria-fauna-target.md +++ b/docs/aetheria-fauna-target.md @@ -1,16 +1,30 @@ # Aetheria spaceborne fauna: target -Campaign `aetheria-fauna`, target content r3 (r1 was admitted from the r0 draft at `f9705d32`, r2 by Self; r3 is -admitted by Imagination). Repo `GameCult/Aetheria`, working branch `master`, cut branches -`eureka/aetheria-fauna-`. Written by Imagination (agents imagination-aeth-fauna, imagination-aeth-fauna-r2 and -imagination-aeth-fauna-r3, sessions self-2026-10-13-ag2 and self-2026-10-14-ag); r3 is against `origin/master` -`b836af48`. Body facts and probes are in `docs/aetheria-fauna-map.md` (cited below as `B`); prior art is +Campaign `aetheria-fauna`, target content r4 (r1 was admitted from the r0 draft at `f9705d32`, r2 by Self; r3 and +r4 are admitted by Imagination). Repo `GameCult/Aetheria`, working branch `master`, cut branches +`eureka/aetheria-fauna-`; the DAG's node primitives land in `GameCult/CultLib` (CultMath). Written by +Imagination (agents imagination-aeth-fauna, imagination-aeth-fauna-r2, imagination-aeth-fauna-r3 and +imagination-aeth-trophic-dag, sessions self-2026-10-13-ag2 and self-2026-10-14-ag); r4 is against `origin/master` +`7633408f` and CultLib `6c902fd4`. Trophic DAG prior art: `docs/research/2026-10-14-trophic-dag-filaments-prior-art.md` +(`TD:`); its probe: `docs/research/2026-10-14-trophic-dag-probe/`. Body facts and probes are in `docs/aetheria-fauna-map.md` (cited below as `B`); prior art is `docs/research/2026-10-13-fauna-prior-art.md` (`PA:`; `[M]` items are from memory and unverified), `docs/research/2026-10-13-fauna-placement-prior-art.md` (placement by simulation) and `docs/research/2026-10-14-fauna-placement-closed-form.md` (`CF:`; its tags: `[V]` read, `[S]` search summary only, `[M]` memory or derivation, unverified). This is the target and the model page. Open forks are questions in the mind under `aetheria-fauna`. +r4 applies `habitat-trophic-dag` (2026-10-14), after the r3 density drivers missed the look: + +> I'm not getting the filament feeling. I think we need a few layers of ecological abstraction, all deterministic, to +> author the various species with. The trophic levels should form a DAG which we can evaluate in either shader +> passes or in C# + +with `voronoi-primitive` (a weighted Voronoi node, her reference shader a sketch of the look) and her correction, +relayed by Self: "Parity between shader and C# is CultMath's whole raison d'etre". Placement becomes a layered, +donor-controlled trophic DAG whose filament layer is the lore's Skein, whose nodes are CultMath functions with HLSL +twins, and which Aetheria composes ("Placement law" below; map section r4). The krill carriage of +`habitat-no-pop-ruled` stands and is extended to every fast term (map B34). + r3 applies `fauna-placement-ruled` (2026-10-13), against the recommendation to run a Physarum pass at generation: > Nothing we generate ahead of time would suffice, because the level is constantly changing. I'd imagine we can come @@ -98,38 +112,56 @@ gather shows the shape of the wells and the currents, and where they are missing 10. **Placement tells an ecological story, and the level writes it as it moves.** Where fauna are is a closed form of the gravity slope and the flow, evaluated at zone time whenever a query, presenter or sensor asks (`fauna-placement-ruled`). Wells orbit, gas giants and suns ripple their wells (B24), and the flow scrolls, so the - pattern moves with the level: a placement generated ahead would go stale. Fauna gather on the shoulders of wells, - not in the cores and not in flat space, and the flow writes texture into that band. The law is in "Placement - law" below. + pattern moves with the level: a placement generated ahead would go stale. Life follows the Skein, the + slime-mould web strung from well to well, and the energy the wells give off; predators follow their prey, and + ambushers wait in the slack between wells. Nothing gathers in the cores, and open flat space away from the Skein + is nearly empty. The law is in "Placement law" below. 11. **No fauna in Sol.** Sol is a single flatspace zone with no fauna inside it; the strangeness begins at its edge. 12. **Harm and harvest go through existing owners.** A fauna attack is damage through the path a weapon's hit takes; a harvest yields items through the economy's yield path; neither gets a fauna-only damage or loot rule. ## Placement law -One law decides where fauna of a class are, from local quantities only (pointwise: the field and its derivatives at -a point, CF:6, CF:56): +Where fauna of a species are is decided by a **trophic DAG**: layers of fields, each a deterministic function of the +layers below it and of world facts, evaluated at a point at zone time when a query, presenter or sensor asks +(`habitat-trophic-dag`). Pointwise fields of slope and flow alone draw bands and blobs (B25); filaments need an +input that is not local, and the DAG takes it from the zone's bodies, which every point can read (map r4). -1. **A habitat density** in [0, 1] at a point, from the gravity slope there and the flow there. The slope term is a - band on the shoulders of the wells: the operator's own `Deposition(slope)` curve from her slime prefab (B20), zero - past the steep core and near zero in flat space. Which flow quantity shapes the band is the question - `habitat-density-driver`; the probe images are in the map (B25). -2. **Candidates with fixed thresholds.** Each class has a lattice of candidate seed points, one per cell, jittered by - a hash of the zone seed and the cell, each with its own fixed threshold from the same hash. A candidate hosts a - member when the density at it is above its threshold. This is Horizon Zero Dawn's placement shape: a density map - discretised by a fixed threshold pattern, deterministic and "locally stable" (CF:33 [V]). Because each threshold - is fixed, raising the density never removes a member, and a member appears or leaves only when the density at - its own point crosses its own threshold (CF:40: the design is [V], the nesting is a reading [M], so it is pinned - here as an invariant instead of trusted). The member's identity is the cell, so it is a `ChunkId` with no stored - state. -3. **No pop.** The density moves as fast as the flow scrolls (B25: under continuous evaluation a fifth of a - flow-shaped population changes in one second). A member never appears or vanishes at full presence; how it is - carried through a change is the question `habitat-no-pop`. -4. **Predators by prey.** Classes that hunt read their prey's density, not a second map: the ideal free - distribution puts predators in proportion to prey (CF:49-50, [S]; composed pointwise, CF:53 [M]). Sharks on krill - shoulders are one product of two pointwise densities. +| Layer | Fields (the first web, from the lore's Open Water; the bestiary stays open) | Reads | +|---|---|---| +| 0 Environment | Slope and the operator's `Deposition(slope)` band, the steep-core mask (`Zone.GetNormal`); the flow (`Zone.Flow`); the zone's bodies: positions from the per-tick orbit cache, gravity radius, kind; energy kernels round them: starlight inside a sun's light radius, heat-light on warm well floors. Later sources join here: gravity-wave rings (whales), mind-light round crews and stations (moths), wormhole mouths (upwelling), wrecks (succession). | World facts only | +| 1 Structure | **The Skein**: strands between neighbouring bodies (a site graph with a soft gate, each edge a Gaussian strand, bent by a Zel'dovich warp of the flow, TD:39 [V]) and a weighted lattice Voronoi web hanging off them (`voronoi-primitive`). **Slack**: the borders of the bodies' weighted Voronoi, where pulls cancel. Which construction is the Skein is the question `skein-filament-construction`. | Layer 0 | +| 2 Producers | Glim: energy times the Skein's feeding. | 0, 1 | +| 3 Grazers | Krill: the Skein times a saturating response to glim (Holling II, TD:55 [S]). Minnows: krill over a hunting ring. | 0-2 | +| 4 Predators and ambushers | Sharks: minnows over a wider ring, on the band. Lanterns and void spiders: slack times krill over a ring. | 0-3 | -The law is pure and costs nothing per tick: it runs per candidate when asked, as `Zone.ChunkPose` does for rocks. +1. **Operators.** A sim-visible layer uses only: pointwise arithmetic, remap curves, smoothstep, min, max and smooth + min, saturating responses; CultMath nodes (integer hash, weighted lattice cellular, weighted diagram over + explicit sites, segment kernel, soft site-graph gate); a **stencil** (the mean of a lower layer at the point and a + fixed ring of taps, at most two levels deep, 9x the cost per level, B36); and **kernel widening** where the prey + is a sum of Gaussian kernels (the exact blur, TD:78 [M]). Grid operators (blur of arbitrary fields, distance + transforms, eikonal paths, jump flooding, TD:19, TD:78), iteration and stored fields are presentation-only. +2. **Donor control.** A layer never reads itself or a layer above it: predators follow prey and never thin it in + the law (TD:56 [M]). Thinning is a sim event (harvest, attack wear), not a placement term. +3. **Candidates with fixed thresholds** (r3, kept). Each species has a lattice of candidate seed points, one per + cell, jittered by a hash of the zone seed and the cell, each with its own fixed threshold from the same hash; a + candidate hosts a member when its species' density there is above its threshold (Horizon Zero Dawn's shape, + CF:33 [V]; nesting pinned as an invariant, CF:40 [M]). The member's identity is the cell, a `ChunkId` with no + stored state. Candidate spacing is per species (16 u krill, 48 u minnows, 128 u sharks in the probe). +4. **No pop.** Terms that move fast are read at the member's own cycle start (`habitat-no-pop-ruled` for the flow, + extended to the flow warp and the lattice web's drift, measured in B34); terms that move slowly and continuously + (the slope band, the soft site graph, B33) are read now and carried by the fade. A member never appears or + vanishes at full presence. +5. **Nodes in CultMath, compositions in Aetheria.** Every node primitive is a CultMath function with an HLSL twin, + pinned by CultMath's mirror test (map B29); what CultMath lacks is a gap filled in CultLib (map r4, gaps 1-5), + never a helper in Aetheria. Each species' composition is C# in `ServerShared` (`Habitat`) calling those + functions, with its parameters in a per-species settings record; the sim evaluates only C#. A presenter that + draws a composed layer (the Skein's glow, glim haze) uses the composition's HLSL twin under a CultMath-owned + composition check. Whether compositions later become data on CultMath's parked tape is the question + `trophic-graph-tape`. + +The law costs nothing per tick: it runs per candidate when asked, as `Zone.ChunkPose` does for rocks; the site graph +is per zone per tick over a handful of bodies. ## Scale classes @@ -164,7 +196,8 @@ The rows follow the rulings `fauna-in-sim-ruled`, `fauna-identity-ruled` and `fa | **Swarm anchor** (later, sharks and whales) | Per field: a law that moves the field's frame across the flow. | Pure function of zone time plus sparse event records (fork `fauna-motion-model`). | `Zone`. Forbidden: per-member stepping. | | **Disturbance** (later) | `(member, time, push)`, sparse, as `ChunkWear` is. | Written when a hull passes through or a shot lands, then decays lazily against zone time. | `Zone`. It is read only by `Pose`. | | **Flow field** (new owner) | One law: `FlowField`, evaluated per zone by `Zone.Flow(position)` from the zone's world-law settings (`PlanetSettings.Flow`). | Settings authored in `Settings.asset`; time is zone time. Never stored per position. | `Zone`. Derived: every shader flow uniform (`_FlowScroll` and the rest). Forbidden: `VolumeSampling._flowScroll`, `ZoneEnvironment.Flow`, any frame-time accumulator. | -| **Habitat** (new owner, a law) | `Habitat` in `ServerShared`: a class's `HabitatSettings`, a point's slope and flow quantities, a candidate's hashed seed point and threshold. | Never stored: evaluated at zone time per candidate when asked. | `Habitat` decides density and hosting; `Zone` supplies the local quantities from `GetNormal` and `Flow`. Forbidden: a generation pass, a saved or cached habitat map, a per-tick sweep. | +| **Habitat** (new owner, a law: the trophic DAG) | `Habitat` in `ServerShared`: one composition per species over CultMath nodes, a species' settings record, a candidate's hashed seed point and threshold. | Never stored: evaluated at zone time per candidate when asked. | `Habitat` decides density and hosting; `Zone` supplies world facts (`GetNormal`, `Flow`, body positions from the per-tick orbit cache); CultMath owns every node and its HLSL twin. Forbidden: a generation pass, a saved or cached habitat map, a per-tick sweep, a math twin in Aetheria, a grid operator in a sim-visible layer. | +| **Skein site graph** (derived) | Per zone: the soft gate of each pair of bodies (map r4). | Recomputed per tick from orbit positions; never saved. | `Habitat`, through a CultMath gate function. Forbidden: a hard (0/1) gate; a cached graph across ticks. | | **Attack** (later, per class) | `(member or swarm, ship, time)`, an event. | Raised by a class law when its trigger holds; applied through the ordinary damage path. | `Zone`. Forbidden: a fauna-only damage rule. | | **Harvest yield** (later) | Item kinds in the catalog, per fauna kind. | A member broken or netted by harvesting gear yields items through the economy's yield table; the field refills by respawn. | The catalog and the yield table. Forbidden: fauna-only loot code. | | **Fauna background kind** (aetheria-ew) | Entries of aetheria-ew's `BackgroundKind` for fauna classes (that target's row says "more by ruling"). | Static data. | `GameplaySettings`, per aetheria-ew. Its envelope gains a motion term per class (fork `fauna-disguise-path`). | @@ -190,19 +223,33 @@ The rows follow the rulings `fauna-in-sim-ruled`, `fauna-identity-ruled` and `fa - `flow-step-free`: the flow at a position and zone time does not depend on step size or frame rate. - `fauna-none-in-sol`: Sol's zone hosts no fauna kind, so no candidate there is ever hosted. - `fauna-placement-closed-form`: whether a candidate hosts a member at zone time t is a pure function of the zone - seed, the class's habitat settings, and the gravity slope and flow at the candidate at t. Nothing about placement - is generated ahead, saved, cached across ticks or stepped. -- `fauna-placement-story`: habitat density is zero in well cores, below every candidate's threshold in flat space, - and highest on well shoulders, and it changes when the slope or the flow at the point changes. + seed, the species' settings, the world facts at t (slope, flow at the member's cycle start, the bodies' positions) + and the lower layers evaluated at bounded points. Nothing about placement is generated ahead, saved, cached across + ticks or stepped. +- `fauna-placement-story`: density is zero in well cores; along a Skein strand between two bodies it exceeds the + density at a point of equal slope off the strand; ambushers' density peaks on the slack borders; a predator's + density rises with its prey's within its hunting ring. - `fauna-placement-nested`: each candidate's threshold is fixed, so raising the density at a candidate never un-hosts it and lowering it never hosts it. - `fauna-placement-no-pop`: a member never appears or vanishes at full presence; every change of hosting is carried - through a fade. + through a fade. Within a member's cycle its presence moves by at most a stated bound per 1/60 s step as the bodies + orbit (the probe's worst was .066, map B34). +- `trophic-dag-acyclic`: each layer reads only world facts and the layers below it; no layer reads itself or a layer + above it. +- `trophic-dag-pointwise`: every sim-visible layer is evaluated at a point from world facts, the per-tick site + graph and a bounded number of evaluations of lower layers (stencils at most two levels deep); no grid, no + iteration, no stored field. +- `trophic-nodes-in-cultmath`: every node primitive a layer calls is a CultMath function with an HLSL twin pinned by + CultMath's parity checks; Aetheria holds compositions only. +- `skein-gate-continuous`: the Skein's site-graph gates are continuous in the bodies' positions, so no strand + appears or vanishes as the bodies orbit. ## Not in scope - Ambient presentation-only life (the slime trails) beyond leaving it untouched; the habitat law reuses its `Deposition(slope)` curve, but the presenter stays presentation. +- Population dynamics: no layer is stepped or regulated by the layer above it; harvest and attack wear thin + members as sim events through `Zone.Wear`, not as placement terms. - Phantom positions (aetheria-ew `ew-where-lies-ruled`). - Ships drifting with the flow (`flow-moves-what-ruled`: ships do not; floating items, mines and vented clouds do, in a later cut). @@ -214,4 +261,4 @@ Attacks and harvest are in scope as later cuts, designed per class (`fauna-inter ## Cut sequence -The map's r3 cut sequence has one line per cut; each is mapped when it is about to run. +The map's r4 cut sequence (with the r3 lines it keeps) has one line per cut; each is mapped when it is about to run. diff --git a/docs/research/2026-10-14-trophic-dag-probe/README.md b/docs/research/2026-10-14-trophic-dag-probe/README.md new file mode 100644 index 000000000..83ce043a0 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/README.md @@ -0,0 +1,40 @@ +# Trophic DAG probe (2026-10-14) + +Serves rulings `aetheria-fauna:ruling:habitat-trophic-dag` and `:voronoi-primitive`, and the questions +`aetheria-fauna:question:skein-filament-construction` and `:trophic-graph-tape`. Facts are map rows B28-B36 in +`docs/aetheria-fauna-map.md`; prior art is `docs/research/2026-10-14-trophic-dag-filaments-prior-art.md` (`TD:`). + +`trophic_probe.py` imports `habitat_probe.py` (the float64 transliteration of `Zone.GetHeight`/`GetNormal` and +`FlowField`, in `../2026-10-14-fauna-habitat-probe/`) and adds what that probe lacked: the wells orbit, as +`Zone.GetOrbitPosition` moves them (period = distance in seconds, `Settings.asset` `OrbitPeriod` exponent 1, +multiplier 1; about 6.3 u/s for every planet). The flow's slope terms are on (swirl 6, slide 1.5, as B25g). Its hash +is the probe's own integer hash, not CultMath `pcg3d`; images are qualitative evidence for a C# law, not the law. + +Filament candidates (krill habitat density, each times the steep-core mask): + +| Name | Construction | +|---|---| +| `band` | The operator's `Deposition(slope)` alone (r3's baseline, B25a). Control, not an option. | +| `skein` | Strands: a Gabriel graph over the zone's wells (TD:31 [V]) with a soft gate (the empty-disc margin through a smoothstep), each edge a Gaussian strand of width 28 u + 3 % of its length, bent by a Zel'dovich warp of the flow, `x - 20 s * u(x)` (TD:39 [V]). | +| `skein-hard` | `skein` with the hard 0/1 Gabriel gate, for the pop count. | +| `lattice` | The operator's reference node: a lattice smooth Voronoi (260 u cells) with her hashed per-site multiplicative weight, raised by up to 1.6x near wells so the cells shrink on the shoulders; output is the border (her smooth-min gap, and F2 - F1 at width .1 cell), warped by the flow for 6 s, times the band widened (`band^.75`). Sites drift on zone time (.05 rad/s). | +| `skein-web` | `skein` plus `lattice` hanging off the strands: the web is gated by a strand halo (4x width) or the band. | +| `slack` | A multiplicatively weighted Voronoi of the wells (weight = gravity radius), distance to the cell border to first order, `(F2 - F1) / |grad(F2 - F1)|`: the saddles where pulls cancel (Apollonius circles round small wells, TD:20 [M]). | + +Trophic layers over `skein-web` (`layer_*.png`): `L1-glim` (starlight inside the sun's light radius plus heat-light on +warm well floors, fed along the Skein), `L2-krill` (Skein times a Holling-II saturation of glim, TD:55 [S]), +`L2-minnows` (Holling of krill averaged over the point and 8 taps on a 48 u ring), `L3-sharks` (minnows over a +160 u ring, times the band), `L3-lanterns` (slack times krill over a 120 u ring). + +Images: `density_.png` (zone 4000 u across, t = 0, absolute density, red = bodies), `members_.png` +(hosted krill candidates at 16 u, fixed thresholds .1 + .9h), `layer_.png`, `members_L2-minnows.png` (48 u), +`members_L3-sharks.png` (128 u), `members_L3-lanterns.png` (64 u). + +Logs: `run1.log` (the full run, 1,211 s), `win.log` (search window and weight range against presence steps), +`cyc.log` (the lattice drift read at the member's cycle start). + +To rerun: copy this folder and `../2026-10-14-fauna-habitat-probe/habitat_probe.py` into one scratch directory +(the scripts write PNGs next to themselves), then `py -3 -u trophic_probe.py 256` (about 20 minutes), +`py -3 -u winprobe.py`, `py -3 -u cycprobe.py`. `habitat_probe.py` reads the B20 Physarum map from +`C:\aeth-wt\fauna-target-r3\docs\research\2026-10-13-fauna-placement-probe\output.txt`; edit the path if the +worktree moved. Python 3 only, no packages. diff --git a/docs/research/2026-10-14-trophic-dag-probe/cyc.log b/docs/research/2026-10-14-trophic-dag-probe/cyc.log new file mode 100644 index 000000000..2cbf08084 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/cyc.log @@ -0,0 +1,2 @@ +web at cycle start, lattice: presence step max 0.0113, 0 steps > .1 of 1500; members 435, changed in 1 s 0.262 +web at cycle start, skein-web: presence step max 0.0656, 0 steps > .1 of 1500; members 1475, changed in 1 s 0.216 diff --git a/docs/research/2026-10-14-trophic-dag-probe/cycprobe.py b/docs/research/2026-10-14-trophic-dag-probe/cycprobe.py new file mode 100644 index 000000000..0bd8da727 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/cycprobe.py @@ -0,0 +1,7 @@ +import sys; sys.argv=['x','64'] +import trophic_probe as T +cands=T.cand_list(16.0); T.WEB_AT_CYCLE=True +for name in ("lattice","skein-web"): + w,b,n=T.pop_probe(T.CANDS[name],cands,100.0,1500) + a=T.members(T.CANDS[name],cands,0.0); b1=T.members(T.CANDS[name],cands,1.0) + print(f"web at cycle start, {name}: presence step max {w:.4f}, {b} steps > .1 of {n}; members {len(a)}, changed in 1 s {len(a^b1)/max(1,len(a)):.3f}", flush=True) diff --git a/docs/research/2026-10-14-trophic-dag-probe/density_band.png b/docs/research/2026-10-14-trophic-dag-probe/density_band.png new file mode 100644 index 000000000..044373741 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/density_band.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dc4ac288d82c01b1a72e6702bd2a46715e1dba4940db4154d295dcb8ffa8f54 +size 23221 diff --git a/docs/research/2026-10-14-trophic-dag-probe/density_lattice.png b/docs/research/2026-10-14-trophic-dag-probe/density_lattice.png new file mode 100644 index 000000000..6cd74e11c --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/density_lattice.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:335a94f8bae2dd0457ddf8e947fa85533163499b0c4a5e10ca937b1e644f54ec +size 30570 diff --git a/docs/research/2026-10-14-trophic-dag-probe/density_skein-web.png b/docs/research/2026-10-14-trophic-dag-probe/density_skein-web.png new file mode 100644 index 000000000..d0a8371a7 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/density_skein-web.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5599e33d263b7d894b600379d613870e3d87f8a2db1d70750fa9b319df34993e +size 29289 diff --git a/docs/research/2026-10-14-trophic-dag-probe/density_skein.png b/docs/research/2026-10-14-trophic-dag-probe/density_skein.png new file mode 100644 index 000000000..3950d7cc0 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/density_skein.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71ab9a50b00bc6b27a651e3ff15392d985e15e7a0f848287713e12d9f36e8648 +size 9376 diff --git a/docs/research/2026-10-14-trophic-dag-probe/density_slack.png b/docs/research/2026-10-14-trophic-dag-probe/density_slack.png new file mode 100644 index 000000000..dbdb2f316 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/density_slack.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ea06510cf51e1f5a4ecfc07f23f280c09662e5b9b63728443c08534adb2144d +size 25435 diff --git a/docs/research/2026-10-14-trophic-dag-probe/layer_L1-glim.png b/docs/research/2026-10-14-trophic-dag-probe/layer_L1-glim.png new file mode 100644 index 000000000..a2f35ad7d --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/layer_L1-glim.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d093a7fe8cea8a91a82adcf69be9da5930dbb56c11e96be3ebc80cd8592534af +size 39049 diff --git a/docs/research/2026-10-14-trophic-dag-probe/layer_L2-krill.png b/docs/research/2026-10-14-trophic-dag-probe/layer_L2-krill.png new file mode 100644 index 000000000..417729d54 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/layer_L2-krill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cf201e2af3f9423a6cd5082783d2d2dbb9cb617b98e292bb59c0a51a399d226 +size 22290 diff --git a/docs/research/2026-10-14-trophic-dag-probe/layer_L2-minnows.png b/docs/research/2026-10-14-trophic-dag-probe/layer_L2-minnows.png new file mode 100644 index 000000000..ee3241f29 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/layer_L2-minnows.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67e963a38da70fd09ab4bf488c5270a65cdfa7f8a79e6a0b1f21db24edf07131 +size 10383 diff --git a/docs/research/2026-10-14-trophic-dag-probe/layer_L3-lanterns.png b/docs/research/2026-10-14-trophic-dag-probe/layer_L3-lanterns.png new file mode 100644 index 000000000..f3bc763b0 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/layer_L3-lanterns.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a68f520e0219ce10e23d8ff7a4cd50d0f10526686f2f2573f31e0eb43a14885 +size 3712 diff --git a/docs/research/2026-10-14-trophic-dag-probe/layer_L3-sharks.png b/docs/research/2026-10-14-trophic-dag-probe/layer_L3-sharks.png new file mode 100644 index 000000000..9030fb036 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/layer_L3-sharks.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1ff400fd25fe4ae1f22ad93dc7d11f19d13653eafe38f18ed20125743daa16b +size 9034 diff --git a/docs/research/2026-10-14-trophic-dag-probe/members_L2-minnows.png b/docs/research/2026-10-14-trophic-dag-probe/members_L2-minnows.png new file mode 100644 index 000000000..4b7729c84 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/members_L2-minnows.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f376b273cbaaa35eadc51aa4aeb670d91eee1ed8294a852aa659a6fdab5633d +size 3284 diff --git a/docs/research/2026-10-14-trophic-dag-probe/members_L3-lanterns.png b/docs/research/2026-10-14-trophic-dag-probe/members_L3-lanterns.png new file mode 100644 index 000000000..f64af5135 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/members_L3-lanterns.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b366bdb9fc3646f2f9c3426e1d935e82f05189fdec950f80544dd61544aeefbc +size 2168 diff --git a/docs/research/2026-10-14-trophic-dag-probe/members_L3-sharks.png b/docs/research/2026-10-14-trophic-dag-probe/members_L3-sharks.png new file mode 100644 index 000000000..8df11a669 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/members_L3-sharks.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf783977fac415abbaa49ba99ddfbec5c839d7c4fdd392085f500e4cbede01ea +size 2093 diff --git a/docs/research/2026-10-14-trophic-dag-probe/members_band.png b/docs/research/2026-10-14-trophic-dag-probe/members_band.png new file mode 100644 index 000000000..18c76262e --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/members_band.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a41e7b140c9f82851be50c770e69cb75e037284a072a72917bfd05cd3592530 +size 5588 diff --git a/docs/research/2026-10-14-trophic-dag-probe/members_lattice.png b/docs/research/2026-10-14-trophic-dag-probe/members_lattice.png new file mode 100644 index 000000000..3f75174f6 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/members_lattice.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3caff4ec7a2608651a5ef17842c839a471372d64d98a5c522c7856a05c798fec +size 2754 diff --git a/docs/research/2026-10-14-trophic-dag-probe/members_skein-web.png b/docs/research/2026-10-14-trophic-dag-probe/members_skein-web.png new file mode 100644 index 000000000..679501ad7 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/members_skein-web.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a597734d3dd6304b428787a3b21e4b9f4b5b8208d63809e041f17afa924e0bf9 +size 4428 diff --git a/docs/research/2026-10-14-trophic-dag-probe/members_skein.png b/docs/research/2026-10-14-trophic-dag-probe/members_skein.png new file mode 100644 index 000000000..0d283382a --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/members_skein.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1071214f9d4dd7cd1a6ba67ff9ec67fc623eba9b44b9f4734938c6177a55bd91 +size 3137 diff --git a/docs/research/2026-10-14-trophic-dag-probe/members_slack.png b/docs/research/2026-10-14-trophic-dag-probe/members_slack.png new file mode 100644 index 000000000..7371680c5 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/members_slack.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f155d2a5bbaa53c1bf32d55a96ce94d38cdbc034290756b245ed9cf6fa76d82 +size 7487 diff --git a/docs/research/2026-10-14-trophic-dag-probe/run1.log b/docs/research/2026-10-14-trophic-dag-probe/run1.log new file mode 100644 index 000000000..50dc816b4 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/run1.log @@ -0,0 +1,27 @@ +grid 256, wells orbit (period = distance s), slope terms on, TAU 20.0 s, lattice 260.0 u +gabriel over 1400 s: hard edge-set changes 29; soft gate largest step per 1/60 s 0.00075 +49109 krill candidates at 16 u + band wells-only (flow frozen at t's cycle): changed in 1 s 0.0323, 60 s 0.891 +band: corr physarumA 0.512; members 2075; changed in 1 s 0.032, 8 s 0.283, 60 s 0.891; presence step max 0.0035 (0 steps > .1, 2500 near-threshold candidates); 23s + skein wells-only (flow frozen at t's cycle): changed in 1 s 0.1064, 60 s 1.498 +skein: corr physarumA 0.225; members 639; changed in 1 s 0.244, 8 s 1.102, 60 s 1.549; presence step max 0.0536 (0 steps > .1, 2500 near-threshold candidates); 58s + skein-hard wells-only (flow frozen at t's cycle): changed in 1 s 0.1089, 60 s 1.495 +skein-hard: corr physarumA 0.225; members 643; changed in 1 s 0.244, 8 s 1.098, 60 s 1.546; presence step max 0.0536 (0 steps > .1, 2500 near-threshold candidates); 55s + lattice wells-only (flow frozen at t's cycle): changed in 1 s 0.4487, 60 s 1.871 +lattice: corr physarumA 0.359; members 419; changed in 1 s 0.594, 8 s 1.680, 60 s 1.874; presence step max 0.1236 (8 steps > .1, 2500 near-threshold candidates); 179s + skein-web wells-only (flow frozen at t's cycle): changed in 1 s 0.1864, 60 s 1.504 +skein-web: corr physarumA 0.296; members 1475; changed in 1 s 0.305, 8 s 1.144, 60 s 1.534; presence step max 0.4933 (8 steps > .1, 2500 near-threshold candidates); 217s + slack wells-only (flow frozen at t's cycle): changed in 1 s 0.0893, 60 s 1.756 +slack: corr physarumA 0.171; members 3942; changed in 1 s 0.089, 8 s 0.672, 60 s 1.756; presence step max 0.0132 (0 steps > .1, 2500 near-threshold candidates); 26s +L1-glim: grid 256, corr physarumA 0.485; 18s +L2-krill: grid 256, corr physarumA 0.277; 19s +L2-minnows: grid 128, corr physarumA 0.360; 42s +L3-sharks: grid 128, corr physarumA 0.403; 376s +L3-lanterns: grid 128, corr physarumA 0.148; 42s +members L2-minnows: 274 of 5432 at 48 u; changed in 1 s 0.124, 8 s 0.478; 52s +members L3-sharks: 19 of 755 at 128 u; changed in 1 s 0.105, 8 s 0.526; 65s +members L3-lanterns: 36 of 3063 at 64 u; changed in 1 s 0.139, 8 s 0.861; 31s +cost krill: 0.26 ms per evaluation (Python) +cost minnows: 3.19 ms per evaluation (Python) +cost sharks: 29.26 ms per evaluation (Python) +total 1211s diff --git a/docs/research/2026-10-14-trophic-dag-probe/trophic_probe.py b/docs/research/2026-10-14-trophic-dag-probe/trophic_probe.py new file mode 100644 index 000000000..8498ea222 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/trophic_probe.py @@ -0,0 +1,307 @@ +"""Trophic DAG probe (Imagination, aetheria-fauna r4). Pure Python 3, no packages. + +Evaluates candidate filament nodes and a layered trophic DAG on the B20 zone (habitat_probe.py's transliteration +of Zone.GetHeight/GetNormal and FlowField, float64), with the wells ORBITING as Zone.GetOrbitPosition moves them +(period = distance seconds, Settings.asset OrbitPeriod exponent 1, multiplier 1). Writes per-layer density images, +a members image per candidate, and prints per candidate: correlation with the B20 Physarum maps, hosted members, +turnover in 1 s / 8 s / 60 s and the largest presence step per 1/60 s (fade band .1). Hashes are the probe's own +integer hash, not CultMath pcg; images are qualitative evidence for the C# law, not the law. + + py -3 trophic_probe.py [grid=256] [only=comma,list] +""" +import sys, os, math, time +ARGS = sys.argv[1:] +sys.argv = [sys.argv[0], "256"] +import habitat_probe as H + +OUT = os.path.dirname(os.path.abspath(__file__)) +G = int(ARGS[0]) if ARGS else 256 +ONLY = ARGS[1].split(",") if len(ARGS) > 1 else None +R = H.R +H.FLOW.update(swirl=6.0, slope=1.5) # flow-slope-terms-ruled: slope terms on (B25g's values) +TAU = 20.0 # Zel'dovich warp time (s): strands bent by the flow, x -> x - TAU u(x) +WEB_TAU = 6.0 # the lattice web is warped less: its cells are smaller than a strand's run +CYCLE = 8.0 # krill cycle period (s), FlowSettings.Period +FADE = .1 + +def smooth(a, b, v): + if a == b: return 1.0 if v >= b else 0.0 + v = (v - a) / (b - a); v = 0.0 if v < 0 else 1.0 if v > 1 else v; return v * v * (3 - 2 * v) + +# ---------------- L0: environment (wells move) ---------------- +def set_time(t): + for (kind, m, d, ph), w in zip(H.BODIES, H.WELLS): + a = ph + (2 * math.pi * t / d if d > 0 else 0.0) + w["pos"] = (math.cos(a) * d, math.sin(a) * d) + pairs_cache.clear() + +def slope(x, z, t): + n = H.normal(x, z, t); return math.acos(max(-1.0, min(1.0, n[1]))) / (math.pi / 2) + +def band(s): return H.deposition(s) / 2.0 # the operator's Deposition(slope), [0, 1]; zero past slope .8 +def core(s): return smooth(.7, .8, s) # steep core mask +def envelope(b): return b ** .75 # the band, widened: where the web may live + +def flow_at(x, z, t, sc): return H.flow_xz(x, z, t, sc) + +# ---------------- L0.5 filament nodes ---------------- +pairs_cache = {} +def pairs(hard): + """Per-zone site graph over the wells: Gabriel graph, gate soft (margin smoothstep) or hard (0/1). + Recomputed per tick from orbit positions (the zone already caches those per tick).""" + if hard in pairs_cache: return pairs_cache[hard] + W = H.WELLS; out = [] + for i in range(len(W)): + for j in range(i + 1, len(W)): + a, b = W[i]["pos"], W[j]["pos"]; L = math.dist(a, b); m = ((a[0] + b[0]) / 2, (a[1] + b[1]) / 2); r = L / 2 + margin = min([(math.dist(m, W[k]["pos"]) - r) / r for k in range(len(W)) if k not in (i, j)] or [9.0]) + gate = (1.0 if margin > 0 else 0.0) if hard else smooth(-.15, .15, margin) + if gate > 0: out.append((a, b, L, gate, 28.0 + .03 * L)) + pairs_cache[hard] = out; return out + +def seg_dist(p, a, b): + ax, az = b[0] - a[0], b[1] - a[1]; L2 = ax * ax + az * az + u = 0.0 if L2 == 0 else max(0.0, min(1.0, ((p[0] - a[0]) * ax + (p[1] - a[1]) * az) / L2)) + return math.hypot(p[0] - a[0] - u * ax, p[1] - a[1] - u * az) + +def strands(q, hard=False, widen=1.0): + best = 0.0 + for a, b, L, gate, sig in pairs(hard): + d = seg_dist(q, a, b) / (sig * widen); best = max(best, gate * math.exp(-d * d)) + return best + +def warp(x, z, t, sc, tau=None): + ux, uz = flow_at(x, z, t, sc); tau = TAU if tau is None else tau; return (x - tau * ux, z - tau * uz) + +def well_slack(x, z): + """Multiplicatively weighted Voronoi of the wells (weight = gravity radius): distance to the cell border, + first order (F2 - F1) / |grad(F2 - F1)|. Borders are Apollonius circles round the small wells.""" + ds = [] + for w in H.WELLS: + dx, dz = x - w["pos"][0], z - w["pos"][1]; r = math.hypot(dx, dz) or 1e-6 + ds.append((r / w["r"], dx / (r * w["r"]), dz / (r * w["r"]))) + ds.sort() + (d1, gx1, gz1), (d2, gx2, gz2) = ds[0], ds[1] + gl = math.hypot(gx2 - gx1, gz2 - gz1) or 1e-9 + return (d2 - d1) / gl + +LAT = 260.0 # lattice Voronoi cell (u) +OMEGA = .05 # site drift rate (rad/s), zone time +KW = .35 # smooth-min width (cell units), her w +WEB_AT_CYCLE = False # read the lattice web's drift at the member's cycle start (as the flow) +WIN = 2 # search window half-size (cells); her reference uses 2 +BOOST = 1.6 # site weight raised near wells by up to this factor +EW = .10 # border line half-width (cell units) +def lattice_web(q, t): + """The operator's reference node: lattice smooth Voronoi, per-site multiplicative weight on distance, smooth-min + blend. Weight = her hashed .5-1, raised near wells so cells shrink there (denser web on the shoulders). + Output: 1 on cell borders, falling into the cells, from F1 minus the smooth minimum.""" + if CUR_T0[0] is not None: t = CUR_T0[0] + px, pz = q[0] / LAT, q[1] / LAT; nx, nz = math.floor(px), math.floor(pz); fx, fz = px - nx, pz - nz + m = 8.0; f1 = 8.0; f2 = 8.0 + for j in range(-WIN, WIN + 1): + for i in range(-WIN, WIN + 1): + cx, cz = nx + i, nz + j + ox, oz = H.hash01(cx, cz, 11), H.hash01(cx, cz, 12) + wt = ox * .5 + .5 + sx, sz = (cx + .5) * LAT, (cz + .5) * LAT + prox = max(math.exp(-(math.dist((sx, sz), w["pos"]) / (2.2 * .103 * w["r"])) ** 2) for w in H.WELLS) + wt *= 1.0 + BOOST * prox + ox = .5 + .5 * math.sin(OMEGA * t + 6.2831 * ox); oz = .5 + .5 * math.sin(OMEGA * t + 6.2831 * oz) + d = math.hypot(i - fx + ox, j - fz + oz) * wt + if d < f1: f1, f2 = d, f1 + elif d < f2: f2 = d + h = smooth(0.0, 1.0, .5 + .5 * (m - d) / KW) + m = m + (d - m) * h - h * (1 - h) * KW / (1 + 3 * KW) + gap0 = .25 * KW / (1 + 3 * KW) + soft = max(0.0, min(1.0, (f1 - m) / gap0)) # her smooth-min gap: 1 on borders, soft falloff + return max(soft ** 2, math.exp(-((f2 - f1) / EW) ** 2)) # F2 - F1 border line of width EW (cell units) + +# ---------------- candidate krill habitat densities (filament candidates) ---------------- +def env(x, z, t): + s = slope(x, z, t); return s, band(s), core(s) + +def D_band(x, z, t, sc): + s, b, c = env(x, z, t); return b + +def D_skein(x, z, t, sc, hard=False): + s, b, c = env(x, z, t); q = warp(x, z, t, sc) + return strands(q, hard) * (1 - c) * (.55 + .45 * b) + +def D_skein_hard(x, z, t, sc): return D_skein(x, z, t, sc, True) + +def D_lattice(x, z, t, sc): + s, b, c = env(x, z, t); q = warp(x, z, t, sc, WEB_TAU) + return lattice_web(q, t) * (1 - c) * envelope(b) + +def skein_web(x, z, t, sc): + """Strands between wells plus the lattice web hanging off them (web gated by a wide strand halo and the band).""" + s, b, c = env(x, z, t); q = warp(x, z, t, sc) + st = strands(q); halo = strands(q, widen=4.0) + web = lattice_web(warp(x, z, t, sc, WEB_TAU), t) * max(halo, b) + return max(st, .8 * web) * (1 - c), s, b, c + +def D_skein_web(x, z, t, sc): return skein_web(x, z, t, sc)[0] + +def D_slack(x, z, t, sc): + s, b, c = env(x, z, t); d = well_slack(x, z); return math.exp(-(d / 70.0) ** 2) * (1 - c) + +# ---------------- L1-L3: the trophic DAG over skein_web ---------------- +def glim(x, z, t, sc): + """Producers: starlight (sun's light radius) plus heat-light on warm well floors, fed along the skein.""" + sun = H.WELLS[0]; light = math.exp(-(math.dist((x, z), sun["pos"]) / (300 * 14152 ** .25 * .5)) ** 2) + heat = sum(math.exp(-(math.dist((x, z), w["pos"]) / (.16 * w["r"])) ** 2) for w in H.WELLS[1:]) + F, s, b, c = skein_web(x, z, t, sc) + return min(1.0, (.35 * light + .9 * heat) * (.3 + .7 * F) + .35 * F), F, b, c + +def holling(p, k=.25): return p * (1 + k) / (p + k) # Holling II saturation, 0 -> 0, 1 -> 1 + +def krill(x, z, t, sc): + g, F, b, c = glim(x, z, t, sc); return F * holling(g) * (1 - c) + +RING = [(math.cos(2 * math.pi * k / 8), math.sin(2 * math.pi * k / 8)) for k in range(8)] +def stencil(f, x, z, t, sc, r): + """Prey density over a hunting radius: the mean of the prey field at the point and 8 taps on a ring of radius r + (pointwise: 9 evaluations of the layer below, no grid).""" + return (f(x, z, t, sc) + sum(f(x + r * cx, z + r * cz, t, sc) for cx, cz in RING)) / 9.0 + +def minnows(x, z, t, sc): + s, b, c = env(x, z, t); return min(1.0, 1.6 * holling(stencil(krill, x, z, t, sc, 48.0)) * (.4 + .6 * b)) + +def sharks(x, z, t, sc): + s, b, c = env(x, z, t); return min(1.0, 2.0 * stencil(minnows, x, z, t, sc, 160.0) * (.3 + .7 * b)) + +def lanterns(x, z, t, sc): + return D_slack(x, z, t, sc) * min(1.0, 2.5 * stencil(krill, x, z, t, sc, 120.0)) + +CANDS = {"band": D_band, "skein": D_skein, "skein-hard": D_skein_hard, "lattice": D_lattice, + "skein-web": D_skein_web, "slack": D_slack} +LAYERS = {"L1-glim": lambda x, z, t, sc: glim(x, z, t, sc)[0], "L2-krill": krill, "L2-minnows": minnows, + "L3-sharks": sharks, "L3-lanterns": lanterns} + +# ---------------- rendering and measurement ---------------- +def grid(f, t, g): + set_time(t); sc = H.scroll(t); vals = [] + for j in range(g): + row = [] + for i in range(g): + x = ((i + .5) / g - .5) * 2 * R; z = ((j + .5) / g - .5) * 2 * R + row.append(None if x * x + z * z > R * R else f(x, z, t, sc)) + vals.append(row) + return vals + +def save(vals, name, g): + px = [(0, 0, 0) if v is None else H.ramp(v) for r in vals for v in r] + H.mark_bodies(px, g); path = os.path.join(OUT, name + ".png"); H.write_png(path, g, g, px); return path + +def phys_corr(vals, g): + phys = [c for r in H.physarum_ascii("physarum A") for c in r] + bw, bh = g // 64, g // 32; blocks = [] + for by in range(32): + for bx in range(64): + cell = [vals[y][x] or 0 for y in range(by * bh, (by + 1) * bh) for x in range(bx * bw, (bx + 1) * bw)] + blocks.append(sum(cell) / len(cell)) + return H.corr(blocks, phys) + +def cand_list(C): + n = int(R / C); out = [] + for j in range(-n, n): + for i in range(-n, n): + x = (i + H.hash01(i, j, 1)) * C; z = (j + H.hash01(i, j, 2)) * C + if x * x + z * z <= R * R: out.append((i, j, x, z, .1 + .9 * H.hash01(i, j, 3), H.hash01(i, j, 4))) + return out + +def cyc_scroll(t, ph): + """habitat-no-pop-ruled: the flow term is read at the member's own cycle start.""" + t0 = t - ((t / CYCLE + ph) % 1.0) * CYCLE; return H.scroll(t0), t0 + +CUR_T0 = [None] +def density_at(f, c, t): + sc, t0 = cyc_scroll(t, c[5]); CUR_T0[0] = t0 if WEB_AT_CYCLE else None + try: return f(c[2], c[3], t, sc), t0 + finally: CUR_T0[0] = None + +def members(f, cands, t, tref=None): + set_time(t) + if tref is None: return {(c[0], c[1]) for c in cands if density_at(f, c, t)[0] > c[4]} + return {(c[0], c[1]) for c in cands if f(c[2], c[3], t, cyc_scroll(tref, c[5])[0]) > c[4]} # flow frozen at tref's cycle + +def presence(d, th): return smooth(th - FADE, th + FADE, d) + +def pop_probe(f, cands, t, nmax=2500): + set_time(t) + near = [c for c in cands if abs(density_at(f, c, t)[0] - c[4]) < .3][:nmax] + prev = {}; worst = 0.0; big = 0 + for k in range(61): + tt = t + k / 60.0; set_time(tt) + for c in near: + d, t0 = density_at(f, c, tt); p = presence(d, c[4]); key = (c[0], c[1]) + if key in prev and prev[key][1] == t0: # cycle turns excluded (carried by the cycle fade) + step = abs(p - prev[key][0]); worst = max(worst, step); big += step > .1 + prev[key] = (p, t0) + return worst, big, len(near) + +def gabriel_flips(T=1400.0, dt=1.0 / 60): + """Hard Gabriel edge-set changes, and the soft gate's largest step per 1/60 s, as the wells orbit.""" + flips = 0; worst = 0.0; prev_h = None; prev_s = None; t = 0.0 + while t <= T: + set_time(t); W = H.WELLS; hs, ss = set(), {} + for i in range(len(W)): + for j in range(i + 1, len(W)): + a, b = W[i]["pos"], W[j]["pos"]; r = math.dist(a, b) / 2; m = ((a[0] + b[0]) / 2, (a[1] + b[1]) / 2) + mg = min((math.dist(m, W[k]["pos"]) - r) / r for k in range(len(W)) if k not in (i, j)) + if mg > 0: hs.add((i, j)) + ss[(i, j)] = smooth(-.15, .15, mg) + if prev_h is not None: + flips += len(hs ^ prev_h); worst = max(worst, max(abs(ss[k] - prev_s[k]) for k in ss)) + prev_h, prev_s = hs, ss; t += dt + return flips, worst + +def main(): + t0 = time.time(); T = 0.0 + print(f"grid {G}, wells orbit (period = distance s), slope terms on, TAU {TAU} s, lattice {LAT} u") + f, w = gabriel_flips() + print(f"gabriel over 1400 s: hard edge-set changes {f}; soft gate largest step per 1/60 s {w:.5f}") + cands = cand_list(16.0); print(f"{len(cands)} krill candidates at 16 u") + for name, fn in CANDS.items(): + if ONLY and name not in ONLY: continue + t1 = time.time(); vals = grid(fn, T, G); p = save(vals, "density_" + name, G) + cr = phys_corr(vals, G) + a = members(fn, cands, T); b = members(fn, cands, T + 1); c8 = members(fn, cands, T + 8); c60 = members(fn, cands, T + 60) + n = max(1, len(a)) + W2 = 600; px = [(10, 14, 30)] * (W2 * W2) + for c in cands: + if (c[0], c[1]) in a: + pi_, pj = int((c[2] / (2 * R) + .5) * W2), int((c[3] / (2 * R) + .5) * W2) + if 0 <= pi_ < W2 and 0 <= pj < W2: px[pj * W2 + pi_] = (235, 230, 170) + H.mark_bodies(px, W2); H.write_png(os.path.join(OUT, "members_" + name + ".png"), W2, W2, px) + a0 = members(fn, cands, T, T); w1 = members(fn, cands, T + 1, T); w60 = members(fn, cands, T + 60, T) + print(f" {name} wells-only (flow frozen at t's cycle): changed in 1 s {len(a0 ^ w1) / max(1, len(a0)):.4f}, 60 s {len(a0 ^ w60) / max(1, len(a0)):.3f}") + wst, big, nn = pop_probe(fn, cands, 100.0) + print(f"{name}: corr physarumA {cr:.3f}; members {len(a)}; changed in 1 s {len(a ^ b) / n:.3f}, 8 s {len(a ^ c8) / n:.3f}, " + f"60 s {len(a ^ c60) / n:.3f}; presence step max {wst:.4f} ({big} steps > .1, {nn} near-threshold candidates); {time.time() - t1:.0f}s") + if ONLY is None or "layers" in ONLY: + for name, fn in LAYERS.items(): + g = G if name.startswith(("L1", "L2-krill")) else G // 2 + t1 = time.time(); vals = grid(fn, T, g); save(vals, "layer_" + name, g) + print(f"{name}: grid {g}, corr physarumA {phys_corr(vals, g):.3f}; {time.time() - t1:.0f}s") + # members per layer at its own candidate spacing + for name, fn, C in (("L2-minnows", minnows, 48.0), ("L3-sharks", sharks, 128.0), ("L3-lanterns", lanterns, 64.0)): + cs = cand_list(C); t1 = time.time(); a = members(fn, cs, T); b = members(fn, cs, T + 1); c8 = members(fn, cs, T + 8) + n = max(1, len(a)); W2 = 600; px = [(10, 14, 30)] * (W2 * W2) + for c in cs: + if (c[0], c[1]) in a: + pi_, pj = int((c[2] / (2 * R) + .5) * W2), int((c[3] / (2 * R) + .5) * W2) + for di in range(-1, 2): + for dj in range(-1, 2): + if 0 <= pi_ + di < W2 and 0 <= pj + dj < W2: px[(pj + dj) * W2 + pi_ + di] = (235, 230, 170) + H.mark_bodies(px, W2); H.write_png(os.path.join(OUT, "members_" + name + ".png"), W2, W2, px) + print(f"members {name}: {len(a)} of {len(cs)} at {C:.0f} u; changed in 1 s {len(a ^ b) / n:.3f}, 8 s {len(a ^ c8) / n:.3f}; {time.time() - t1:.0f}s") + # cost per evaluation (Python, relative): one krill vs one minnow vs one shark evaluation + set_time(T); sc = H.scroll(T) + for name, fn in (("krill", krill), ("minnows", minnows), ("sharks", sharks)): + t1 = time.perf_counter(); [fn(300.0 + k, 200.0, T, sc) for k in range(20)] + print(f"cost {name}: {(time.perf_counter() - t1) / 20 * 1e3:.2f} ms per evaluation (Python)") + print(f"total {time.time() - t0:.0f}s") + +if __name__ == "__main__": main() diff --git a/docs/research/2026-10-14-trophic-dag-probe/win.log b/docs/research/2026-10-14-trophic-dag-probe/win.log new file mode 100644 index 000000000..c9c905100 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/win.log @@ -0,0 +1,6 @@ +WIN 2 BOOST 1.6 lattice: presence step max 0.1236, 8 steps > .1 of 1500 candidates +WIN 2 BOOST 1.6 skein-web: presence step max 0.4933, 8 steps > .1 of 1500 candidates +WIN 3 BOOST 1.6 lattice: presence step max 0.1236, 8 steps > .1 of 1500 candidates +WIN 3 BOOST 1.6 skein-web: presence step max 0.4933, 8 steps > .1 of 1500 candidates +WIN 2 BOOST 0.6 lattice: presence step max 0.0471, 0 steps > .1 of 1500 candidates +WIN 2 BOOST 0.6 skein-web: presence step max 0.2361, 11 steps > .1 of 1500 candidates diff --git a/docs/research/2026-10-14-trophic-dag-probe/winprobe.py b/docs/research/2026-10-14-trophic-dag-probe/winprobe.py new file mode 100644 index 000000000..e836af667 --- /dev/null +++ b/docs/research/2026-10-14-trophic-dag-probe/winprobe.py @@ -0,0 +1,8 @@ +import sys; sys.argv=['x','64'] +import trophic_probe as T +cands=T.cand_list(16.0) +for win,boost in ((2,1.6),(3,1.6),(2,0.6)): + T.WIN, T.BOOST = win, boost + for name in ("lattice","skein-web"): + w,b,n=T.pop_probe(T.CANDS[name],cands,100.0,1500) + print(f"WIN {win} BOOST {boost} {name}: presence step max {w:.4f}, {b} steps > .1 of {n} candidates", flush=True)