Skip to content

Commit e8f8fcf

Browse files
wormeymanclaude
andauthored
Port phase 5: Vulcanus helpers through elevation (#225) (#285)
* Port phase 5: Vulcanus helper layer (#225) Ports src/noise/expressions/vulcanusHelpers.ts - vulcanus_detail_noise, vulcanus_plasma, vulcanus_threshold, vulcanus_contrast, vulcanus_biome_noise, the vulcanus_scale_multiplier program constant and the six vulcanus_wobble_* fields. It lands first because climate, biomes, elevation and the resource stack all read it, so an error here would arrive blended into seven files rather than localised to one. Tier 1 grades the three leaf closures the oracle captured, as frozen exact f32 match counts out of 38: wobbleX 38/38, worst residual exactly 0 mountainPlasma 7/38, worst residual 2.807e-3 detailNoise 1/38, worst residual 7.778e-5 The TypeScript was measured against the same fixture and reaches the identical 38, 7 and 1 with the same residuals, so these describe the distance both ports sit from the game rather than a gap between them. detailNoise is the row worth keeping: it has the SMALLEST residual of the three and the FEWEST exact matches. A field can be uniformly close and almost never right, which is the whole argument for counting matches instead of bounding error. Also recorded: test/vulcanusHelpers.spec.ts bounds vulcanus_wobble_x at 4e-4 and its comment claims a measured worst of 2.32e-4. Re-measured here the worst residual is exactly 0 at all 38 positions, so that bound is inert - it would not notice the field losing four digits. Left alone rather than fixed, because it belongs to #256 with the other 86. Unit tests pin what a residual cannot see: the +12243 detail-noise seed offset against the plasma's 12643 and 13423 + seed, which differ by a transposed digit; that only the plasma's SECOND term depends on its seed parameter; that biome_noise uses its seed1 raw; and that each wobble pair is two independent fields. Prepared moves from expressions::fulgora_shared to multioctave_noise. It is a general multioctave-hoisting utility rather than anything planet-specific, and a Vulcanus module reaching into a Fulgora one for it would be the wrong shape. Four Fulgora importers updated; no behaviour change. The poison entry was watched going red, not assumed: under --features poison wobbleX drops 38 -> 6. engine.wasm is unchanged and still byte-matches its source. Nothing fmw-wasm exports reaches the new module yet, so it is dead-code-eliminated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2wz8eFazisYuENEkmBvvG * Port phase 5: Vulcanus crack and climate layers (#225) Ports src/noise/expressions/vulcanusCracks.ts and vulcanusClimate.ts - the five crack and flood fields the elevation chain samples, and the two climate fields. Both stack on the helper layer that landed in the previous commit. Tier 1 grades all seven as frozen exact f32 match counts out of 61, each with its worst residual: hairlineCracks 3 1.853e-3 floodCracksA 15 4.440e-4 floodCracksB 40 1.122e-4 floodPaths 10 5.460e-4 floodBasaltsFunc 8 6.387e-4 aux 40 4.584e-4 moisture 20 1.117e-4 The TypeScript was measured against the same fixtures and reaches all seven identically, so these describe the distance both ports sit from the game rather than a gap between them. hairlineCracks at 3 of 61 is the finding, and it points at #269. It is the SHALLOWEST expression in the layer - a bare plasma with nothing composed on top - so its weakness cannot come from anything this file builds. plasma is two basis_noise_expr results subtracted, and that adapter returns the un-narrowed f64 product #269 is about. The scales here are 0.3 * 0.325 and 0.6 * 0.325, neither exact in f32, which is precisely the case #269 records the oracle-basis fixture as blind to by construction: that fixture was captured at output_scale 1 and input_scale 0.125, both exact. So this is a second independent fixture pointing at the same adapter. Do not chase these five counts before #269 is settled. The two climate counts are flattered by their clamp. Both fields clamp to [0, 1] and every position where the clamp saturates is exact for free, because both ports and the game all return the bound itself. Read 40 of 61 as an upper bound on what the arithmetic achieves rather than as a measure of it. vulcanus_temperature is deliberately not ported. It reads vulcanus_elev, which arrives with the elevation chain, and wiring it half-finished would mean a field graded against nothing. Every min and max goes through eval::math::min2/max2 with the argument order the TypeScript writes, per the signed-zero divergence recorded in CLAUDE.md. This layer has four such sites. One unit test earned its comment by failing first, and the port was right: aux is min(abs(noise), 0.3 - 0.6 * flood_paths), so nudging flood_paths DOWN raises the arm the min does not select and nothing moves. The dependency is real but observable in one direction only, and that failure looks exactly like a missing wire. Both new tier-1 tests were watched going red under --features poison. engine.wasm is unchanged and still byte-matches its source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2wz8eFazisYuENEkmBvvG * Port phase 5: Vulcanus spawn, biomes and elevation (#225) Completes the chain to vulcanus_elevation. Ports vulcanusSpawn.ts, vulcanusBiomes.ts and vulcanusElevation.ts, and closes the vulcanus_temperature the climate layer deferred, since it reads vulcanus_elev and could not be ported until that existed. Tier 1 grades fourteen more fields as frozen exact f32 match counts. Every one was measured again on the TypeScript side against the same fixture and all fourteen agree, so they are the distance both ports sit from the game: spawn, of 410 startingArea 371, startingCircle 247, ashlandsStart 61 biomes, of 434 mountain_volcano_spots 359, mountains_raw_volcano 163, mountains/ashlands/basalts_biome_full 128/107/127, mountains/ashlands/basalts_biome 403/402/408 elevation, of 434 elev 113, elevation 113, temperature 196 Three readings worth keeping. The oracle cannot see the -500 clamp, and that was checked rather than assumed. vulcanus_elevation is max(-500, elev), and the captured elev bottoms out at -58.77 - so the two columns are the same field at all 434 positions, 0 of 434 differ, and a port that dropped the max would score 113 as well. Both are graded anyway because they separate for free the day a capture reaches a deep lake, and the clamp's real test lives in the module, where it constructs the case the fixture does not. The clamp flatters a count, now measurably. The three clamped biomes score 403, 402 and 408 against their own unclamped sources at 128, 107 and 127. Same quantity, times two, clamped: nothing improved between them, the clamp saturates at 0 or 1 over most of the map, and a saturated position is exact for free. The *_biome_full row is the port's real score. A discrete output scores like a discrete output. mountain_volcano_spots at 359 is the highest unclamped count in the Vulcanus port, and it is dominated by which single candidate survives per region - a choice a sub-ULP error almost never changes. That is the same property CLAUDE.md records for voronoi_cell_id. Elevation's worst residual is 1.332e-1, which is only alarming until the scale is read: the field spans -58 to +1024 here, so it is about 1.3e-4 relative, the same order as every layer upstream. An absolute bound would need re-tuning per field for scale alone, which is a third argument for counting matches. Notes on the port itself: - vulcanus_biomes keeps a real cache, unlike every layer so far. raw_spots at one point reads selected spots from up to four neighbouring regions, which is cross-position state, so the region cache is a RefCell<BTreeMap> and eval stays &self. BTreeMap rather than HashMap because a determinism-critical port should not carry a container with unspecified iteration order, even where nothing iterates it yet. - volcano_area is evaluated at every spot candidate and pulls the whole pre-volcano chain there. The TypeScript memoizes those; this recomputes them. Nothing on the render path reaches this layer yet, so it is correct-first by choice, and the note says to measure before that changes. - The mountains pre-volcano split is a named method rather than an inline local. Collapsing the two stages would be an infinite recursion, which announces itself, but reading volcano_area off the POST-volcano raw would not. - Three numbers in spawn are transcription traps and each has its own test: basalts_start's distance is a bare 250 where both its siblings scale by r, mountains takes half the distortion coefficient of the other two, and the three results carry different multipliers. - min(a, a) in vulcanus_ashlands_func is transcribed as written. The two basis calls are byte-identical in the Lua, so it is the identity, and a test says so - collapsing it later should be deliberate. - cliff_elevation is a separate entry point because multisample's offsets are in the CONSUMING program's grid units: the cliff generator's 4-tile lattice moves the field 16 tiles for a dx of 4, which #83 measured. All seven Vulcanus tier-1 tests were watched going red under --features poison. engine.wasm is unchanged and still byte-matches its source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2wz8eFazisYuENEkmBvvG * Record phase 5 in CLAUDE.md, and correct two comments (#225) Three documentation changes. No behaviour, and engine.wasm is unchanged. CLAUDE.md's Rust engine section stopped at phase 4 and still listed Vulcanus as future work, so the repo's own map said six ported layers did not exist. Adds a phase 5 section with the 24 frozen counts' four readings, the biome layer's real cache and why it is the only one, the pre-volcano split, and the cliff multisample grid. Fixes an inverted comment on the ashlands basis leaf, found by review on #285. It said the input scale "divides by the volcanism scale multiplier" where the code has that multiplier in the NUMERATOR. The code was right and matched both the TypeScript and the Lua; only the comment was wrong. Worth more than a typo fix: scale_multiplier is exactly 1 at the default preset, which is what every fixture and the poison gate run at, so m/50/3 and 1/50/3/m are bit-identical throughout and an edit "fixing" the code to match the comment would diverge from the game only at a non-default volcanism frequency, with a green gate. Corrects the crack layer's #269 note, which blamed the wrong number. It said the layer is discriminating because its INPUT scales (0.3 * 0.325 and 0.6 * 0.325) are not f32-exact. That is wrong, and measurement says the discriminator is the OUTPUT scale alone: basis_noise returns an f32, so a power-of-two output scale is a pure exponent shift and narrowing the product is the identity, while any other output scale can leave the f32 grid. Over 90,000 samples at a fixed input scale, output scales 1, 0.5, 0.25, 2, 4 and 64 change 0.00% of products; 0.6 changes 79.88%, 0.75 and 3 change 56.32%, 150 changes 97.46%, 125 changes 98.38%. Holding the output scale at 1 and sweeping input scales 0.125, 0.205, 0.51, 0.6, 1.5 and 0.002 changes 0.00% every time. So exactly ONE of the crack layer's twelve basis calls is exposed: hairline_cracks' first term at output scale 0.6, where 80.10% of products differ. The other eleven sit at 1, 0.5 or 0.25. That is a one-term explanation for the one bad count in that layer, and why the four fields built only from power-of-two sites score 15, 40, 10 and 8 rather than 3. The wrong reasoning is recorded in the comment rather than quietly replaced, because it is the plausible guess: the input scale is the number that looks inexact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2wz8eFazisYuENEkmBvvG * Re-score phase 5 against the landed #269, and correct the prediction (#225) Rebased onto `df3e39e`, which narrowed `basis_noise`'s output scale. Six of the 24 frozen counts moved. The point of this commit is not the counts - it is that this branch predicted, in writing and before it could check, which ones would. hairlineCracks 3 -> 2 directly exposed at output scale 0.6 floodCracksA 15 -> 15 not exposed floodCracksB 40 -> 40 not exposed floodPaths 10 -> 10 not exposed floodBasaltsFunc 8 -> 9 exposed VIA hairline_cracks mountainPlasma 7 -> 11 directly exposed at 125 and 625 elev 113 -> 115 directly exposed at 250 and 150 elevation 113 -> 115 same field as elev in this fixture temperature 196 -> 196 unmoved despite reading elev ## Correction one: exposure is transitive `fixtures.rs` said "do not expect the other four to move", reasoning that eleven of this layer's twelve DIRECT `basis_noise_expr` calls sit at power-of-two output scales and are blind by construction. Three of the four held. `floodBasaltsFunc` did not. The reason is in the layer's own verbatim transcription at the top of `src/noise/expressions/vulcanusCracks.ts`: flood_basalts_func = min(max(flood_cracks_a - 0.125, flood_paths), flood_cracks_b) + 0.3 * min(0.5, hairline_cracks) It READS `hairline_cracks`, so the single exposed term reaches it. The three fields that held are exactly the three that never touch it. The rule is that a field is exposed if it reads an exposed site directly OR through any field it composes - counting call sites misses that, and this is the correction. ## Correction two: hairlineCracks got WORSE, 3 to 2 That is not evidence against #269. The primitive is graded 196/196 against the game at five output scales in `test/basisOutputScale.spec.ts`, which is as settled as this project gets. It is the both-directions movement #273 measured: these are deep composed chains carrying other unported narrowings, so correcting one term shifts values slightly and a position that happened to land exactly right can stop doing so. A count falling by one at 61 positions says the field is still wrong for reasons #269 does not address - the `input_scale` question recorded on that issue is the next term to look at. Recorded rather than smoothed over, because a count that moves the wrong way is the kind of thing a port quietly re-baselines and then cannot explain later. Comments carrying the old numbers are updated in the same pass: the helper, crack, elevation and temperature blocks in `fixtures.rs`, and the phase 5 section of CLAUDE.md. Worst residuals are left labelled as measured before #269, since only the counts were re-measured after it. 175 Rust tests pass. Full gate green at VERIFY_RC=0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2wz8eFazisYuENEkmBvvG --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 094e664 commit e8f8fcf

15 files changed

Lines changed: 2740 additions & 40 deletions

CLAUDE.md

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1202,7 +1202,7 @@ Field labels carry in-game tooltip text via `FInfo` (an `info` prop on
12021202
`EnemyValueRow`, an `info:` entry in `controlCatalog.ts` for the enemy-base
12031203
autoplace rows).
12041204

1205-
### The Rust/WASM noise engine (`crates/`) - phases 1-3 expressions landed
1205+
### The Rust/WASM noise engine (`crates/`) - phases 1-4 landed, phase 5 in progress
12061206

12071207
A Cargo workspace at the repository root, landed empty on purpose (#219) so the
12081208
gate was proven green on `main` before any port code depended on it. Two crates:
@@ -1294,6 +1294,112 @@ been red whether or not the argmax had a control at all. `POISONED_TESTS` now
12941294
carries FULL test paths rather than bare `fixtures::` names, so a control can
12951295
live beside its op.
12961296

1297+
**Phase 5 (#225) ports Vulcanus, and its EXPRESSION chain down to elevation is
1298+
in.** Landed: `vulcanus_helpers`, `vulcanus_cracks`, `vulcanus_climate`,
1299+
`vulcanus_spawn`, `vulcanus_biomes`, `vulcanus_elevation`, plus
1300+
`vulcanus_temperature` on the elevation module. `vulcanus_shared` needed no
1301+
port - it is `starting_spot_at_angle`, done in #279 - and `vulcanus_seed`
1302+
landed in phase 2. Still out: `vulcanus_resources`, `tiles/vulcanus_catalog`,
1303+
and the cliff, resource and rock stacks.
1304+
1305+
Tier 1 grades **24 named fields** across six fixtures. Every count was measured
1306+
again on the TypeScript side against the same fixture and all 24 agree, so they
1307+
are the distance BOTH ports sit from the game rather than a gap between them.
1308+
1309+
**Four things this phase measured that are worth more than the counts:**
1310+
1311+
- **A second, independent fixture pointed at #269, and #269 has since landed.**
1312+
`hairline_cracks` is the shallowest expression in its layer - a bare `plasma`,
1313+
nothing composed on top - so its weakness could not come from the crack file.
1314+
`plasma` subtracts two `basis_noise_expr` results, and that adapter returned
1315+
the un-narrowed f64 product. Fixed in `df3e39e`, and this branch re-scored
1316+
against it:
1317+
1318+
| field | before | after | exposed? |
1319+
| -------------------- | ------- | ----------- | ----------------------------- |
1320+
| `hairlineCracks` | 3/61 | **2/61** | directly, at output scale 0.6 |
1321+
| `floodCracksA` | 15/61 | 15/61 | no |
1322+
| `floodCracksB` | 40/61 | 40/61 | no |
1323+
| `floodPaths` | 10/61 | 10/61 | no |
1324+
| `floodBasaltsFunc` | 8/61 | **9/61** | via `hairline_cracks` |
1325+
| `mountainPlasma` | 7/38 | **11/38** | directly, at 125 and 625 |
1326+
| `elev` / `elevation` | 113/434 | **115/434** | directly, at 250 and 150 |
1327+
1328+
**Two corrections came out of that, and both are worth more than the counts.**
1329+
1330+
First, **exposure is transitive.** `fixtures.rs` predicted the four flood
1331+
fields would not move, on the grounds that eleven of the layer's twelve DIRECT
1332+
`basis_noise_expr` calls sit at power-of-two output scales. Three held.
1333+
`floodBasaltsFunc` did not, because it READS `hairline_cracks` -
1334+
`+ 0.3 * min(0.5, hairline_cracks)`, right there in the layer's own verbatim
1335+
transcription. The three that held are exactly the three that never touch it.
1336+
Count composition, not call sites.
1337+
1338+
Second, **`hairlineCracks` went DOWN, 3 to 2.** That is not evidence against
1339+
the fix: the primitive is graded 196/196 against the game at five output
1340+
scales. It is the both-directions movement #273 measured. These chains carry
1341+
other unported narrowings, so correcting one term shifts values slightly and a
1342+
position that happened to land exactly right can stop doing so. A count
1343+
falling by one at 61 positions says the field is still wrong for reasons this
1344+
change does not address.
1345+
1346+
- **A clamp flatters a count, and here it is measurable.** The three clamped
1347+
biomes score 403, 402 and 408 of 434 against their own unclamped sources at
1348+
128, 107 and 127 - the same quantity, times 2, clamped. Nothing improved
1349+
between them: the clamp saturates at 0 or 1 over most of the map and a
1350+
saturated position is exact for free. Read `*_biome_full` as the port's score
1351+
and `*_biome` as what the consumer needs. Same effect in `starting_area`
1352+
(371 of 410) against the unclamped `ashlands_start` (61) feeding it.
1353+
- **The oracle cannot see elevation's `-500` clamp**, and that was checked
1354+
rather than assumed. `vulcanus_elevation` is `max(-500, elev)` and the
1355+
captured `elev` bottoms out at **-58.77**, so the two columns are the same
1356+
field at all 434 positions - 0 of 434 differ - and a port that dropped the
1357+
`max` would score 115 either way. Both are graded anyway; the clamp's real
1358+
test lives in the module, constructing the case the fixture does not.
1359+
- **A discrete output scores like one.** `mountain_volcano_spots` at 359 of 434
1360+
is the highest UNCLAMPED count in the Vulcanus port, because it is dominated
1361+
by which single candidate survives per region - a choice a sub-ULP error
1362+
almost never changes. The same property `voronoi_cell_id` has.
1363+
1364+
**`detailNoise` is the reading to carry out of this phase.** It has the
1365+
SMALLEST residual of its three helper fields (7.778e-5) and the FEWEST exact
1366+
matches (**1 of 38**), where `mountainPlasma` has 2.815e-3 and 11 of 38. A field
1367+
can be uniformly close and almost never right, which is the argument for
1368+
counting matches rather than bounding error, stated in one number.
1369+
1370+
Read elevation's worst residual of 1.332e-1 against its scale before reacting:
1371+
the field spans -58 to +1024, so that is ~1.3e-4 relative, the same order as
1372+
every layer above it. An absolute bound would need re-tuning per field for
1373+
scale alone - a third reason not to use one.
1374+
1375+
**`vulcanus_biomes` keeps a REAL cache, and it is the only layer that does.**
1376+
Every other ported layer evaluates top to bottom into locals, because every read
1377+
is at the same `(x, y)`. `raw_spots` is not: it reads selected spots from up to
1378+
four neighbouring regions, which is genuine cross-position state. The region
1379+
cache is a `RefCell<BTreeMap>` so `eval` can stay `&self` while the density and
1380+
favorability closures handed to `select_spots` borrow it. `BTreeMap` rather than
1381+
`HashMap` deliberately - nothing iterates it today, but a determinism-critical
1382+
port should not carry a container whose iteration order is unspecified.
1383+
1384+
`volcano_area` is evaluated at every spot candidate and pulls the whole
1385+
pre-volcano chain at that candidate; the TypeScript memoizes those and the port
1386+
recomputes them. **Nothing on the render path reaches this layer yet**, so it is
1387+
correct-first on purpose. If it ever joins a per-pixel render that is the first
1388+
measurement to take - `multioctave_noise`'s own docs record what happened last
1389+
time a per-call rebuild went unmeasured, which was 20x.
1390+
1391+
**The mountains pre-volcano split is load-bearing.** `mountain_volcano_spots`
1392+
depends on the mountains biome and the mountains biome folds the volcano field
1393+
back in; the Lua breaks that with a PRE-volcano stage that `volcano_area` reads.
1394+
Collapsing the two is an infinite recursion, which announces itself - reading
1395+
`volcano_area` off the POST-volcano raw does not.
1396+
1397+
**`cliff_elevation` is a separate entry point, not a convenience.**
1398+
`multisample`'s offsets are in the CONSUMING program's grid units, so the cliff
1399+
generator's 4-tile lattice moves the field 16 tiles for a `dx` of 4 (#83). The
1400+
tile and terrain channels pass 1; cliffs pass 4; both go through one code path
1401+
with the grid as a parameter.
1402+
12971403
**Tier 3 now covers both preview PNGs**, which is what #224's gate asks for.
12981404
`test/wasmFulgoraRenderParity.spec.ts` renders through the real boundary and
12991405
compares against the images Factorio itself produced:

crates/fmw-noise/src/expressions/fulgora_elevation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
use crate::eval::math::slider_rescale;
2626
use crate::eval::math::{lerp_f32, max2, min2};
2727
use crate::expressions::fulgora_cells::CellFields;
28-
use crate::expressions::fulgora_shared::Prepared;
2928
use crate::expressions::fulgora_shared::{FulgoraCtx, SharedFields};
3029
use crate::multioctave_noise::MultioctaveParams;
30+
use crate::multioctave_noise::Prepared;
3131
use crate::poison;
3232

3333
/// `seed1` for each multioctave call: `crc32` of the Lua's string seed.

crates/fmw-noise/src/expressions/fulgora_roads.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@
3434
3535
use crate::eval::math::lerp;
3636
use crate::expressions::fulgora_cells::CellFields;
37-
use crate::expressions::fulgora_shared::{FulgoraCtx, Prepared, SharedFields};
37+
use crate::expressions::fulgora_shared::{FulgoraCtx, SharedFields};
3838
use crate::multioctave_noise::MultioctaveParams;
39+
use crate::multioctave_noise::Prepared;
3940
use crate::voronoi_noise::{Voronoi, VoronoiDistanceType, VoronoiParams};
4041

4142
/// `seed1` values, computed with CRC32 over the UTF-8 bytes of the name in the

crates/fmw-noise/src/expressions/fulgora_ruins.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ use crate::eval::math::max2;
2020
use crate::expressions::fulgora_cells::CellFields;
2121
use crate::expressions::fulgora_masks::MaskFields;
2222
use crate::expressions::fulgora_roads::RoadFields;
23-
use crate::expressions::fulgora_shared::{FulgoraCtx, Prepared};
23+
use crate::expressions::fulgora_shared::FulgoraCtx;
2424
use crate::multioctave_noise::MultioctaveParams;
25+
use crate::multioctave_noise::Prepared;
2526

2627
const SEED1_RUINS_WALLS: u32 = 2_307_136_174; // crc32("fulgora_ruins_walls") = 0x89841AAE
2728
const SEED1_RUINS_PAVING: u32 = 3_946_133_559; // crc32("fulgora_ruins_paving") = 0xEB353837

crates/fmw-noise/src/expressions/fulgora_shared.rs

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@
2222
//! evaluated, checked field by field, which is what makes the substitution
2323
//! legitimate; a field that read a neighbour would need the cache back.
2424
25-
use crate::basis_noise::{tables_from_seed, BasisNoiseTables};
2625
use crate::eval::math::{clamp, slider_to_linear};
2726
use crate::expressions::starting_spot_at_angle::{starting_spot_at_angle, AngleTrig, StartingSpot};
28-
use crate::multioctave_noise::{octave_terms, sum_octaves, MultioctaveParams, OctaveTerms};
27+
use crate::multioctave_noise::{MultioctaveParams, Prepared};
2928
use crate::poison;
3029

3130
/// `seed1` for `fulgora_wobble_x`: `crc32(utf8("fulgora_wobble_x"))`.
@@ -84,40 +83,6 @@ pub struct SharedFields {
8483
pub starting_vault_mask: f64,
8584
}
8685

87-
/// One multioctave call with its seed tables and octave terms already derived.
88-
///
89-
/// **This is the shape every renderer needs, and its absence was a measured
90-
/// bug.** `multioctave_noise(x, y, &params)` re-derives both on every call, and
91-
/// `tables_from_seed` runs a PRNG over three 256-byte tables; Fulgora's chain
92-
/// makes eight such calls per pixel. Building them per point measured **1.15x**
93-
/// against the TypeScript, which builds them once in a closure. Hoisting is
94-
/// what the ratio in phase 3's pull request is.
95-
///
96-
/// Results are identical either way, so nothing in tiers 1 to 3 could see it.
97-
///
98-
/// No `Debug` or `Clone`: `OctaveTerms` has neither, and deriving them here
99-
/// would mean giving them to derived state whose shape is an implementation
100-
/// detail.
101-
pub struct Prepared {
102-
terms: OctaveTerms,
103-
tables: BasisNoiseTables,
104-
}
105-
106-
impl Prepared {
107-
#[must_use]
108-
pub fn new(params: &MultioctaveParams) -> Self {
109-
Self {
110-
terms: octave_terms(params),
111-
tables: tables_from_seed(params.seed0, params.seed1),
112-
}
113-
}
114-
115-
#[must_use]
116-
pub fn eval(&self, x: f64, y: f64) -> f32 {
117-
sum_octaves(x, y, &self.terms, &self.tables)
118-
}
119-
}
120-
12186
/// The per-render constants of Fulgora's shared layer.
12287
pub struct FulgoraShared {
12388
/// `fulgora_grid` - the Voronoi cell size in tiles.

crates/fmw-noise/src/expressions/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ pub mod fulgora_scrap;
1515
pub mod fulgora_shared;
1616
pub mod fulgora_stack;
1717
pub mod starting_spot_at_angle;
18+
pub mod vulcanus_biomes;
19+
pub mod vulcanus_climate;
20+
pub mod vulcanus_cracks;
21+
pub mod vulcanus_elevation;
22+
pub mod vulcanus_helpers;
1823
pub mod vulcanus_seed;
24+
pub mod vulcanus_spawn;

0 commit comments

Comments
 (0)