diff --git a/CLAUDE.md b/CLAUDE.md index a68af1d8..239b6a06 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1362,9 +1362,12 @@ Three traps that slice paid for, all transferable: guard, not the correctness gate. **One TypeScript file in a ported directory was ported for a reason that is not -obvious.** `cliffConnections.ts` has **zero `src/` consumers** - only 23 -investigation specs import it - so that #84's cliff investigation can be run -against the engine. +obvious.** `cliffConnections.ts` has **zero consumers of any kind** since #360 deleted the +23 investigation specs that imported it. It is kept as the human-readable +reference `crates/fmw-noise/src/cliffs/connections.rs` cites as its source, so +that #84's cliff investigation can still be run against the engine. The +type-checker still covers it, because `tsconfig.json` includes `src/**/*` by +glob rather than by reachability. #### The three tiers, and what each one cannot see diff --git a/src/model/elevationPreviewCtx.ts b/src/model/elevationPreviewCtx.ts index c99d6353..3a6e3a24 100644 --- a/src/model/elevationPreviewCtx.ts +++ b/src/model/elevationPreviewCtx.ts @@ -3,7 +3,7 @@ import type { Point } from "../noise/distanceFromNearestPoint"; import type { VulcanusResourceControls } from "../noise/eval/ctx"; import { readMapType } from "./mapType"; import { readResourceControls, readVulcanusResourceControls } from "./resourceReads"; -import type { ResourceControlLevers } from "../noise/resources/resolveResource"; +import type { ResourceControlLevers } from "../noise/resources/resourceCatalog"; import type { Preset } from "./types"; import { ENEMY_CONTROL_NAME, type EnemyControls } from "../noise/enemies/enemyCatalog"; import { diff --git a/src/model/resourceReads.ts b/src/model/resourceReads.ts index c8d66d73..3694e9fd 100644 --- a/src/model/resourceReads.ts +++ b/src/model/resourceReads.ts @@ -9,7 +9,7 @@ */ import type { AutoplaceSetting } from "./types"; import type { VulcanusResourceControls, VulcanusResourceLevers } from "../noise/eval/ctx"; -import type { ResourceControlLevers } from "../noise/resources/resolveResource"; +import type { ResourceControlLevers } from "../noise/resources/resourceCatalog"; import { RESOURCE_CATALOG } from "../noise/resources/resourceCatalog"; const DEFAULT_LEVERS: ResourceControlLevers = { frequency: 1, size: 1, richness: 1 }; diff --git a/src/noise/cliffs/cliffCatalog.ts b/src/noise/cliffs/cliffCatalog.ts index e2a85d81..4b779b8d 100644 --- a/src/noise/cliffs/cliffCatalog.ts +++ b/src/noise/cliffs/cliffCatalog.ts @@ -427,3 +427,21 @@ export function cliffCollisionTileBox( bottom: Math.floor(centerY + b), }; } + +/** + * The Vulcanus tiles whose `CollisionMask` shares a layer with the cliff's, so a + * cliff whose collision box touches one is never placed. + * + * `tile_collision_masks.lava()` sets `water_tile = true` and the cliff mask + * holds `water_tile`; no other Vulcanus tile does. Notably + * `volcanic-jagged-ground` - the tile the ore patches paint, which the Lua + * itself labels "CLIFF TILE" - is `tile_collision_masks.ground()`, which the + * cliff mask does not touch, so ore does NOT exclude cliffs. That distinction is + * the whole reason the earlier ore-separation work correctly found no exclusion + * rule while this one exists. + * + * Lives here rather than beside the Vulcanus renderer because that renderer is + * deleted by #227 and this rule is not. The Rust carries the same pair inlined + * at `crates/fmw-noise/src/cliffs/vulcanus_fields.rs:218`. + */ +export const VULCANUS_CLIFF_BLOCKING_TILES: ReadonlySet = new Set(["lava", "lava-hot"]); diff --git a/src/noise/cliffs/cliffConnections.ts b/src/noise/cliffs/cliffConnections.ts index e59f7c15..d735fc4c 100644 --- a/src/noise/cliffs/cliffConnections.ts +++ b/src/noise/cliffs/cliffConnections.ts @@ -120,8 +120,9 @@ * | `oppositeSide` | immediate `0x01000302` | `N<->S`, `E<->W` | * | `destroyEnd` | 4 jump tables under `0x102cfc9db` | `side -> none`, else destroy | * - * `test/cliffConnections.spec.ts` re-derives all four from the names and asserts - * they match, so a transcription slip fails rather than shifts the model. + * `crates/fmw-noise/src/cliffs/connections.rs` re-derives all four from the names + * and asserts they match, so a transcription slip fails rather than shifts the + * model. The TypeScript spec that used to do this went with #227. */ import { @@ -131,7 +132,22 @@ import { CLIFF_GRID_SIZE, CLIFF_ORIENTATION_NAMES, } from "./cliffCatalog"; -import type { PlacedCliffCell } from "./cliffPlacement"; + +/** + * A placed cliff: the cell centre, plus the 8-bit edge-crossing `code` it was + * placed by. The code is carried out rather than discarded because it is the + * only thing that names the cliff's ORIENTATION, and therefore its collision + * box - `cliffOrientationForCode(code)` in `cliffCatalog.ts`. + * + * Inlined from `cliffPlacement.ts`, which #227 deletes. This module is kept as + * the reference `crates/fmw-noise/src/cliffs/connections.rs` cites, so it must + * not import anything that deletion removes. + */ +export interface PlacedCliffCell { + readonly x: number; + readonly y: number; + readonly code: number; +} /** * `CellSide`, in the engine's enum order. Read off `getNeighborPosition` @@ -310,8 +326,8 @@ export interface CliffConnectionOptions { * `<= 0x31`, so during a real generation sweep a cliff pointing into a * not-yet-generated chunk keeps its end, and this model destroys it. It is * therefore an UPPER bound on how much the rule removes - see - * `test/cliffConnections.spec.ts`, which is what measures whether that bound is - * tight. + * `crates/fmw-noise/src/fixtures.rs`, which runs over the same fixture and is + * what measures whether that bound is tight. */ export function applyCliffConnections( cells: readonly PlacedCliffCell[], diff --git a/src/noise/preview/elevationRenderRequest.ts b/src/noise/preview/elevationRenderRequest.ts index 28f6177d..c838dec2 100644 --- a/src/noise/preview/elevationRenderRequest.ts +++ b/src/noise/preview/elevationRenderRequest.ts @@ -6,7 +6,7 @@ import type { EnemyControls } from "../enemies/enemyCatalog"; import type { Planet } from "../../model/planets"; import { PLACEMENT_MARK_RADIUS_PX } from "../placement/placementRoll"; import { NAUVIS_MAX_STARTING_POINTS } from "../wasm/request"; -import type { ResourceControlLevers } from "../resources/resolveResource"; +import type { ResourceControlLevers } from "../resources/resourceCatalog"; import type { RockControls } from "../rocks/rockCatalog"; import { renderCliffs } from "./renderCliffs"; import { renderElevation } from "./renderElevation"; diff --git a/src/noise/preview/palette.ts b/src/noise/preview/palette.ts new file mode 100644 index 00000000..d607616c --- /dev/null +++ b/src/noise/preview/palette.ts @@ -0,0 +1,19 @@ +/** + * Colours the map preview paints with, kept apart from the renderers so they + * outlive them. #227 deletes the TypeScript renderers these were declared in; + * the Rust engine now does the painting and these values are what the surviving + * parity specs grade its output against. + * + * Deliberately an independent copy of the Rust constants rather than a read of + * them - a spec that imported the engine's own numbers would assert nothing. + * The mirrors are `crates/fmw-wasm/src/render.rs:156` and `:159` for the + * elevation pair, and `crates/fmw-noise/src/trees/catalog.rs:69` for the tree + * colour. Rust stores RGB and writes the alpha separately. + */ + +/** RGBA for water (elevation < 0) and land, as [r, g, b, a] byte tuples. */ +export const WATER_RGBA: readonly [number, number, number, number] = [40, 90, 150, 255]; +export const LAND_RGBA: readonly [number, number, number, number] = [70, 120, 60, 255]; + +/** `{0.19, 0.39, 0.19}` in 8-bit - utility-constants.lua:201. */ +export const TREE_MAP_COLOR: readonly [number, number, number] = [48, 99, 48]; diff --git a/src/noise/preview/renderElevation.ts b/src/noise/preview/renderElevation.ts index 87dd7a13..9458edf2 100644 --- a/src/noise/preview/renderElevation.ts +++ b/src/noise/preview/renderElevation.ts @@ -1,10 +1,7 @@ import { makeElevationLakes, type ElevationLakesParams } from "../expressions/elevationLakes"; import { makeElevationNauvis } from "../expressions/elevationNauvis"; import { makeElevationIsland } from "../expressions/elevationIsland"; - -/** RGBA for water (elevation < 0) and land, as [r, g, b, a] byte tuples. */ -export const WATER_RGBA: [number, number, number, number] = [40, 90, 150, 255]; -export const LAND_RGBA: [number, number, number, number] = [70, 120, 60, 255]; +import { LAND_RGBA, WATER_RGBA } from "./palette"; export interface RenderElevationOptions { /** Map seed (= map_seed / seed0). Callers resolve a null "random" seed first. */ diff --git a/src/noise/preview/renderResources.ts b/src/noise/preview/renderResources.ts index a096416c..468f1ae5 100644 --- a/src/noise/preview/renderResources.ts +++ b/src/noise/preview/renderResources.ts @@ -66,13 +66,9 @@ import { makePlacementSet, } from "../placement/placementRoll"; import type { PlacementCollisionBox } from "../placement/placementRoll"; -import { RESOURCE_CATALOG } from "../resources/resourceCatalog"; +import { RESOURCE_CATALOG, type ResourceControlLevers } from "../resources/resourceCatalog"; import { makeResourcePatches } from "../resources/resourcePatches"; -import { - comparePriority, - makeResourceResolver, - type ResourceControlLevers, -} from "../resources/resolveResource"; +import { comparePriority, makeResourceResolver } from "../resources/resolveResource"; import { makeTileResolver } from "../tiles/resolve"; import { paintMark } from "./renderCliffs"; diff --git a/src/noise/preview/renderTrees.ts b/src/noise/preview/renderTrees.ts index c7f5bd9a..2d9b5f0e 100644 --- a/src/noise/preview/renderTrees.ts +++ b/src/noise/preview/renderTrees.ts @@ -41,9 +41,8 @@ */ import { WATER_TILE_COLORS } from "./renderResources"; import { makeTreeDensity, type TreeFieldParams } from "../trees/treeField"; +import { TREE_MAP_COLOR } from "./palette"; -/** `{0.19, 0.39, 0.19}` in 8-bit - utility-constants.lua:201. */ -export const TREE_MAP_COLOR: readonly [number, number, number] = [48, 99, 48]; /** The alpha component of the same constant: fully-forested tiles blend at 40%. */ export const TREE_MAX_ALPHA = 0.4; diff --git a/src/noise/preview/renderVulcanusCliffs.ts b/src/noise/preview/renderVulcanusCliffs.ts index b3b06368..d2197a67 100644 --- a/src/noise/preview/renderVulcanusCliffs.ts +++ b/src/noise/preview/renderVulcanusCliffs.ts @@ -25,6 +25,7 @@ import type { EvalCtxInput } from "../eval/ctx"; import { withCtxDefaults } from "../eval/ctx"; import { makeCliffPlacementFromFields } from "../cliffs/cliffPlacement"; +import { VULCANUS_CLIFF_BLOCKING_TILES } from "../cliffs/cliffCatalog"; import { VULCANUS_CLIFF_ELEVATION_0, VULCANUS_CLIFF_ELEVATION_INTERVAL, @@ -40,20 +41,6 @@ import { makeVulcanusTileResolverFrom, } from "../tiles/vulcanusCatalog"; -/** - * The Vulcanus tiles whose `CollisionMask` shares a layer with the cliff's, so a - * cliff whose collision box touches one is never placed. - * - * `tile_collision_masks.lava()` sets `water_tile = true` and the cliff mask - * holds `water_tile`; no other Vulcanus tile does. Notably - * `volcanic-jagged-ground` - the tile the ore patches paint, which the Lua - * itself labels "CLIFF TILE" - is `tile_collision_masks.ground()`, which the - * cliff mask does not touch, so ore does NOT exclude cliffs. That distinction is - * the whole reason the earlier ore-separation work correctly found no exclusion - * rule while this one exists. - */ -export const VULCANUS_CLIFF_BLOCKING_TILES: ReadonlySet = new Set(["lava", "lava-hot"]); - export interface RenderVulcanusCliffsOptions { readonly seed0: number; /** World tile at the top-left pixel. Default (0, 0). */ diff --git a/src/noise/resources/resolveResource.ts b/src/noise/resources/resolveResource.ts index ff0c1244..dc9a2a1e 100644 --- a/src/noise/resources/resolveResource.ts +++ b/src/noise/resources/resolveResource.ts @@ -22,14 +22,11 @@ */ import type { Point } from "../distanceFromNearestPoint"; import { makeResourcePatches, type ResourcePatches } from "./resourcePatches"; -import { RESOURCE_CATALOG, type ResourceParams } from "./resourceCatalog"; - -/** control::frequency|size|richness levers for one resource. */ -export interface ResourceControlLevers { - readonly frequency: number; - readonly size: number; - readonly richness: number; -} +import { + RESOURCE_CATALOG, + type ResourceControlLevers, + type ResourceParams, +} from "./resourceCatalog"; export interface ResourceResolverCtx { readonly seed0: number; diff --git a/src/noise/resources/resourceCatalog.ts b/src/noise/resources/resourceCatalog.ts index 0d5f7218..c5e6f92b 100644 --- a/src/noise/resources/resourceCatalog.ts +++ b/src/noise/resources/resourceCatalog.ts @@ -59,6 +59,13 @@ export interface ResourceParams { */ export type ResourcePlacement = "threshold" | "roll"; +/** control::frequency|size|richness levers for one resource. */ +export interface ResourceControlLevers { + readonly frequency: number; + readonly size: number; + readonly richness: number; +} + /** map_color (0..1) -> 0..255, rounded, matching the game's preview tint. */ function color255(r: number, g: number, b: number): readonly [number, number, number] { return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; diff --git a/test/cliffCatalog.spec.ts b/test/cliffCatalog.spec.ts index 092d852d..629582b7 100644 --- a/test/cliffCatalog.spec.ts +++ b/test/cliffCatalog.spec.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vite-plus/test"; import { CLIFF_MAP_COLOR, + VULCANUS_CLIFF_BLOCKING_TILES, getModifiedElevationInterval, getModifiedRichness, isCliffPlaced, @@ -46,3 +47,13 @@ describe("cliff orientation predicate", () => { ]); }); }); + +// #360 deleted the block in cliffOreDirection.spec.ts that pinned this literal. +// The set survives and two specs still read it, so the value is re-pinned here. +// The Rust holds the same pair inlined twice, at +// `crates/fmw-noise/src/cliffs/vulcanus_fields.rs:218` and `fixtures.rs`. +describe("VULCANUS_CLIFF_BLOCKING_TILES", () => { + it("is exactly the two lava tiles", () => { + expect([...VULCANUS_CLIFF_BLOCKING_TILES].sort()).toEqual(["lava", "lava-hot"]); + }); +}); diff --git a/test/cliffOreLeverTileConfound.spec.ts b/test/cliffOreLeverTileConfound.spec.ts index 33b18b3d..7c83b980 100644 --- a/test/cliffOreLeverTileConfound.spec.ts +++ b/test/cliffOreLeverTileConfound.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vite-plus/test"; import lever from "./fixtures/oracle-vulcanus-tile-lever.seed123456.json"; -import { VULCANUS_CLIFF_BLOCKING_TILES } from "../src/noise/preview/renderVulcanusCliffs"; +import { VULCANUS_CLIFF_BLOCKING_TILES } from "../src/noise/cliffs/cliffCatalog"; import { makeVulcanusTileResolver } from "../src/noise/tiles/vulcanusCatalog"; import { DEFAULT_VULCANUS_RESOURCE_CONTROLS } from "../src/noise/eval/ctx"; diff --git a/test/multisampleChannelAudit.spec.ts b/test/multisampleChannelAudit.spec.ts index 1b42882b..9e901530 100644 --- a/test/multisampleChannelAudit.spec.ts +++ b/test/multisampleChannelAudit.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vite-plus/test"; import fixture from "./fixtures/oracle-vulcanus-tile-names.seed123456.json"; -import { VULCANUS_CLIFF_BLOCKING_TILES } from "../src/noise/preview/renderVulcanusCliffs"; +import { VULCANUS_CLIFF_BLOCKING_TILES } from "../src/noise/cliffs/cliffCatalog"; import { makeVulcanusStack, makeVulcanusTileResolverFrom, diff --git a/test/wasmElevationRenderParity.spec.ts b/test/wasmElevationRenderParity.spec.ts index 8b8cde72..edd024f2 100644 --- a/test/wasmElevationRenderParity.spec.ts +++ b/test/wasmElevationRenderParity.spec.ts @@ -17,7 +17,7 @@ import { runRenderRequest, type ElevationRenderRequest, } from "../src/noise/preview/elevationRenderRequest"; -import { LAND_RGBA, WATER_RGBA } from "../src/noise/preview/renderElevation"; +import { LAND_RGBA, WATER_RGBA } from "../src/noise/preview/palette"; /** * Tier 3 for the ELEVATION view (#227): the Rust engine's elevation render diff --git a/test/wasmNauvisRenderParity.spec.ts b/test/wasmNauvisRenderParity.spec.ts index d192d037..c7bfc627 100644 --- a/test/wasmNauvisRenderParity.spec.ts +++ b/test/wasmNauvisRenderParity.spec.ts @@ -19,7 +19,7 @@ import { runRenderRequest, type ElevationRenderRequest, } from "../src/noise/preview/elevationRenderRequest"; -import { TREE_MAP_COLOR } from "../src/noise/preview/renderTrees"; +import { TREE_MAP_COLOR } from "../src/noise/preview/palette"; import { planTiles } from "../src/noise/preview/tiling"; /**