Fauna target r4: placement is a trophic DAG over CultMath nodes; map r4 and the trophic DAG probe
Applies rulings habitat-trophic-dag and voronoi-primitive and the operator's correction that CultMath owns shader/C# parity. The target's Placement law becomes a layered, donor-controlled DAG (environment, the Skein, producers, grazers, predators and ambushers) with pointwise and stencil operators only, nodes in CultMath and compositions in Aetheria; invariants trophic-dag-acyclic, -pointwise, trophic-nodes-in-cultmath and skein-gate-continuous are added. The map gains r4 body facts B28-B36 (CultMath's node and parity surface, the tape, the probe), decisions, two questions and the r4 cut sequence. The probe renders filament candidates and trophic layers on the orbiting B20 zone and measures popping and turnover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
47f047865a
commit
4354ef2c36
27 changed files with 622 additions and 38 deletions
|
|
@ -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:<line>`, 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`.
|
||||
|
|
|
|||
|
|
@ -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-<cut>`. 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<n>`); 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-<cut>`; 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:<line>`); 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<n>`); prior art is
|
||||
`docs/research/2026-10-13-fauna-prior-art.md` (`PA:<line>`; `[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:<line>`; 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.
|
||||
|
|
|
|||
40
docs/research/2026-10-14-trophic-dag-probe/README.md
Normal file
40
docs/research/2026-10-14-trophic-dag-probe/README.md
Normal file
|
|
@ -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:<line>`).
|
||||
|
||||
`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_<candidate>.png` (zone 4000 u across, t = 0, absolute density, red = bodies), `members_<candidate>.png`
|
||||
(hosted krill candidates at 16 u, fixed thresholds .1 + .9h), `layer_<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.
|
||||
2
docs/research/2026-10-14-trophic-dag-probe/cyc.log
Normal file
2
docs/research/2026-10-14-trophic-dag-probe/cyc.log
Normal file
|
|
@ -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
|
||||
7
docs/research/2026-10-14-trophic-dag-probe/cycprobe.py
Normal file
7
docs/research/2026-10-14-trophic-dag-probe/cycprobe.py
Normal file
|
|
@ -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)
|
||||
BIN
docs/research/2026-10-14-trophic-dag-probe/density_band.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/density_band.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/density_lattice.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/density_lattice.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/density_skein-web.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/density_skein-web.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/density_skein.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/density_skein.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/density_slack.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/density_slack.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L1-glim.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L1-glim.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L2-krill.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L2-krill.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L2-minnows.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L2-minnows.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L3-lanterns.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L3-lanterns.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L3-sharks.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/layer_L3-sharks.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/members_L2-minnows.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/members_L2-minnows.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/members_L3-lanterns.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/members_L3-lanterns.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/members_L3-sharks.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/members_L3-sharks.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/members_band.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/members_band.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/members_lattice.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/members_lattice.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/members_skein-web.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/members_skein-web.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/members_skein.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/members_skein.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/research/2026-10-14-trophic-dag-probe/members_slack.png
(Stored with Git LFS)
Normal file
BIN
docs/research/2026-10-14-trophic-dag-probe/members_slack.png
(Stored with Git LFS)
Normal file
Binary file not shown.
27
docs/research/2026-10-14-trophic-dag-probe/run1.log
Normal file
27
docs/research/2026-10-14-trophic-dag-probe/run1.log
Normal file
|
|
@ -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
|
||||
307
docs/research/2026-10-14-trophic-dag-probe/trophic_probe.py
Normal file
307
docs/research/2026-10-14-trophic-dag-probe/trophic_probe.py
Normal file
|
|
@ -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()
|
||||
6
docs/research/2026-10-14-trophic-dag-probe/win.log
Normal file
6
docs/research/2026-10-14-trophic-dag-probe/win.log
Normal file
|
|
@ -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
|
||||
8
docs/research/2026-10-14-trophic-dag-probe/winprobe.py
Normal file
8
docs/research/2026-10-14-trophic-dag-probe/winprobe.py
Normal file
|
|
@ -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)
|
||||
Loading…
Reference in a new issue