diff --git a/README.md b/README.md index 4084cfa..b66c42a 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ A Minecraft building editor for collaboration between a human and an AI agent. -![Daylight screenshot of the Gothic hall built and inspected in Minecraft](docs/builds/gothic-hall-polished-day.png) +![Example building constructed and inspected in Minecraft](docs/images/build-demo.png) -An unedited in-game capture of our Gothic hall: 30,125 blocks and 53 lanterns. See the [build report](docs/builds/GOTHIC_HALL.md) for the reference, checked incremental edits, and dusk and interior photos. +An unedited in-game example created through checked incremental building and camera inspection. Map-specific designs, generators and progress reports belong in separate map projects. Working prototype: a Paper plugin, an editing core, an MCP/ACP Bridge, and a Fabric camera mod. Building, conflicts, undo, crash recovery, and `.schem` have been tested against local Paper through HTTP and real MCP stdio. The camera has been tested in Prism with one client: a real 1280×720 PNG was delivered through MCP. Dedicated ChatGPT sign-in, in-game text replies, and a real one-block build → inspect → undo cycle through ACP have also been verified. @@ -17,7 +17,7 @@ Working prototype: a Paper plugin, an editing core, an MCP/ACP Bridge, and a Fab - Provides 19 MCP tools and `/ai` game chat through a pinned `codex-acp` adapter. - Discovers the running server's complete vanilla block and item catalog on demand; ordinary recipes accept every registered block type and its valid states. - Captures real images through a local worker on a spectator client. A single client with the owner temporarily in spectator has also been tested. -- Exports real north-up world surface maps without a player through the optional terrain plugin; applies reversible, terrain-following site layouts with checked snapshots. +- Previews deterministic terrain recipes and prepares relative terrain brushes through the same checked apply and undo workflow. The current scope is one owner, one project, and one world, with at most 4096 blocks per plan and loaded chunks only. A complete delta history, automatic merging of manual edits, and the full design document are not implemented yet. [Detailed status and limitations](docs/IMPLEMENTATION.md). @@ -25,14 +25,6 @@ Material discovery stays compact: `project_context` returns catalog counts and a Block support does not replace placement design: doors, beds and tall plants need all their parts, attachments need support, and decorative leaves should normally use `persistent=true`. Terrain brushes still reject fluids and structures in their scan. The `.schem` codec accepts registered block states but rejects entity/block-entity payloads and refuses to export block entities. World checkpoints remain necessary for complete landscaped builds and their extra data. -## Shacraft lobby construction - -The clock station in zone 02 now contains a furnished vestibule, a SMASH selection floor and a two-stop lift, with unfinished upper spaces closed. See the [station photographs and verification](docs/SHACRAFT-STATION.md). For future work, use the [decoration playbook](docs/DECORATION-PLAYBOOK.md), [14 practical recipes](docs/DECORATION-RECIPES.md) and [structured recipe catalog](docs/recipes/decorations.json). - -The arrival square is finished with the real Shacraft emblem, hexagonal paving, evergreen flower beds, benches, copper-hood lanterns, stone urns and a welcome board. A cream-capped balustrade frames the square. An independently reversible invisible enclosure currently keeps normal players inside zone 01, including at its five road approaches and above the square; the configured lobby spawn remains inside. The station foundation, local roads and broad stairs are ready; other districts remain marked reservations. [Completed zone 01 and containment verification](docs/SHACRAFT-ZONE01-COMPLETE.md) · [Balustrade](docs/SHACRAFT-BALUSTRADE.md) · [Arrival garden](docs/SHACRAFT-ARRIVAL-GARDEN.md) · [Foundations and elevations](docs/SHACRAFT-FOUNDATIONS.md) · [Earlier site survey](docs/SHACRAFT-LOBBY-LAYOUT.md). - -![Completed Shacraft arrival square, real Minecraft capture](docs/references/shacraft-zone01-overview.png) - ## Build Verified environment: Linux x64, Minecraft/Paper 26.2, Java 25, and Node.js 22.22.3+. From the repository root: @@ -48,7 +40,6 @@ Build outputs: - `paper-plugin/target/paper-plugin-0.1.0-SNAPSHOT.jar` — server plugin, including the editing core. - `camera-mod/build/libs/minecraft-builder-camera-0.1.0-SNAPSHOT.jar` — client mod. - `bridge/dist/` — executable MCP/ACP components. -- `terrain-world-plugin/target/terrain-world-plugin-0.1.0-SNAPSHOT.jar` — optional separate-world generator and read-only surface map exporter. ## Local setup @@ -58,7 +49,7 @@ Build outputs: python3 scripts/dev-server.py --accept-eula --run ``` - For subsequent runs, use `python3 scripts/dev-server.py --run`. The server lives in `.runtime/server`, listens on `127.0.0.1:25575`, and keeps Minecraft authentication enabled. The user has accepted the EULA for the server already prepared in this workspace. + For subsequent runs, use `python3 scripts/dev-server.py --run`. The server lives in `.runtime/server`, listens on `127.0.0.1:25575`, and keeps Minecraft authentication enabled. 2. Connect a Minecraft Java 26.2 client to `127.0.0.1:25575`. In **that server's** console, grant your game account operator access with `op `. In the game, run: @@ -89,12 +80,16 @@ Build outputs: Connect MCP to an external client using `python3 scripts/bridge.py mcp`. Owner scope comes from the Paper configuration. This command is intended for a client that launches MCP over stdio, rather than an interactive terminal. +For an external map project, set `MCB_RUNTIME_DIR=/path/to/map/.runtime` when launching the server, bridge, terrain, layout, survey and camera helpers. Their runtime defaults then use that directory; source code and build outputs stay in this checkout. Without the override, standalone setup still uses this repository's `.runtime`. Keep private runtime directories and any local compatibility symlinks out of Git. + ## Camera An ordinary player does not need the mod. For the observer, install the pinned Fabric Loader and API versions and add the camera JAR to a separate 26.2 profile. Before launching, pass `MCB_CAMERA_TOKEN` from the plugin's private `camera-token` setting to that process; set `camera-player-uuid` in Paper and restart the server. The observer must connect in spectator mode. If the builder and observer play simultaneously, they need valid separate game sessions. With one client, set the camera UUID to the owner's UUID and temporarily enable spectator mode. This scenario has been verified in the Prism profile **26.2 MCP Building**; `scripts/camera-wrapper.py` passes the secret to Java without adding it to Prism's logged variables. See the [camera instructions](camera-mod/README.md) for the exact procedure and capture limitations. Once connected, `/ai camera save name` saves the owner's viewpoint. [Local single-client test results](docs/ONE_CLIENT_TEST.md). +For operator inspections of another world on the server, [camera-capture.py](scripts/camera-capture.py) captures an observer already positioned by the operator. It talks only to the authenticated loopback worker and does not teleport, edit blocks, or change the editor's authorized world. The ordinary MCP camera route retains its project checks. + ## Tests and documentation ```bash @@ -105,10 +100,12 @@ JAVA_HOME="$HOME/.cache/minecraft-builder-mcp/jdk-25.0.2" camera-mod/gradlew --p `python3 scripts/live-server-test.py` starts and stops its own process in `.runtime/server`, checks conflicts and undo, deliberately terminates that process to test recovery, and then exercises MCP and `.schem`. First build the project, run the plugin once, and accept the EULA; any existing server must be stopped. This test targets the prepared test world and changes only bounded test regions. Results are saved in `.runtime/live-server-results.json` and `.runtime/live-mcp-results.log`. +This disposable-server test keeps its repository-local paths and does not follow `MCB_RUNTIME_DIR`. Run it only in a checkout whose `.runtime/server` is a disposable test server, not a compatibility symlink to a map's retained world. + - [Project design and future phases](docs/DESIGN.md). - [Implementation status and verification](docs/IMPLEMENTATION.md). - [Runtime materials and compact discovery](docs/MATERIALS.md). -- [Gothic hall from a reference: 30,125 blocks in the live world](docs/builds/GOTHIC_HALL.md). +- [Decoration workflow and placement checks](docs/DECORATION.md). - [Protocol](docs/PROTOCOL.md), [editing core and journal](world-core/README.md). - [Bridge, sign-in, and ACP limitations](bridge/README.md). @@ -118,10 +115,7 @@ Git is initialized on branch `main`. Generated worlds, secrets, dependencies, an Deterministic terrain recipes now support hills, ridges, plateaus, dry basins/channels and terraces. `terrain_preview` returns a native heightmap and cached recipe ID; `terrain_prepare` creates a checked tile plan for the existing apply/undo pipeline. Offline previews and bounded resumable batches are available through `scripts/terrain.py`. See [Terraforming tools](docs/TERRAFORMING.md) for examples, safety semantics and scale limits. -For a fresh map, the optional [initial terrain world plugin](terrain-world-plugin/README.md) generates a separate world directly from a recipe, with optional lakes and rivers at a configured water level. The [Shacraft lobby recipe](examples/terrain/shacraft-lobby-world.json) lays out a 768×768 mountain basin with building platforms. Initial generation has its own immutable manifest; subsequent construction uses the normal scoped editor. - `terrain_brush_prepare` additionally edits existing terrain relatively: raise/lower, flatten and snapshot-based smoothing, with soft edges, preserved columns, native before/after previews and checked read dependencies. See the relative-brush section of the terraforming guide. -The natural Shacraft v2 world replaces rectangular terrain platforms with a warped ridged mountain basin, soft hills and connected water courses. The live generation pass checked every column, including surface materials and water fill. -![Shacraft natural v2 terrain, real in-game capture](docs/references/shacraft-natural-v2-overview.png) +World-generation profiles, map geometry, custom gameplay plugins and construction archives are external project resources. They are not installed by the MCP build or exposed as implicit MCP tools. diff --git a/bridge/README.md b/bridge/README.md index fa1f253..b6b1306 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -105,6 +105,6 @@ Deterministic terrain recipes now support hills, ridges, plateaus, dry basins/ch ## Shared building guidance -`src/building-guidance.ts` is the shared source for material discovery, terrain art direction and decoration used by MCP server instructions and the ACP agent. It prioritizes focused material searches and reuse of defaults, deliberate silhouettes, calm walking areas, localized mountain detail, organic foundations, slope-aware materials, preservation and real camera verification. Tool descriptions state the exact schema boundaries: the current MCP terrain recipe offers amplitude/scale value noise and a limited palette; the advanced `shacraft-natural-v1` profile and initial water generation are operator-configured features. Ordinary building plans can place registered fluid states; terrain brushes still cannot scan through water. +`src/building-guidance.ts` is the shared source for material discovery, terrain art direction and decoration used by MCP server instructions and the ACP agent. It prioritizes focused material searches and reuse of defaults, deliberate silhouettes, calm walking areas, localized mountain detail, organic foundations, slope-aware materials, preservation and real camera verification. Tool descriptions state the exact schema boundaries: the current MCP terrain recipe offers amplitude/scale value noise and a limited palette; advanced noise and initial-world generation require a separately supplied external generator and are not assumed to be installed or available as MCP tools. Ordinary building plans can place registered fluid states; terrain brushes still cannot scan through water. ACP stores a fingerprint of successfully delivered instructions with its session state. An older or missing fingerprint causes the updated guidance to be sent on the next prompt in a resumed session, preserving history. Once delivered, unchanged guidance is not repeated on every turn or restart. Transport failures leave the old fingerprint so delivery can be retried. diff --git a/bridge/src/building-guidance.ts b/bridge/src/building-guidance.ts index f67b749..5345e12 100644 --- a/bridge/src/building-guidance.ts +++ b/bridge/src/building-guidance.ts @@ -2,10 +2,10 @@ export const MATERIAL_GUIDANCE = `Material discovery: project_context contains only a catalog summary. All vanilla block states registered by the running server are available to ordinary build recipes, including water; item-only materials can be discovered but cannot be placed as blocks. Use material_search with a focused query, then material_describe for 1–3 chosen materials to obtain exact default states and allowed property values. Reuse those results while the catalog version is unchanged; do not enumerate the registry or repeatedly describe known materials. Omitted properties use server defaults. Block states do not accept raw NBT or configure inventories/sign text; same-material edits preserve existing block-entity data. Pass through region_inspect snapshot_id hashes in expected_blocks to detect changes to that data. Terrain recipes and brushes retain their separate limited palettes and fluid restrictions.`; export const TERRAIN_GUIDANCE = `Terrain art direction: compose a readable skyline and calm playable valleys before adding detail. Prefer broad low-frequency relief, localized ridged mountain detail and modest coordinate warping when the available generator supports them. Independent seeded noise layers reduce repetition; more octaves alone do not fix bad composition. Keep fine detail weak along walking routes. Preserve sightlines, water courses and space for architecture. Avoid large rectangular plateaus unless explicitly requested: adapt buildings to the landscape and flatten only their actual foundations with wide, irregular transitions. Preserve existing terrain instead of repeatedly flattening whole districts. Use rock on steep slopes and soil/grass on gentle ground; avoid grass-and-dirt contour stripes across cliffs. Grade routes and entrances separately; natural-looking terrain is not automatically walkable. -Capability boundary: MCP terrain recipes currently expose three-octave value noise through amplitude/scale only. OpenSimplex2S, ridged noise and domain warping are available in the separately configured shacraft-natural-v1 initial-world profile, NOT as terrain_preview parameters or callable MCP tools. Do not invent noise, spline, erosion, biome or fluid APIs. MCP terrain tools do not place water; the initial-world generator can fill lakes/rivers at a configured level. Brushes reject water in their scan. Preserve waterways and their beds; request operator-side generation work if the task requires unsupported capabilities. There is no hydraulic erosion simulation or initial-generation block undo. +Capability boundary: MCP terrain recipes currently expose three-octave value noise through amplitude/scale only. Advanced noise families, domain warping and initial-world generation require a separately supplied external generator, NOT as terrain_preview parameters or callable MCP tools. Do not assume any map profile or external generator is installed. Do not invent noise, spline, erosion, biome or fluid APIs. MCP terrain tools do not place water; ordinary building plans can place registered fluid block states. External generators may have different capabilities that require separate verification. Brushes reject water in their scan. Preserve waterways and their beds; request operator-side generation work if the task requires unsupported capabilities. The MCP terrain tools provide no hydraulic erosion simulation or undo for external world generation. Workflow: inspect a small relevant region, preserve structures and their foundations, preview composition, then apply bounded checked plans. A target heightmap is not a Minecraft screenshot. Inspect terrain from an overview and a player-height view when the camera is available; check silhouettes, chunk seams, shores, materials and route slopes. Poll pending captures by capture_id until completed or a reported error; report that error rather than claiming visual verification. On a shared camera client, ask the player to close menus and stay still. Distinguish proposed, prepared, server-applied and visually inspected work. Keep summaries compact; reuse terrain_id and operation IDs instead of dumping blocks or regenerating equivalent recipes.`; -export const DECORATION_GUIDANCE = `Decoration art direction: Establish purpose and silhouette before adding detail. Use a coherent palette, grouped texture variation and structural depth; avoid random high-contrast block noise. Preserve quiet surfaces, walking clearance, entrances and important sightlines. Build one small prototype, inspect it, then repeat its motifs with controlled variation. Discover selected materials and exact block states through material_search and material_describe. Verify orientation, attachment, support and stable leaves; decorative partial blocks do not guarantee containment. Keep unfinished rooms closed. Inspect actual captures from player height and an overview, in daylight and at night when available; state which views or lighting conditions remain unverified. Confirm server-applied changes before reporting completion. Documented decoration recipes are design specifications, not callable tools; translate selected details into supported bounded plans and preserve manual edits.`; +export const DECORATION_GUIDANCE = `Decoration art direction: Establish purpose and silhouette before adding detail. Use a coherent palette, grouped texture variation and structural depth; avoid random high-contrast block noise. Preserve quiet surfaces, walking clearance, entrances and important sightlines. Build one small prototype, inspect it, then repeat its motifs with controlled variation. Discover selected materials and exact block states through material_search and material_describe. Verify orientation, attachment, support and stable leaves; decorative partial blocks do not guarantee containment. For open trapdoors, derive facing from the physical edge touching the support: the panel occupies the edge opposite facing. Verify one side view before repeating it; do not invert horizontal lids or already correct facade panels. Keep unfinished rooms closed. Inspect actual captures from player height and an overview, in daylight and at night when available; state which views or lighting conditions remain unverified. Confirm server-applied changes before reporting completion. Documented decoration recipes are design specifications, not callable tools; translate selected details into supported bounded plans and preserve manual edits.`; export const AGENT_BASE = `You are the Minecraft builder for the current authorized project. Respond in the player's language using short game-chat messages. Use only minecraft-builder-mcp to inspect and edit the world. Begin each new task with project_context; read relevant world data before designing. Only implemented server capabilities may be used. Prepare compact geometry, inspect statistics, apply using stable idempotency keys, and poll operation_status. Preserve manual edits: conflict requires localized redesign or the user's decision, never blindly overwrite fresh snapshots. A cancelled or failed operation may have partial writes. Camera requests are asynchronous; poll capture_id and inspect actual image. If unavailable, explicitly say visually unverified. Never use console commands, shell, files, or other MCP servers to modify Minecraft. Do not claim any action completed without server evidence.`; diff --git a/bridge/test/acp.test.mjs b/bridge/test/acp.test.mjs index bb014f0..ed57af6 100644 --- a/bridge/test/acp.test.mjs +++ b/bridge/test/acp.test.mjs @@ -28,7 +28,9 @@ test('updated terrain and material guidance reaches resumed ACP sessions once wi session=new CodexSession({projectId:'p',playerId:'u'},options); await session.prompt('same guidance after restart',async()=>{}); const calls=await records();const prompts=calls.filter(v=>v.method==='session/prompt').map(v=>v.params.prompt[0].text); - assert.ok(prompts[1].includes('shacraft-natural-v1')); + assert.ok(prompts[1].includes('separately supplied external generator')); + assert.ok(!prompts[1].includes('shacraft')); + assert.ok(prompts[1].includes('panel occupies the edge opposite facing')); assert.ok(prompts[1].includes('Avoid large rectangular plateaus')); assert.ok(prompts[1].includes('NOT as terrain_preview parameters')); for(const prompt of [prompts[0],prompts[1]]) { diff --git a/camera-mod/README.md b/camera-mod/README.md index 27ba490..c5415de 100644 --- a/camera-mod/README.md +++ b/camera-mod/README.md @@ -47,6 +47,20 @@ python3 scripts/live-camera-test.py --camera-id test --delay 8 Before capture begins, return to Minecraft, close chat and menus, stop moving, and keep the mouse still. Rotation tolerance is only 0.1°, so even slight movement cancels the image. Switching windows may open the pause menu. In this mode, capture temporarily uses your game view; the server teleport changes your position. Your game mode and previous position are not restored automatically. Choose the desired mode and location manually when returning to building. +### Operator capture of an already positioned observer + +[scripts/camera-capture.py](../scripts/camera-capture.py) can inspect a separate world after an authorized operator has placed the spectator there. This local CLI is not an additional MCP tool: it neither teleports nor edits the world, and does not change Paper's active editor project. Use the actual client dimension key and observer feet position: + +```bash +MCB_RUNTIME_DIR=/path/to/map/.runtime python3 scripts/camera-capture.py \ + --dimension minecraft:overworld --pose 16.5 90 16.5 45 25 --fov 80 \ + --out /path/to/map/.runtime/captures/overview.png +``` + +The default private config is `MCB_RUNTIME_DIR/server/plugins/MinecraftBuilderMCP/config.yml`, falling back to this checkout's `.runtime/server`. `MCB_CAMERA_PAPER_CONFIG` overrides that default; `--config` overrides both. The helper disables proxies and redirects, authenticates to `127.0.0.1`, bounds responses, correlates the capture UUID/dimension/timestamp, and checks the PNG and its dimensions. It writes unchanged PNG bytes and an allowlisted `.capture.json` receipt with a SHA-256. It does not certify a server revision or restore the operator's previous viewpoint. Existing output files at the selected paths are replaced. The normal readiness and spectator requirements still apply. + +When positioning through the vanilla console, write explicit decimal X/Z coordinates, such as `16.0`, when the capture requests exactly `16.0`. Integer X/Z arguments to `/teleport` select block centres and can become `16.5`. That half-block offset exceeds the camera's position tolerance and causes a timeout. Use the same feet position, dimension and rotation in the teleport and capture request. A completed real-world smoke capture verified this operator path on Paper 26.2; it remains separate from the MCP project's scoped camera route. + ## Protocol All requests, including health checks and image retrieval, require `Authorization: Bearer `. JSON is not written to the log. diff --git a/docs/DECORATION-PLAYBOOK.md b/docs/DECORATION-PLAYBOOK.md deleted file mode 100644 index 50b487b..0000000 --- a/docs/DECORATION-PLAYBOOK.md +++ /dev/null @@ -1,115 +0,0 @@ -# Minecraft decoration playbook - -Research reviewed **13 September 2026** for the Shacraft lobby. These notes combine primary Minecraft articles, interviews with builders and official Java release notes with our own measured-building workflow. They record reusable design knowledge; this research stage makes no changes to the world. - -Start with this page, then open only the relevant entry in the [14-recipe cookbook](DECORATION-RECIPES.md). The [structured catalog](recipes/decorations.json) is the authoritative source for recipe dimensions, materials, assembly and acceptance checks. Recipes are original design proposals, not copied tutorial schematics, executable `build_prepare` payloads or claims of completed construction. - -## Short working memory - -1. Give the place a purpose and identify the main arrival, decision point and place to pause. -2. Resolve silhouette, structural rhythm and major material fields before surface details. -3. Assign each material a role. Keep a dominant family, supporting material and selective accent. -4. Make depth with real layers: a recessed window, a projecting sill, a supported eave. Keep the closure behind decorative partial blocks intact. -5. Cluster texture changes where wear, moisture, construction or planting explains them. Preserve calm surfaces between accents. -6. Arrange furniture as useful groups. Keep entrances, lift controls and destination signs visible. -7. Measure the actual ground, floor and route before placing a standard module. Preserve established widths and headroom. -8. Check material availability, exact states, rotation, attachment, substrate and stability after updates. -9. Build one sample, inspect it at player height and from the approach, then repeat with controlled variation. -10. Compare real day/night photographs and inspect concealed sides. A map, plan or successful placement receipt is not a lighting review. -11. Keep unfinished rooms closed. Decoration must not create a passage or a way around an existing enclosure. -12. Save recipe IDs, anchors, rotations, seeds, operation receipts and selected photographs; load only the local context needed for the next edit. - -These are project working rules. Numeric dimensions below are starting proposals for this lobby, not universal Minecraft laws. - -## What the research changes in our practice - -### Shape and depth before micro-detail - -Aegos and Shannooty describe working out roofs and the overall silhouette before detailing. Milosz explains using a consistent palette and comparatively simple textures at large scale. For Shacraft, the clock tower should remain the first readable feature; a dormer, finial or cornice is useful only if it strengthens that composition. Review a proposed feature from the main approach before copying it around the roof. [Tranquil Towers](https://www.minecraft.net/en-us/article/tranquil-towers), [The Sky is No Limit](https://www.minecraft.net/en-us/article/sky-no-limit). - -The glass-pane article explicitly connects windows with variation in wall depth. Our adaptation uses a recessed **full-glass closure** and a projecting stone surround, since public containment is part of this project. Thin panes can be useful elsewhere, but their appearance does not prove a sealed boundary. Quartz also provides a consistent family for architecture and small furnishings. [Taking Inventory: Glass Pane](https://www.minecraft.net/en-us/article/taking-inventory--glass-pane), [Build With It: Quartz](https://www.minecraft.net/en-us/article/build-with-it--quartz). - -### Material variation should explain the surface - -Rough diorite and its polished form demonstrate that texture intensity changes the character of a surface; a brighter material is not automatically a quieter one. Mossy cobblestone can suggest age. Our design inference is to place texture by cause: moisture and earth contact at a retaining wall, wear near an approach, clean edges around a maintained station entrance. Use connected patches rather than an independent random material choice for every block. [Build with It: Diorite!](https://www.minecraft.net/en-us/article/build-it--diorite-), [Build With It: Cobblestone!](https://www.minecraft.net/en-us/article/build-with-it--cobblestone-). - -A color gradient is optional. If used, it should reinforce a chosen mass or light direction and read at the intended distance. Do not impose an arbitrary weathering percentage or a fixed color-ratio formula on every building. We should compare a small clean sample and a restrained textured sample before committing a facade. - -### Interior groups should tell players how to use the room - -The builders in *Interior Motives* emphasize purpose and agreement between inside and outside. Jonathan “SnugSites” discusses livable furniture arrangements and the value of two-block partitions when rooms need different finishes on each face. We should build waiting groups, information points and viewing pockets, reserving a clear route between them; an empty section of floor can be necessary circulation. [Interior Motives](https://www.minecraft.net/en-us/article/interior-motives), [Fantastic Furniture](https://www.minecraft.net/en-us/article/fantastic-furniture). - -Spruce, plants and lanterns are useful companions to copper and masonry in *Cozy Chambers*. For our station this means warmth at the scale of benches, counters and exhibits, with a limited number of focal groups. A Minecraft article also documents a flowerpot above a decorated pot as a large planter. The original cookbook used a small stone planter; the runtime catalog now makes registered pot block types discoverable, while custom pot decoration and stored-item data remain outside the ordinary state editor. [Cozy Chambers](https://www.minecraft.net/en-us/article/cozy-chambers), [Decorated Pot](https://www.minecraft.net/en-us/article/decorated-pot). - -### Decorate the transition between architecture and landscape - -The official presentation of BlueNerd's landscaping work connects paths, banks, soil and vegetation. Zaypixel's path examples show how material families and nearby objects communicate different settings. Our application is a clean, formal station axis that gradually gives way to local rock, soil and planted pockets farther out. Curve a route when its destination or terrain calls for it. Do not force a kink every seven blocks: that number belongs to one organic-building tutorial, not the engine or every architectural style. [Landscaping and Terraforming](https://www.minecraft.net/en-us/article/tutorial--tips-for-landscaping-and-terraforming), [Five simple path designs](https://www.minecraft.net/en-us/article/five-simple-path-designs). - -Kelpie the Fox's outdoor groups provide useful roles for benches, small structures and hanging lights. The article identifies **Mizuno's 16 Craft** in its reference imagery. Borrow composition and purpose, then judge our result in the actual client textures; matching block names alone cannot reproduce a resource-pack image. [Cottagecore Decor](https://www.minecraft.net/en-us/article/tutorial--cottagecore-decor). - -### Lighting requires its own review - -Regular lanterns are a useful recurring fixture, but a fixture count does not establish room legibility. Look at the spaces between lights, stair landings, sign faces and exhibit fronts. We already used real station photographs to find dark interiors and refine concealed lighting; see the [completed station record](SHACRAFT-STATION.md). [Taking Inventory: Lantern](https://www.minecraft.net/en-us/article/taking-inventory--lantern). - -**Tinted glass is not stained glass:** the Java release notes describe tinted glass as blocking light. Do not use it as a substitute for a transparent lighting lens. The same release notes document waxed copper as preventing oxidation. These facts explain material choices; exact accepted states still come from our current server. [Java Caves & Cliffs Part I](https://www.minecraft.net/en-us/article/caves---cliffs--part-i-out-today-java). - -## Shacraft palette and composition - -The following assignments are our design direction based on the existing arrival garden and station, rather than claims made by the sources: - -- **Large cream masses:** `smooth_sandstone`, with `cut_sandstone` for purposeful masonry bands. -- **Clean prominent edges:** `smooth_quartz` and `quartz_pillar`; use supported sandstone stairs/slabs when a partial shape is needed. -- **Roofs and identity:** the `waxed_oxidized_cut_copper` family. Keep broad roof planes visible. -- **Human-scale warmth:** spruce for benches, counters, ceilings and small structural posts. -- **Selective emphasis:** gold at a major identity or control point, with green backing. Repeated small gold accents must not compete with the main sign. -- **Grounded base:** stone bricks, andesite and restrained moss near actual soil or damp recesses. -- **Planting:** a dominant evergreen mass and small pink/white flower groups, continuing zone 01's established vocabulary. -- **SMASH:** preserve the station frame; use a contained island exhibit and restrained warm accent to distinguish this floor. - -Place more detail at an entrance, destination or stopping point. Leave broader quiet surfaces and uninterrupted paving between those locations. This is a distribution choice, not a ban on ornament. - -## Recipe index - -Each cookbook entry includes dimensions, palette, assembly order, clearance, controlled variants and failure checks: - -- Facade: `arched-window-bay`, `cornice-bracket`. -- Roof: `closed-dormer`. -- Interior: `waiting-pocket`, `information-counter`, `smash-exhibit`. -- Routes: `avenue-edge`, `retaining-stair`. -- Lighting: `copper-lantern`, `concealed-light-lens`. -- Planting and landscape: `layered-planter`, `shore-pocket`. -- Small props and surfaces: `luggage-bench`, `masonry-weathering`. - -## Current capability boundary - -The saved Stage 07 `project_context` lists **100 material IDs**. All base recipe material lists were checked against that historical snapshot. The structured recipes retain that dated capability basis so earlier designs remain reproducible; it is no longer the building allowlist. The runtime implementation in [`BuildingWorld.java`](../paper-plugin/src/main/java/io/github/minecraftbuilder/paper/BuildingWorld.java) now accepts all registered vanilla block types and their valid states. Read the live catalog summary, then discover only the materials needed for the next detail through `material_search` and `material_describe`. [Material workflow](MATERIALS.md). - -The original recipe catalog uses flush colored paving instead of carpet, masonry planters instead of pots, full stained glass instead of panes, and wooden block props instead of item entities. These remain deliberate design variants. Registered carpets, trapdoors, flowerpots, decorated pots, banners, candles, wildflowers, leaf litter and firefly bushes can now be selected through the runtime catalog. Block-type availability does not add arbitrary banner patterns, pot decorations, sign text, inventories or text-display creation; the station's existing labels use a separate project-specific plugin. Item-only materials are searchable but cannot be placed by a block recipe. - -Java 1.21.5 introduced useful plant density/orientation options; its firefly bush emits only light level 2, so it should be treated as an atmospheric accent rather than primary street illumination. Java 26.2 also adds sulfur and cinnabar material families. Material IDs and exact properties come from the running server's registry, so search before using a version-specific feature. Additional behavior is not a new callable MCP API. [Java 1.21.5 release notes](https://feedback.minecraft.net/hc/en-us/articles/35298208390797-Minecraft-Java-Edition-1-21-5-Spring-to-Life), [Java 26.2 release notes](https://www.minecraft.net/en-us/article/minecraft-java-edition-26-2). - -For this runtime, discover the chain ID rather than assuming an older name; the saved station uses `minecraft:iron_chain`. Use `persistent=true` for decorative leaves unless deliberate natural decay is intended, and check distance state on read-back. Flowers need accepted substrates, logs need real support, and stairs/slabs/lanterns need the correct directional and attachment states. Place every half or part of doors, beds and tall plants explicitly. Ordinary building plans can place fluids and waterlogged states, but terrain brushes still reject water in their scan. The existing shore recipe remains a land-side design that preserves water. Fluid flow, random ticks and other later game behavior are not fully reversed by undoing only a plan's direct writes. - -## Applying a recipe economically - -The Markdown cookbook is for human review; the JSON makes one entry easy to retrieve without reading the entire document. For a local repository agent: - -```bash -jq '.recipes[] | {id, category, size}' docs/recipes/decorations.json -jq '.recipes[] | select(.id == "copper-lantern")' docs/recipes/decorations.json -``` - -Save a placement record with recipe ID, recipe-catalog version and runtime material-catalog version, local origin, rotation, chosen variant, terrain/floor anchor, palette substitutions, variation seed and operation IDs. Use a focused `material_search` page (16 results by default, 32 maximum), then describe only 1–3 selected materials. Reuse their defaults and property domains while the runtime catalog version is unchanged. Inspect the target area and its support/clearance neighborhood, then compile a bounded expected-state patch. Copy each block-entity `snapshot_id` from `region_inspect` into `expected_blocks` to detect data edits without reading inventories or NBT into context. Keep the checked operation receipts and a small set of matching photographs. Large-scale repetition comes after a successful sample. - -The shared [MCP/ACP guidance](../bridge/src/building-guidance.ts) includes short material-discovery and decoration summaries. Neither the full recipe catalog nor the material registry is injected into every model turn. The recipe JSON is not a new MCP resource or tool: the in-game ACP agent has no repository filesystem access, so its instructions must not pretend it can retrieve this file. New bridge processes receive the updated summary, and resumed ACP sessions receive changed guidance once through its stored fingerprint. Deployment and validation of the registry expansion are recorded in [MATERIALS.md](MATERIALS.md#validation). - -## Acceptance before calling a decoration complete - -1. **Fit:** same survey anchors, preserved structure, route width and four-block public headroom where that is the existing Shacraft contract. Four blocks is our design margin, not Minecraft's minimum player height. -2. **Stability:** supports, substrates, leaves, attached pieces and rotated states remain valid after block updates. -3. **Movement:** inspect approaches, corners, stairs and ceilings. A decorative fence or hedge does not replace the checked enclosure. Keep unfinished interiors inaccessible. -4. **Visuals:** matched overview, approach and player-height views; day/night where lighting matters. Check sign visibility, density, material contrast and dark exhibit faces in the actual client. -5. **Evidence:** distinguish proposed design, server-applied blocks, measured geometry and visually inspected results. Record an unavailable camera honestly. -6. **Preservation:** keep the before state, checked edits and useful photographs. A later manual edit is a conflict to resolve, not an invitation to overwrite it. - -The 14 catalog entries have been reviewed as design specifications and checked for material-name availability. They have **not** been instantiated or visually approved as a new set of in-world samples. diff --git a/docs/DECORATION-RECIPES.md b/docs/DECORATION-RECIPES.md deleted file mode 100644 index 4de0dcc..0000000 --- a/docs/DECORATION-RECIPES.md +++ /dev/null @@ -1,512 +0,0 @@ -# Shacraft decoration recipes - -Generated from [decorations.json](recipes/decorations.json), the authoritative catalog. Read the [playbook](DECORATION-PLAYBOOK.md) for principles, current capabilities and the acceptance workflow. - -**14 original design proposals, reviewed 13 September 2026.** Material IDs match the saved 100-material Stage 07 palette. These are design references, not executable Minecraft recipes or completed in-world samples. Recheck live capabilities before applying. - -Dimensions below are **width × height × depth** in blocks. They exclude separately specified circulation and buried support. Local +X is right, +Y is up, +Z points into the object; the front faces -Z. Measurements and spacing are project proposals, not engine limits. - -## arched-window-bay - -**Recessed arched window bay** · facade · **7 × 10 × 3 blocks** - -Give a large cream facade readable shadow and a consistent structural rhythm. - -Materials: `smooth_sandstone`, `cut_sandstone`, `smooth_sandstone_stairs`, `smooth_sandstone_slab`, `smooth_quartz`, `brown_stained_glass`. - -Assembly: - -1. Reserve a seven-wide bay between the actual structural axes; keep adjacent columns and floor decks intact. -2. At the glazing plane, use five-wide glass rows for the straight portion, then three and one at the crown. Fill every surrounding cell with full masonry so the plane stays sealed. -3. Place glazing one block behind the front wall plane; use the remaining front depth for a sill and paired jamb brackets. -4. Use sandstone stairs only as outer trim. Maintain full masonry or full glass behind partial-block trim. -5. Repeat the approved bay on the established grid; redesign corners and the main doorway deliberately. - -**Clearance:** Keep projections outside the existing public route and its four-block headroom; do not narrow the seven-block station entry. - -Variants: - -- Plain rectangular head on a secondary facade. -- One gold accent above the principal entrance only. - -Acceptance: - -- Closure plane has no unfilled cells, including arch shoulders. -- Recess reads from a diagonal player-height camera. -- No sill hides a sign or clips furniture. -- Stair rotation and corner shape checked after neighboring blocks update. - -Avoid: - -- Different window silhouettes in every bay. -- Assuming a pane or stair is a full containment wall. - -Conceptual sources: [The Sky is No Limit](https://www.minecraft.net/en-us/article/sky-no-limit), [Taking Inventory: Glass Pane](https://www.minecraft.net/en-us/article/taking-inventory--glass-pane), [Build With It: Quartz](https://www.minecraft.net/en-us/article/build-with-it--quartz). Recipe geometry and sequence are our own proposal. - -## cornice-bracket - -**Cornice and paired bracket** · facade · **9 × 3 × 2 blocks** - -Explain the floor/eave line without filling the wall with ornament. - -Materials: `smooth_sandstone`, `smooth_sandstone_stairs`, `smooth_sandstone_slab`, `smooth_quartz`. - -Assembly: - -1. Place a continuous stone band on the existing floor or eave line. -2. Use an inverted-stair underside and a thin slab cap, with at most two blocks of total outward depth. -3. Put the two bracket groups over structural supports and leave the central field quiet. -4. Continue the band around corners with an explicitly resolved return; prototype the corner before repeating. - -**Clearance:** Remain above route headroom and outside signs' viewing rays; preserve roof drainage shapes and window tops. - -Variants: - -- Single-block projection on small wings. -- A stronger cap at the tower base, using the same materials. - -Acceptance: - -- Horizontal band remains readable from the main approach. -- Brackets align with actual supports. -- Cap does not overwhelm the window opening. - -Avoid: - -- A thick shelf over every horizontal edge. -- Unrelated gold trim on every bracket. - -Conceptual sources: [Tranquil Towers](https://www.minecraft.net/en-us/article/tranquil-towers), [The Sky is No Limit](https://www.minecraft.net/en-us/article/sky-no-limit), [Build With It: Quartz](https://www.minecraft.net/en-us/article/build-with-it--quartz). Recipe geometry and sequence are our own proposal. - -## closed-dormer - -**Closed green-copper dormer** · roof · **7 × 7 × 7 blocks** - -Add a secondary roof feature while keeping the clock tower dominant. - -Materials: `smooth_sandstone`, `brown_stained_glass`, `waxed_oxidized_cut_copper`, `waxed_oxidized_cut_copper_stairs`, `waxed_oxidized_cut_copper_slab`. - -Assembly: - -1. Fit a seven-wide sample to the actual roof pitch; use cream side cheeks and three-wide recessed glass. -2. Build the green pitched cap with a one-block overhang. -3. Keep a solid back and floor closure behind the apparent window; retain the existing public ceiling. -4. Test one dormer from the main approach before any repetition. Consider 14-18 blocks between centers only where the measured structural grid permits it. - -**Clearance:** No opening into an unfinished attic, no overlap with tower faces or main roof ridge. - -Variants: - -- One larger central feature on a secondary wing. -- Omit the dormer if it competes with the clock silhouette. - -Acceptance: - -- Tower remains the primary skyline feature. -- Quiet copper roof planes remain visible between details. -- Full closure survives the added roof cutout. - -Avoid: - -- Treating proposed spacing as a universal rule. -- Adding attic access as part of a cosmetic edit. - -Conceptual sources: [Tranquil Towers](https://www.minecraft.net/en-us/article/tranquil-towers), [The Sky is No Limit](https://www.minecraft.net/en-us/article/sky-no-limit). Recipe geometry and sequence are our own proposal. - -## waiting-pocket - -**Paired waiting benches** · interior · **9 × 4 × 7 blocks** - -Create a believable place to wait beside the main hall. - -Materials: `spruce_stairs`, `spruce_slab`, `spruce_planks`, `smooth_sandstone`, `green_concrete`, `lantern`, `oak_leaves`, `dirt`. - -Assembly: - -1. Place two three-seat spruce benches facing a shared open pocket, with cream end supports. -2. Keep a compact table at one side rather than on the main desire line. -3. Use a flush green paving inset to group the furniture; this substitutes for unsupported carpet. -4. Add one planter and one supported lantern near the back, leaving the front visually open. - -**Clearance:** Reserve an additional three-block-wide clear passing strip and four blocks of route headroom. Existing wider routes retain their width. - -Variants: - -- Single bench and planter in a smaller side recess. -- Mirror the group around the room axis while varying the small prop. - -Acceptance: - -- Lift sign remains visible from the approach. -- Seat fronts and table can be approached without stepping onto furniture. -- Lamp and leaves remain stable after updates. - -Avoid: - -- Rows of isolated decorative chairs. -- Plants in front of navigation signs. - -Conceptual sources: [Interior Motives](https://www.minecraft.net/en-us/article/interior-motives), [Fantastic Furniture](https://www.minecraft.net/en-us/article/fantastic-furniture), [Cozy Chambers](https://www.minecraft.net/en-us/article/cozy-chambers). Recipe geometry and sequence are our own proposal. - -## information-counter - -**Information counter** · interior · **9 × 5 × 4 blocks** - -Provide a clear information point that belongs in a station. - -Materials: `spruce_planks`, `spruce_slab`, `smooth_sandstone`, `smooth_sandstone_slab`, `green_concrete`, `glowstone`, `brown_stained_glass`. - -Assembly: - -1. Frame a seven-block counter between cream end piers; use spruce body and a thin cream slab top. -2. Align one green sign backing and one lit recess behind the counter. -3. Keep the back wall solid. Any future staff door remains a finished wall panel until the room exists. -4. Add actual text only through an existing authorized label mechanism; the block catalog does not create text entities. - -**Clearance:** Reserve three clear queue blocks in front, separated from the entrance-to-lift route and its full width. - -Variants: - -- Short five-wide counter for a side room. -- A small gold emblem only above the principal information point. - -Acceptance: - -- Queue does not cross the main route. -- Counter is recognizable before reading the sign. -- No false usable door into unfinished space. - -Avoid: - -- Several equally bright competing headers. -- Claiming matchmaking or staff interaction exists merely because the counter is built. - -Conceptual sources: [Interior Motives](https://www.minecraft.net/en-us/article/interior-motives), [Build With It: Quartz](https://www.minecraft.net/en-us/article/build-with-it--quartz), [Fantastic Furniture](https://www.minecraft.net/en-us/article/fantastic-furniture). Recipe geometry and sequence are our own proposal. - -## smash-exhibit - -**SMASH exhibit niche** · interior · **7 × 6 × 5 blocks** - -Communicate floating islands and knockback through one readable display. - -Materials: `deepslate`, `smooth_sandstone`, `green_concrete`, `gold_block`, `grass_block`, `dirt`, `glowstone`, `brown_stained_glass`. - -Assembly: - -1. Build a full dark base, full back and light stone frame around a five-wide exhibit. -2. Choose one miniature island or one abstract impact motif as the subject. -3. Put one small gold focal detail near the subject and keep the rest of the frame consistent with adjacent niches. -4. Light the subject from concealed side, base or overhead recesses; preserve full-block base and closure. -5. Use one shared frame for all six future arena places; vary the subject and real destination text only when available. - -**Clearance:** Reserve a three-block viewing strip outside the main circulation aisle; preserve the existing selection aisle. - -Variants: - -- Closed trophy pedestal with a single object. -- Small landscape model for a future named arena. - -Acceptance: - -- Subject is legible from player eye level without entering the display. -- No dark face hides the model's silhouette. -- Placeholders clearly remain unassigned. - -Avoid: - -- Six unrelated palettes. -- A visual void that is also an accidental physical hole. - -Conceptual sources: [Interior Motives](https://www.minecraft.net/en-us/article/interior-motives), [Fantastic Furniture](https://www.minecraft.net/en-us/article/fantastic-furniture), [Cozy Chambers](https://www.minecraft.net/en-us/article/cozy-chambers). Recipe geometry and sequence are our own proposal. - -## avenue-edge - -**Formal avenue edge** · path · **9 × 1 × 12 blocks** - -Connect station stonework to the garden through a continuous legible route. - -Materials: `stone_bricks`, `smooth_stone`, `smooth_sandstone`, `mossy_stone_bricks`. - -Assembly: - -1. Keep seven center columns as continuous clear paving and one flush border column on each side. -2. Survey elevations along the whole segment before decoration; grade transitions as separate geometry. -3. Place furniture in additional two-to-three-block shoulders or wider pockets, not inside the nine-block strip. -4. Keep the station approach clean. Introduce small connected worn patches at outer edges beside planting. -5. Widen at entrances and follow actual destinations rather than adding arbitrary bends. - -**Clearance:** Seven-block clear walking width and four-block headroom; this example must not narrow a wider established route. - -Variants: - -- Use local gray stone farther from the station. -- Curve the outer border while keeping the tread continuous. - -Acceptance: - -- Entire route walkable at all grade changes. -- Flush borders do not introduce a lip. -- No lamp or planter creates a pinch point. - -Avoid: - -- Uniform random scatter of five paving materials. -- Applying an organic-path zigzag rule to a formal station axis. - -Conceptual sources: [Tutorial: Tips For Landscaping and Terraforming](https://www.minecraft.net/en-us/article/tutorial--tips-for-landscaping-and-terraforming), [Five simple path designs](https://www.minecraft.net/en-us/article/five-simple-path-designs), [Build With It: Cobblestone!](https://www.minecraft.net/en-us/article/build-with-it--cobblestone-). Recipe geometry and sequence are our own proposal. - -## copper-lantern - -**Supported copper-arm lantern** · lighting · **3 × 7 × 1 blocks** - -Establish one recognizable outdoor fixture family. - -Materials: `stone_bricks`, `stripped_spruce_log`, `waxed_oxidized_cut_copper`, `iron_chain`, `lantern`. - -Assembly: - -1. With the sample's first above-ground cell at y=0, place a full stone foot at (0,0,0), rooted into surveyed ground below. -2. Raise a post at x=0,z=0 through y=1..5; place a full-block copper arm across x=0..2 at y=6. -3. Place a vertical iron chain at (2,5,0) attached beneath the full arm, then a hanging lantern at (2,4,0). -4. Place support before attached details and inspect resulting block states. -5. Keep the entire post outside the clear path; use a mirrored pair only at a major entry. - -**Clearance:** Four empty cells below the hanging lantern if ground is level with the sample. Recalculate for actual terrain; do not assume the same clearance over a rising stair. - -Variants: - -- Shorter ground-mounted fixture in a planted pocket with no walk-under route. -- One gold marker at a major junction, outside this base recipe. - -Acceptance: - -- Full arm, chain axis and hanging lantern state remain valid after updates. -- Night view shows the next step, intersection and sign between lamps. -- Post cannot be used to bypass an existing containment boundary. - -Avoid: - -- Unsupported lanterns. -- Treating a fixed lamp spacing as guaranteed illumination coverage. - -Conceptual sources: [Tutorial: Cottagecore Decor](https://www.minecraft.net/en-us/article/tutorial--cottagecore-decor), [Taking Inventory: Lantern](https://www.minecraft.net/en-us/article/taking-inventory--lantern), [Caves & Cliffs: Part I out today on Java](https://www.minecraft.net/en-us/article/caves---cliffs--part-i-out-today-java). Recipe geometry and sequence are our own proposal. - -## layered-planter - -**Layered planting pocket** · planting · **7 × 6 × 5 blocks** - -Produce a planted mass with a readable front, middle and background. - -Materials: `dirt`, `grass_block`, `spruce_log`, `spruce_leaves`, `oak_leaves`, `white_tulip`, `pink_tulip`, `oxeye_daisy`, `smooth_sandstone`. - -Assembly: - -1. Shape a low cream rim with solid soil behind it; support the entire pocket down to the existing terrain. -2. Put one small rooted tree toward the rear and two unequal shrub groups below it. -3. Use one dominant foliage species and two restrained flower colors near the front. -4. Leave patches of visible grass between groups; taper density toward the edge. -5. Save a seed and cluster anchors for repeatable variation; change group positions rather than every block independently. - -**Clearance:** Keep the highest mass outside entrance and sign sightlines; no leaves overhang the protected walking clearance. - -Variants: - -- Formal clipped evergreen beside the station. -- Looser shrubs without a tree beside the mountain path. - -Acceptance: - -- Flowers have valid soil. -- Leaves use persistent=true and a stable read-back distance state. -- Trunk has stable soil under it. -- Foliage tint checked in the actual biome. - -Avoid: - -- A flower on every grass block. -- Using leaves as the only containment boundary. - -Conceptual sources: [Cozy Chambers](https://www.minecraft.net/en-us/article/cozy-chambers), [Tutorial: Tips For Landscaping and Terraforming](https://www.minecraft.net/en-us/article/tutorial--tips-for-landscaping-and-terraforming), [Tutorial: Cottagecore Decor](https://www.minecraft.net/en-us/article/tutorial--cottagecore-decor). Recipe geometry and sequence are our own proposal. - -## shore-pocket - -**Quiet shore with a stony recess** · landscape · **12 × 4 × 8 blocks** - -Connect existing water and terrain with a believable local bank. - -Materials: `stone`, `andesite`, `cobblestone`, `mossy_cobblestone`, `dirt`, `grass_block`, `oak_leaves`. - -Assembly: - -1. Sample a short existing shoreline together with its water level and submerged bed. -2. Shape one quiet bank and one small stony recess; maintain continuous ground beneath the visible soil skin. -3. Put exposed rock at the outcrop and moss only in selected damp recesses. -4. Keep vegetation in sheltered land pockets and leave a clear view of the water. -5. This catalog specifies only land-side decor. Preserve water cells and the connected bed; stop if execution requires fluid placement or an unsupported brush through water. - -**Clearance:** Preserve public route width, foundations, water level and any existing waterfront containment. - -Variants: - -- Grass bank with one isolated outcrop. -- Stone recess beside a retaining wall, without changing its footing. - -Acceptance: - -- Water network and visible level preserved. -- No new flow reaches a road or foundation after updates. -- A below-water view shows a continuous bed; top-down appearance alone is insufficient. - -Avoid: - -- Repeating identical coves as tiles. -- Assuming water exists in the MCP material palette. - -Conceptual sources: [Tutorial: Tips For Landscaping and Terraforming](https://www.minecraft.net/en-us/article/tutorial--tips-for-landscaping-and-terraforming), [Build With It: Cobblestone!](https://www.minecraft.net/en-us/article/build-with-it--cobblestone-). Recipe geometry and sequence are our own proposal. - -## retaining-stair - -**Retaining wall and short stair** · path · **9 × 5 × 10 blocks** - -Make a grade change feel supported and intentionally connected. - -Materials: `stone_bricks`, `stone_brick_stairs`, `stone_brick_slab`, `smooth_sandstone`, `smooth_sandstone_slab`, `mossy_stone_bricks`, `dirt`. - -Assembly: - -1. Survey lower and upper approaches first; adapt the proposed envelope to the actual rise. -2. Reserve five clear stair columns and two shoulder columns per side. -3. Use three or four one-block rises followed by a three-block-deep landing; fill every tread and retaining wall down to solid ground. -4. Return the side walls into the upper terrace instead of ending them as disconnected pillars. -5. Keep the cream cap continuous; limit moss to local soil-contact or shaded recesses. - -**Clearance:** Five clear stair blocks and four-block headroom along the full ascending path, including caps and any overhead fixture. - -Variants: - -- A shorter flight for a lower terrace. -- An additional landing and separate second flight when the actual rise requires it. - -Acceptance: - -- Stair facing matches direction of ascent. -- Approach and landing elevations agree with the survey. -- No hollow exposed underside or unsupported cap. -- Shoulders do not create a climb-over route around containment. - -Avoid: - -- Solving a height mismatch with a decorative slab at the doorway. -- Placing a standard stair module without measuring the destination. - -Conceptual sources: [Tutorial: Tips For Landscaping and Terraforming](https://www.minecraft.net/en-us/article/tutorial--tips-for-landscaping-and-terraforming), [Five simple path designs](https://www.minecraft.net/en-us/article/five-simple-path-designs), [Build With It: Cobblestone!](https://www.minecraft.net/en-us/article/build-with-it--cobblestone-). Recipe geometry and sequence are our own proposal. - -## luggage-bench - -**Bench and luggage group** · props · **7 × 3 × 4 blocks** - -Tell a station story with a small, useful-looking prop group. - -Materials: `spruce_stairs`, `spruce_planks`, `stripped_spruce_log`, `smooth_sandstone`, `green_concrete`, `gold_block`. - -Assembly: - -1. Put a three-seat bench against the back of a widened pocket, facing the plaza. -2. Group two wood-toned luggage shapes at one end, leaving seat fronts clear. -3. Reserve a green backing for one real destination or station identity plaque. -4. Use at most one small metallic accent in the group; omit it if it attracts more attention than the navigation. - -**Clearance:** Provide a separate three-block passing strip in front and preserve any wider existing route. - -Variants: - -- Bench alone. -- Bench with one planter instead of luggage. - -Acceptance: - -- Object reads as a resting place from the walking path. -- Luggage does not intrude into the bench approach. -- No label advertises an unfinished accessible destination. - -Avoid: - -- Crates in every empty corner. -- Unsupported decorative item entities or invented text APIs. - -Conceptual sources: [Interior Motives](https://www.minecraft.net/en-us/article/interior-motives), [Fantastic Furniture](https://www.minecraft.net/en-us/article/fantastic-furniture), [Five simple path designs](https://www.minecraft.net/en-us/article/five-simple-path-designs). Recipe geometry and sequence are our own proposal. - -## concealed-light-lens - -**Full-block concealed light lens** · lighting · **3 × 2 × 3 blocks** - -Illuminate a large room without adding a hanging fixture to every bay. - -Materials: `glowstone`, `brown_stained_glass`, `smooth_sandstone`. - -Assembly: - -1. Reserve a three-by-three floor sample outside patterned paving, tree roots, landings and interactive controls. -2. Keep full paving in the eight perimeter columns; in the center put glowstone below a full brown stained-glass block flush with the walking surface. -3. Keep supporting construction below the sample intact and record expected states before replacement. -4. Prototype one lens; increase coverage only after matching night and interior photographs reveal dark areas. -5. A ceiling variant reverses the emitter and visible lens positions within an intact full-block ceiling. - -**Clearance:** Top of the floor lens remains flush. Ceiling version preserves all existing route headroom and sealed upper rooms. - -Variants: - -- Use ordinary full glass if the brown lens makes the sample too dark. -- Concealed side lighting for an exhibit rather than more floor lights. - -Acceptance: - -- Full-block collision remains at the floor/ceiling surface. -- Actual captured room and exhibit faces become legible. -- No new bright fixture competes with the entrance or lift. - -Avoid: - -- Tinted glass: it blocks light and is not interchangeable with stained glass. -- Assuming more fixtures automatically produce a better interior. - -Local precedent: Stage 07 used concealed brown-glass lighting and real camera review; this reusable sample has not itself been instantiated. - -Conceptual sources: [Cozy Chambers](https://www.minecraft.net/en-us/article/cozy-chambers), [Caves & Cliffs: Part I out today on Java](https://www.minecraft.net/en-us/article/caves---cliffs--part-i-out-today-java). Recipe geometry and sequence are our own proposal. - -## masonry-weathering - -**Cause-based masonry weathering** · surface · **12 × 6 × 1 blocks** - -Add age and material variation while retaining large calm wall fields. - -Materials: `stone_bricks`, `cracked_stone_bricks`, `mossy_stone_bricks`, `andesite`, `smooth_sandstone`, `cut_sandstone`. - -Assembly: - -1. Choose either the gray retaining-wall family or the cream facade family; do not mix both randomly. -2. Save a mask for soil contact, sheltered joints and selected worn edges. -3. For gray masonry, use small connected mossy patches in the mask and a few connected cracked areas away from structural focal edges. -4. For cream masonry, keep the field mostly smooth and use cut sandstone to express masonry bands or quiet local variation; do not add green flecks everywhere. -5. Preserve trim, signs and dominant shapes, then inspect at near, approach and whole-building distances. - -**Clearance:** Surface substitution only; preserve structural collision, public closure and existing manual edits. - -Variants: - -- Clean maintained station entrance with minimal weathering. -- More aged retaining wall near shaded soil contact. - -Acceptance: - -- Weathering locations have a visible cause. -- Large forms still read from the approach. -- Exact seed and patch anchors recorded for reproducibility. - -Avoid: - -- Independent random choice at every voxel. -- A universal percentage of damage on every material. -- Replacing load-bearing full cubes with stairs merely for texture. - -Conceptual sources: [The Sky is No Limit](https://www.minecraft.net/en-us/article/sky-no-limit), [Build With It: Cobblestone!](https://www.minecraft.net/en-us/article/build-with-it--cobblestone-), [Build with It: Diorite!](https://www.minecraft.net/en-us/article/build-it--diorite-). Recipe geometry and sequence are our own proposal. diff --git a/docs/DECORATION.md b/docs/DECORATION.md new file mode 100644 index 0000000..2e61c5c --- /dev/null +++ b/docs/DECORATION.md @@ -0,0 +1,13 @@ +# Decoration workflow + +Decoration uses the ordinary checked building tools. A design recipe is a specification to translate into geometry; it is not an additional MCP tool. Keep building-specific palettes, coordinates, references and construction records in the map project. + +1. Read `project_context` and inspect the relevant part of the world. Establish the room's purpose, main silhouette and clear routes before adding detail. +2. Choose a coherent palette. Use `material_search` for focused discovery and `material_describe` for a few exact defaults and property domains; reuse them while the catalog version is unchanged. See [material discovery](MATERIALS.md). +3. Build one small prototype. Check proportions, supports, orientation and daylight appearance before repeating its motif. Leave calm surfaces between focal details and use texture variation in deliberate patches. +4. Preserve entrances, circulation, sightlines and unfinished-space closures. Thin decorative blocks do not prove containment. Doors, beds and tall plants need all their component blocks; leaves usually need `persistent=true`. +5. For an open trapdoor, choose the physical panel edge first: the panel occupies the edge **opposite `facing`**, with a thickness of 3/16 block. Check that it touches the intended support instead of leaving a 13/16-block gap. Horizontal lids use `half`; already correct panels must not be globally inverted. +6. Apply bounded plans with fresh expected states, including `snapshot_id` for existing block entities. Stop on conflicts and preserve manual changes. Use checked undo when a completed detail needs to be removed. +7. Inspect actual Minecraft captures from player height and an overview; check night lighting when relevant. State which views remain unverified. A matching block-state list does not establish good lighting or physical panel contact. + +The short shared instructions live in [building-guidance.ts](../bridge/src/building-guidance.ts). They are supplied to MCP and ACP clients without loading an entire recipe library into every turn. External map documents are not implicitly accessible to the in-game ACP agent. diff --git a/docs/FLOATING-ISLANDS.md b/docs/FLOATING-ISLANDS.md new file mode 100644 index 0000000..69a6803 --- /dev/null +++ b/docs/FLOATING-ISLANDS.md @@ -0,0 +1,95 @@ +# Floating-island geometry + +[`scripts/voxel-island.py`](../scripts/voxel-island.py) is a deterministic offline +Python helper. It returns solid vertical columns for a floating island. It does +not contact Minecraft, place blocks, or register an MCP tool. Surface grading, +materials, buildings and gameplay remain the caller's responsibility. + +Run this example from the MCP checkout: + +```python +import importlib.util +from pathlib import Path + +spec = importlib.util.spec_from_file_location("islands", Path("scripts/voxel-island.py")) +islands = importlib.util.module_from_spec(spec) +spec.loader.exec_module(islands) + +columns = islands.columns( + cx=120, cz=-80, radius_x=18, radius_z=13, + surface_y=144, depth=30, seed=27, contour=0.055, +) +for (x, z), column in columns.items(): + for y in range(column["bottom"], column["top"] + 1): + pass # Add this coordinate to an offline blueprint, with a chosen material. +``` + +`bottom` and `top` are inclusive integer block coordinates. `radial` is normalized +distance from the shaped outline: zero at the centre, one at the rim. The top is +flat; modify it deliberately for circulation instead of adding surface noise by +default. Keep generated volumes bounded before expanding columns into voxels. + +## Shape and noise + +The outline starts with an ellipse. After dividing offsets by the two radii, the +angular boundary is `1 + contour * (sin(3θ + phase) + 0.45*sin(7θ - phase))`. +Low-frequency lobes establish a readable silhouette; the weaker seventh harmonic +adds smaller variations. Accepted contour strength is `0..0.2`. The boundary +therefore remains positive and at most `1.29` times the base ellipse; the helper's +`1.3`-radius sampling envelope contains it. + +The underside uses a radial taper, `(1 - radial**1.55)**0.62`, multiplied by +correlated value noise sampled every 7.5 blocks. A smaller field sampled every +3.8 blocks adds hanging ribs. Smoothstep interpolation makes neighbouring samples +coherent. Thickness is rounded once and is at least three blocks. `depth` controls +the main taper, not an exact maximum thickness: the base and ribs add several +blocks. Read the returned bounds instead of predicting a fixed lowest Y. + +Use broad noise fields for material bands as well. Independent random material +choices at every block create speckles and conceal the shape. Keep the seed and +generator version with the resulting blueprint. The underside noise is anchored +in world X/Z: moving the centre preserves the translated outline but can change +its thickness field. + +This column model is a structural starting point. A flat top with many similarly +tall perimeter columns can look like a cut cylinder even when the outline and +tip are irregular. Inspect an actual oblique close view before adding more noise. +Use a few connected clefts, varied rock ribs and restrained edge vegetation to +break the sidewall silhouette. Keep a clear fighting or walking surface where +the design needs one; sculpting the underside does not require roughening every +usable floor. Preserve a versioned baseline and validate support again after +the decorative pass. + +## Checks before placing a blueprint + +- Flood-fill the intended top footprint using four-neighbour adjacency. The + continuous outline is radial, but discrete connectivity should still be checked + for the chosen radii and seed. Check separate islands remain separate. +- Require the entire base of each pier, stair approach and planter to have + support. Testing only the centre of a building misses unsupported outer piers. + Never skip unavailable foundation cells and then place the complete building + above them. Move the feature inward or author a connected plinth first. +- Check plant substrates and lantern attachment faces against the final combined + blueprint. Hanging lamps need a real beam/chain; slabs and stairs need their + actual state and collision height considered. +- Verify each spawn has its intended floor and four clear blocks above it. Check + routes through the final furnishings with their actual standing heights; + drawing a ring in the floor does not prove that the ring remains traversable. +- Measure all intended gaps and landing areas. A block count is geometric + evidence, not proof that sprinting, knockback or a configured double jump can + cross the gap. Confirm those interactions on the running server. +- For void arenas, inspect every exposed standing surface below the main deck. + Place the elimination threshold to prevent unintended recovery ledges or + sheltered camping pockets, while leaving the visible underside intact. + +The supplied regression checks cover reproducibility, negative coordinates, +sampling bounds, solid column intervals and representative discrete connectivity: + +```bash +python3 -m unittest discover -s scripts -p 'test_voxel_island.py' +``` + +Use the normal checked MCP editing path to modify an existing world. Loading an +offline blueprint as the initial generator of a new world is a separate operation; +it does not acquire block-by-block editor undo simply because this helper produced +the geometry. diff --git a/docs/IMPLEMENTATION.md b/docs/IMPLEMENTATION.md index 74f53ac..6835afb 100644 --- a/docs/IMPLEMENTATION.md +++ b/docs/IMPLEMENTATION.md @@ -31,7 +31,7 @@ The following scenarios passed on real Paper in a separately created test world: - A hollow cube through real MCP: 26 blocks; `.schem` export, the asset library, undo, import at the same anchor, and another undo restoring 27 blocks of air. - A missing camera returns an error without substituting an image. - After installing the mod in Prism, a 575-block tower was built and four real 1280×720 captures were obtained. The last completed the full Camera → Paper → Bridge → MCP ImageContent path. The first request failed when the viewpoint changed; a retry with the client stationary succeeded. [Test report](ONE_CLIENT_TEST.md). -- A reference-based Gothic hall was built, photographed, and polished through checked recipes: 30,125 final blocks and 53 lanterns. Final verification covered 30,843 positions, including 718 removals; grass growth on eight newly planted soil blocks was explicitly recorded. Day, dusk, and interior captures were inspected. At that stage, the narrower Paper policy rejected nonpersistent leaves and waterlogged lantern states; the runtime registry expansion removes that material restriction. [Build report](builds/GOTHIC_HALL.md). +- A reference-based hall was built, photographed, and polished through checked recipes: 30,125 final blocks and 53 lanterns. Final verification covered 30,843 positions, including 718 removals; grass growth on eight newly planted soil blocks was explicitly recorded. Day, dusk, and interior captures were inspected. At that stage, the narrower Paper policy rejected nonpersistent leaves and waterlogged lantern states; the runtime registry expansion removes that material restriction. [Example capture](images/build-demo.png). Real `codex-acp` completed `initialize` in a separate profile without login: ACP v1 and session loading support are confirmed. That initial check did not invoke a model or consume model tokens. Dedicated ChatGPT sign-in and real authenticated ACP calls to `project_context` and `region_inspect` have since passed against Paper. A subsequent model turn placed and verified one oak-plank block at (24, -60, -45), then used checked undo and verified air. Place operation: `ae0166c8-6d03-45ab-a80a-48b644819a2b`; undo: `751017ba-8e07-433b-b38d-71dcc8cf2330`. A local proxy restricted writes to the fixture and its recorded undo. @@ -59,7 +59,7 @@ Sponge v2 `.schem` is implemented without a WorldEdit dependency: up to 4096 blo 1. Extend the verified ACP one-block roundtrip to larger in-game `/ai` builds, real cancellation, and reconnect recovery. 2. Extend the verified single-client Prism scenario: test disconnection/freezing during capture and convenient switching between building and camera use. -3. Extend the demonstrated procedural Gothic-hall workflow to larger authenticated ACP builds, then reassess release readiness, limits, context deltas, and geometry extensions. +3. Extend the demonstrated procedural building workflow to larger authenticated ACP builds, then reassess release readiness, limits, context deltas, and geometry extensions. ## Terrain toolkit diff --git a/docs/SHACRAFT-ARRIVAL-GARDEN.md b/docs/SHACRAFT-ARRIVAL-GARDEN.md deleted file mode 100644 index 4ae73a3..0000000 --- a/docs/SHACRAFT-ARRIVAL-GARDEN.md +++ /dev/null @@ -1,47 +0,0 @@ -# Shacraft arrival square — garden finish - -Stage 03 finishes **zone 01** on the existing foundations. The clock station and other districts keep their previous construction state. - -The main reference is sheet 03, *Clock station and arrival square*, supported by sheets 01, 02, 10 and 11. Its defining features are a pale open apron, the green Shacraft emblem, concentric hexagonal paving bands, evergreen planting islands, pink and white flowers, benches and warm garden lighting. The concept images guide the composition; they are not measured Minecraft drawings. - -![Finished arrival garden in Minecraft](references/shacraft-arrival-garden-overview.png) - -## Built composition - -- The former square setting-out grid is replaced with a cream paved field and three flush hexagonal contour bands. -- The actual green artwork from the preserved `logo-180.png` is sampled into a 25 × 35 block footprint. The open hexagonal shield and S replace the earlier placeholder medallion. -- Six low stone-edged planting islands contain six custom conifers, six smaller topiary trees, low evergreen clusters and 258 flowers. The southern beds extend to the balustrade, eliminating isolated strips of paving behind them. -- Six spruce benches face the square. Each has a three-block level approach through the planter edge. The benches are decorative; no sitting interaction is added. -- Sixteen supported lantern fixtures use slender posts and green copper hoods. Eight former light piers are replaced, including a balanced pair beside the arrival stair. -- Obsolete zone 01 survey stakes, its block number and its floating survey label are retired. Other district markings remain. - -Paving remains at block Y95, with the main walking plane at Y96. The central sightline toward the clock station and all existing road connections are preserved. No terrain platform, station construction or new fountain is introduced by this stage. - -![Planting, a bench and copper lantern fixtures](references/shacraft-arrival-garden-detail.png) - -![Evening lighting in the actual Minecraft world](references/shacraft-arrival-garden-evening.png) - -## Checked construction - -Before the work, the full server surface matched Stage 02 at all 589,824 columns. A fresh 359,964-voxel plaza survey included soil, foundations, fixtures and canopy air; 294,516 overlapping voxel states also matched the previous checkpoint. Unexpected manual edits stop the compiler, and the normal checked editor protects against changes between preparation and application. - -The final candidate consists of 4,734 changed blocks. Its review checks persistent leaf distances, valid soil under flowers, grounded trunks, lantern support, bench approaches and 1.8 blocks of walking headroom. Grass beneath opaque tree trunks is replaced with dirt explicitly so random ticks do not change the recorded foundation state. The road audit starts with the previously verified usable surface, excludes actual planting and furniture, and checks the original eight route widths and their connectivity. - -Final verification uses fresh observed blocks and a new full server map. These sequential surveys are not atomic world snapshots; construction remains idle during them. Perspective screenshots are real Fabric client captures, with heuristic render readiness. Geometry checks do not represent a human playtest or a full simulation of moving-player collisions. - -The [final live report](references/shacraft-arrival-garden-verification.json) passed after **4,811 checked writes in 12 batches**, including a 77-block finishing pass that gives the conifers continuous green tips. All **4,734 final changed states** matched the latest observation. It checked 537 persistent leaf states, 258 supported flowers, 73 connected trunk blocks in 12 trees, 16 lantern supports and **4,955 clear walking samples**. All eight inherited road widths and 18 bench-front positions passed, with no isolated usable paving. The full **589,824-column map** matched the expected world, and all 41,467 visible water columns remained unchanged. - -![Actual arrival garden surface map](references/shacraft-arrival-garden-map.png) - -## Reusable tools - -- `scripts/plaza-assets.py`: deterministic conifers, oriented benches and supported copper-hood lantern fixtures. Functions return local voxel states and never write to a world. -- `scripts/build-shacraft-plaza.py`: composes the approved garden geometry, real brand mask and static assets into a checked recipe. It preserves foundation elevations and existing road treads. -- `scripts/verify-plaza.py`: independent candidate/live validation of planting, fixtures, walking clearance and the inherited road network. -- `scripts/layout.py`: bounded checked placement, receipts and conflict-aware reverse undo. - -The Paper policy now supports 99 materials. The added flowers are single-block decorative species; new copper forms are waxed. Existing restrictions on fluids, waterlogged states and nonpersistent leaves remain in force. The strict schematic codec retains its earlier material subset, so complete world checkpoints and voxel recipes are the preservation format for these gardens. - -The final recipe, original observations and applied ledger are retained locally under `.runtime/plaza-stage03/`. The reference study and its earlier candidate are preserved separately from the finished state. Stage 02 remains available in the local Git archive and its full world backup. - -The restorable final world checkpoint is `.runtime/projects/shacraft-arrival-garden-20260913/`, captured after an explicit save flush with automatic saving temporarily disabled and then re-enabled. It includes the full world container, matching plugin state, both stage ledgers and the earlier foundation receipts. Undo the apex polish ledger before the main garden ledger. The removed zone 01 text display is an independently recorded operator action rather than a block-journal change. diff --git a/docs/SHACRAFT-BALUSTRADE.md b/docs/SHACRAFT-BALUSTRADE.md deleted file mode 100644 index 101884a..0000000 --- a/docs/SHACRAFT-BALUSTRADE.md +++ /dev/null @@ -1,46 +0,0 @@ -# Shacraft arrival square — perimeter balustrade - -Stage 04 completes the edge of the finished zone 01 garden with a continuous pale handrail, regularly spaced sandstone piers and connected stone balusters. It follows the existing hexagonal terrace while keeping the road approaches open. The garden, logo apron, lighting and clock-station construction retain their previous state. - -![Completed arrival-square balustrade in Minecraft](references/shacraft-balustrade-overview.png) - -## Boundary and heights - -The balustrade contains **302 perimeter columns** split into **five uninterrupted fence runs**, separated by the existing approach openings. The runs contain 46, 80, 74, 71 and 31 columns. Forty-six cut-sandstone piers mark endpoints, principal corners and intermediate bays. - -Paving remains at block Y95, with its walking plane at Y96. Balusters and piers occupy Y96. A bottom smooth-sandstone slab at Y97 forms a continuous handrail with its upper surface at **Y97.5**, 1.5 blocks above the plaza walking plane. Wall connections are explicit, including tall side connections beneath the handrail and reciprocal connections to six adjoining road-rail columns. - -The boundary forms a cardinal block chain. Elbows close the diagonal steps so adjacent railing pieces meet along block faces. Where planting occupies the inner choice, the connector moves to the outer side. Twenty-two small outer columns have stone-brick footings rising from observed full support to cut-sandstone coping at Y95. They support individual elbows without adding another terrace. - -Four existing southern planter-rim slab columns are incorporated into the new boundary. Planting contents, existing lamps, road treads and the clear approach widths are preserved. - -![Connected railing, pale coping and sandstone piers](references/shacraft-balustrade-detail.png) - -![Grounded outer elbows and the terrace edge](references/shacraft-balustrade-exterior.png) - -## Checked construction - -`scripts/build-shacraft-balustrade.py` reads captured blocks and compiles a plan without changing the world. It refuses unexpected decoration at fence or handrail positions, protects inherited road cells, and finds observed footing support. `scripts/layout.py` checks expected states during preparation, journals bounded applications and verifies their results. - -The editor made **692 checked writes in four batches**: 682 initial writes and ten finishing writes for the road-rail joints and buried soil. Twenty-one grass blocks beneath the new footings had already changed naturally to dirt; the remaining buried grass block was explicitly stabilized. The recorded final comparison covers **710 distinct expected states**, including those observed soil transitions. Their positions are preserved in `settled-soil.json` alongside the stage records. - -The [final observed-world report](references/shacraft-balustrade-verification.json) passed. It checks the complete **556,308-voxel captured volume** against the baseline plus final plan, including **555,598 voxels outside the final changes**, with no mismatches. All 302 handrail columns, 46 piers, 22 grounded footings and six reciprocal rail joins passed. It also confirms all 1,172 preserved garden fixture states, **4,815 walking/headroom samples**, the original eight route widths and 32 bench-access columns. - -The separate complete surface comparison matched all **589,824 map columns**, including 589,516 columns outside the edited area. All 41,467 previously visible water columns remained unchanged. The navigation graph reached all 57,352 surface samples across 14,338 usable columns. - -These sequential voxel and surface captures are not atomic snapshots; construction was idle during final measurements. The volume check covers the supplied observed bounds. Route checks verify geometry, support and headroom, without simulating a moving player's complete collision body. Perspective images are actual Fabric client captures with heuristic render readiness. - -![Actual server surface after the balustrade finish](references/shacraft-balustrade-map.png) - -## Tools and preservation - -- `scripts/build-shacraft-balustrade.py` compiles the continuous perimeter, grounded elbows and explicit connections. -- `scripts/verify-balustrade.py` independently checks the final rail, protected garden, approaches, navigation and complete observed volume. -- `scripts/verify-foundation-survey.py` checks the full before/after server surface against the final column states. -- `scripts/layout.py` provides checked placement, journal receipts and conflict-aware reverse undo. - -Stage inputs, captured observations, final recipes and application receipts remain under `.runtime/balustrade-stage04/`. The archive separates initial computed previews and candidate checks from final Minecraft images and fresh observed-world reports. - -The restorable checkpoint is `.runtime/projects/shacraft-plaza-balustrade-20260913/`. It contains the complete world container, matching plugin state and stage receipts. The world was explicitly flushed with saving temporarily disabled for the copy, and automatic saving was re-enabled afterward. All 137 recorded world-file hashes passed verification. - -Previous references and stages remain in `/home/emil/Desktop/Shacraft-Lobby-Archive`. Raw observations, complete world saves, private plugin configuration, authentication data and journals stay outside its Git history. Undo the finishing ledger before the initial balustrade ledger, then process older stages only if requested. Natural soil transitions are recorded explicitly and must be considered during restoration; manual-edit conflicts stop checked undo. diff --git a/docs/SHACRAFT-FOUNDATIONS.md b/docs/SHACRAFT-FOUNDATIONS.md deleted file mode 100644 index 5c46d8e..0000000 --- a/docs/SHACRAFT-FOUNDATIONS.md +++ /dev/null @@ -1,61 +0,0 @@ -# Shacraft — foundations for zones 01 and 02 - -This document preserves the Stage 02 foundation checkpoint. Zone 01 has since received its [Stage 03 arrival garden finish](SHACRAFT-ARRIVAL-GARDEN.md). - -This construction stage develops the arrival square and clock station in the approved natural v2 world. It also builds their local approaches. The other district reservations and future bridge spans remain at the survey stage. - -![Completed foundations and local approaches in Minecraft](references/shacraft-foundations-overview.png) - -## Levels and circulation - -The arrival square and oval station forecourt use paving blocks at **Y95**, with the walking surface at Y96. The station floor uses blocks at **Y98**, with the walking surface at Y99. The station follows its actual hall, tower and pavilion footprint. Its western retaining wall has shallow blind arches and stone pilasters; solid backing remains behind the recesses. - -A nine-block clear avenue links the square to the forecourt. A **21-block clear stair** rises to the station in half-block tread increments, with full landings. The local southern approach is nine blocks wide, the district connections seven, and the lake approach five. Curved roads use full transverse rows of correctly facing stairs, with separate edge coping. Lamps and balustrades sit outside the reserved clear lanes. - -Finished road endpoints, expressed as X/Z and the supporting block's Y: - -- Southern approach: `(1, 110)`, Y79, joining native ground at the same level. -- Portal approach: `(-72, -46)`, Y89, joining native ground at the same level. -- Lake approach: `(-90, 32)`, an east-facing bottom stair at Y84, stepping down to native full blocks at Y83. -- Northwest station approach: `(-80, -77)`, Y85, joining native ground at the same level. -- Eastern bridge approach: `(82, 8)`, Y87. -- Northeast bridge approach: `(82, -78)`, Y86. - -The two bridge approaches end at temporary balustrades beyond the completed walking deck. Those barriers must be removed when the corresponding bridge spans are constructed. Beyond the other local endpoints, the remaining route markings reserve later work; they are not finished roads. - -![Actual server surface with the completed roads and foundations](references/shacraft-foundations-map.png) - -## Materials and construction - -The plaza has pale sandstone paving, restrained stone courses and a flush green Shacraft medallion. The station has a stone structural floor with setting-out bands for the future hall, clock tower and end pavilions. Exposed bases combine deepslate, stone brick and andesite coping. Twenty-seven lantern piers light the edges. - -Every deck has structural courses and solid support down to the observed original terrain. The generator does not create a new rectangular terrain platform outside the building footprint. Small local cuts clear the paving and headroom; the original mountains and watercourses remain outside this construction scope. - -![Western station base after arcade refinement](references/shacraft-foundations-west.png) - -![Broad station entrance stair, captured in Minecraft](references/shacraft-foundations-stairs.png) - -## Checked workflow - -The design was compiled against a **1,981,407-voxel live before-survey**, including underground support and headroom. Unexpected differences from the original terrain plus known survey markers stop compilation. The placement driver also checks the caller's expected states atomically during preparation, journals bounded operations and verifies each applied batch. - -The stage inputs and receipts are retained locally under `.runtime/foundations-stage02/`. A fresh post-build voxel survey checks standing heights and 1.8 blocks of vertical clearance on the actual full blocks and both stair treads. An independent half-block navigation graph checks route connectivity and full-width cross sections. These are geometric and block-state checks, not a simulated moving player or a manual playtest. - -The full server-derived before/after surface maps are compared across the 768 × 768 world. The verifier applies air edits before finding the new top block, so lowered surfaces are checked correctly. Surface maps and sequential voxel surveys are not atomic; construction is kept idle while final measurements are taken. Perspective images use the real Fabric client, whose render readiness remains heuristic. - -The [completed verification report](references/shacraft-foundations-verification.json) records **79,081 checked writes across 67 journalled batches**, including a 408-block refinement that moved the western pilasters clear of the arches and darkened their intact backing. All **78,673 final changed voxel states** matched the post-build survey; **15,708 standing-height and headroom samples** passed. All **589,824 surface columns** matched the expected result, including **572,923 columns outside the edited area**. All 41,467 previously visible water columns remained unchanged, and no observed water blocks were replaced. - -## Tools and preservation - -- `scripts/foundation-study/design.py` and `geometry.py` define the elevations, contours, stairs and road corridors. -- `scripts/build-shacraft-foundations.py` compiles observed terrain, approved geometry and materials into a checked block plan; it does not write to the world. -- `scripts/layout.py` applies bounded batches and supports checked reverse undo through the stage ledger. -- `scripts/foundation-survey.py` records actual voxel states and checks floor support and headroom. -- `scripts/foundation-study/verify_geometry.py` independently checks planned navigation. -- `scripts/verify-foundation-survey.py` compares actual surface maps with the complete edited column state, including excavation. - -Original references and the marked-site checkpoint are preserved in the separate local Git repository `/home/emil/Desktop/Shacraft-Lobby-Archive`. Each completed stage has its own directory, source provenance and SHA-256 manifest. Restorable world backups remain outside Git, together with their matching private plugin state and operation journals. - -The completed world checkpoint is `.runtime/projects/shacraft-foundations-01-02-20260913/`, captured with saving disabled after an explicit flush; normal saving was then re-enabled. It contains the full world container, including `world/dimensions/minecraft/shacraft_lobby_v2`, both relevant plugins and the construction ledgers. World-file checksums are recorded in `checkpoint.json`. - -An undo must run newer stage ledgers before older ones and stop on manual-edit conflicts. Floating labels are separately managed entities; their operator commands are recorded with the stage artifacts rather than in the block journal. diff --git a/docs/SHACRAFT-LOBBY-LAYOUT.md b/docs/SHACRAFT-LOBBY-LAYOUT.md deleted file mode 100644 index 6531e96..0000000 --- a/docs/SHACRAFT-LOBBY-LAYOUT.md +++ /dev/null @@ -1,68 +0,0 @@ -# Shacraft lobby site marking - -This document preserves the **Stage 01 survey checkpoint**. The current world has progressed to [Stage 02: foundations and local roads for zones 01 and 02](SHACRAFT-FOUNDATIONS.md). - -The existing `shacraft_lobby_v2` alpine terrain is now marked for the future lobby. This is a site survey: building contours, entrances, courtyards, paths, bridge deck reservations, height stakes and wayfinding. Buildings, playable minigames and finished paths have not been constructed. - -![Live world map and layout legend](references/shacraft-layout-final.png) - -## Visit - -Connect to the private local server at `127.0.0.1:25575` using the **26.2 MCP Building** Prism profile, then use `/lobby`. Spectator mode is useful for inspecting the complete arrangement. The configured camera client can reconnect autonomously after a local server restart. - -The planning envelope is 768 × 768 blocks. Natural mountains frame the developed valley; the terrain was not flattened to fit district rectangles. Surface contours replace a single observed top block. Raised lines reserve future bridge, pier and airship elevations while leaving the river below intact. - -## Legend and coordinates - -Coordinates below identify the districts in X/Z. The same two-digit numbers are drawn with blocks; lit colored posts carry nearby floating names. - -- **01 · lime · arrival square · (0, 8).** Open hexagonal plaza and Shacraft medallion, with a direct view toward the clock station. -- **02 · yellow · clock station · (-18, -119).** Hall, projecting tower, end pavilions and a smaller oval forecourt. -- **03 · purple · portal concourse · (-201, -156).** Chamfered hall, six separate portal bays and a garden approach. -- **04 · cyan · airship harbor · (179, -137).** Terminal, three west-facing piers at Y79 and a flagship reservation at Y92. -- **05 · red · sky gardens · (207, 88).** Palm glasshouse, winter garden and observatory. White paths follow their surrounding courts. -- **06 · orange · market quarter · (-195, 225).** Six small building plots, an open market square and individual entrance connections. These plots use the calmer southern shoulder. -- **07 · white · arrival avenue · (8, 284).** Long southern approach and a final viaduct at Y65. -- **08 · blue · lake waterworks · (-135, 43).** Pumping house, viewing terrace, Y51 boardwalk and two marked future stair approaches. -- **09 · pink · scenic overlooks.** Five small viewpoints with optional trails, preserving the mountain slopes. - -White paired lines reserve path edges. Main avenues are designed for nine-block clear corridors, the district loop generally seven, and smaller access paths three to five. Colored building boundaries are reservations, not final material choices. White cross-ties and tall end stakes show bridge deck widths and elevations. Labels identify the larger level changes where stairs or staged access will be needed. The current block contours alone do not certify walking access. - -## What was placed and checked - -- 41 geometric reservations and 48 route segments, including six bridges, three airship piers, six portal bays and six market buildings. -- 16,967 distinct marker positions, applied with 17,040 checked writes across 36 journalled batches. The small second pass adds missing access links and extends bridge height stakes. -- Eight district labels and eleven access/elevation labels, verified again after the server restarted. -- Every batch was checked before planning, atomically against its caller snapshot during preparation, and after application. Stable operation keys and persisted receipts support resumption and checked undo. -- Full before/after world surface comparison and targeted reads beneath obscuring markers are recorded in the [verification report](references/shacraft-layout-verification.json). The map is read from actual Paper blocks, not reconstructed from the noise generator. - -![In-game view of the arrival reservation and station axis](references/shacraft-layout-arrival.png) - -## Autonomous map exporter - -The optional terrain plugin implements this **server-console** command: - -```text -lobby map survey-name -lobby status -``` - -It writes `plugins/ShacraftTerrain/maps/survey-name.png` and `.json`. The PNG has one pixel per world column, north up, with material colors and height shading. The JSON includes actual surface heights, material palette and coordinates. Existing names are never overwritten. The exporter loads only existing chunks, reads one chunk per tick, and renders/writes the detached result asynchronously. It requires no Minecraft client or account. - -The exporter reads chunks sequentially, so a map is not an atomic world snapshot. Construction was idle during the verification captures. A top surface also hides the water beneath a bridge; separate bounded RPC reads verified that water. Text display entities appear in game but are not included in this block-surface map. - -This is an orthographic world map. Perspective screenshots still use the Fabric spectator client and its heuristic render readiness. - -## Reproduce and revise - -The reusable [spatial plan](../examples/layout/shacraft-lobby-layout.json) and [access refinement](../examples/layout/shacraft-access.json) describe reservations, routes, heights and intent. Their geometry was studied against the natural terrain before live surface data was used for placement. - -- `scripts/mark-shacraft-layout.py` compiles the primary survey from a current surface map and authorized world scope. -- `scripts/compile-layout-access.py` compiles the access refinements against the original survey and known primary marker states. -- `scripts/layout.py report` describes bounded batches without touching a server. `prepare` checks the input without allocating expiring plans. `apply --execute` writes and verifies; `undo --execute` uses reverse journalled undo. -- `scripts/render-layout-map.py` renders the labelled atlas from live surface data. Supplying `--blocks` explicitly produces a labelled **design preview** instead. -- `scripts/verify-layout-survey.py` compares every visible column and checks hidden water through bounded live reads. - -The local applied ledgers are `.runtime/layout-study/placement-final.json` and `.runtime/layout-study/access-ledger.json`, with adjacent immutable input snapshots. Undo the access ledger before the primary ledger. Manual changes can block undo and must be preserved and reviewed. The 19 text displays are a separate operator-managed layer with tag `shacraft_layout_v1`; they are not part of the block journal. An explicit undo of the whole survey also requires removing that tagged display layer in the same world. - -A complete stopped-world backup, including plugin state and layout ledgers, is stored locally in `.runtime/projects/shacraft-marked-lobby-20260913/`. The earlier unmarked natural world remains backed up separately. Private configuration and authentication material are excluded from the public examples and report. diff --git a/docs/SHACRAFT-STATION.md b/docs/SHACRAFT-STATION.md deleted file mode 100644 index 61b7f7d..0000000 --- a/docs/SHACRAFT-STATION.md +++ /dev/null @@ -1,65 +0,0 @@ -# Shacraft clock station — vestibule and SMASH gallery - -The clock station is built, furnished and verified in the local lobby world. Its two public floors contain the arrival vestibule and SMASH selection gallery, with enclosed personal lift cabins, warm architectural lighting and a closed island display. The complete world and matching plugin state are backed up. - -![Completed clock station in Minecraft](references/station-stage07/station-overview.png) - -The station occupies the existing irregular zone 02 foundation. Its 5,764 surveyed columns fit inside X **-74..40**, Z **-145..-83**. Pale stone arcades, green copper roofs, spruce ceilings, brass accents and warm lanterns continue the original clock-station reference. The roof silhouette rises from eaves at Y126 to a main ridge at Y140 and a clock-tower peak at Y158. Roof overhangs extend the complete building envelope to X **-76..42**, Z **-147..-81**. - -The coordinate drawings and generated perspectives remain in `docs/references/zone02-interior-v1/`. Those images establish the visual direction; the saved layout and actual foundation geometry govern placement. They are not photographs of the completed building. - -![South entrance and completed Shacraft nameplate](references/station-stage07/station-front.png) - -## Two complete public floors - -The vestibule floor occupies block Y98, with players standing at **Y99**. The SMASH floor occupies block Y112, with players standing at **Y113**. The intermediate deck at Y111..112 is continuous. Decorative coffer ceilings close each room; the upper structural ceiling at Y124..125 separates both public floors from the inaccessible roof and clock tower. - -The south entrance follows the existing X=-6 approach axis. Its clear opening spans X=-9..-3, retaining seven blocks of width. Matching square columns align vertically on both floors. Shallow cream corbels and spruce ceiling panels remain above the circulation space, while inset green and brass-colored floor bands guide the central approach. Side galleries contain spruce benches, small planted stone boxes and restrained floor mosaics. There are no additional unfinished public rooms, stairs to the attic or open shafts. - -The interior generator owns only the two-block setback inside the actual foundation, at block Y98..123. It leaves the exterior wall and window layers to the exterior compiler. Its 5,064 interior columns contain fifteen benches, ten small topiary planters and 48 hanging lanterns, with three additional miniature trees in the diorama. The complete recipe combines the exterior and interior before selecting only changed states against the captured baseline. - -Real Minecraft photographs prompted a lighting refinement. Sparse brown-glass floor tiles reveal concealed glowstone below the paving; matching small lenses recess into the plain spruce ceiling panels. The regular nine-block layout adjusts locally around beams, mosaics, furniture, controls and landing points. All lens and emitter replacements retain full-block collision and solid floor/ceiling separation. The enclosed diorama receives its own concealed lighting. - -![Furnished and illuminated ground-floor vestibule](references/station-stage07/station-vestibule.png) - -## Enclosed lift cabins - -The lift housing is **X=-10..-2, Z=-124..-116** on both floors. Each stationary cabin has a five-by-five clear interior at X=-8..-4, Z=-122..-118 and a five-block-wide south opening. Both cabin floors remain solid. The space above each cabin is closed, so the lift provides no passage to a shaft or unfinished upper rooms. - -Gold selector blocks sit on the north interior wall at **(-6, 101, -123)** and **(-6, 115, -123)**. Right-clicking a selector from inside the matching cabin changes floors for that player alone, arriving at **(-5.5, 113, -119.5)** or **(-5.5, 99, -119.5)** facing south. The listener checks the destination support and two air blocks before teleporting. The independent geometry audit checks four blocks of headroom. - -`/station` enters the ground-floor vestibule. `/station 1` and `/station 2` select the two completed cabin stops. The commands were exercised on the local server and both resulting player positions were read back; a third stop was rejected. The physical mouse-click path is covered by listener and cabin-selection tests but was not exercised through an automated client click. - -Zone 01's previous invisible boundary remains in place. Use the station entry command while that earlier garden enclosure is retained; opening the physical route is a separate checked edit. - -Twenty-four persistent text displays provide the exterior name, lift instructions, floor names, numbered arena placeholders and rules panels. A repeated installation replaced the previous set without increasing the count. These separately managed entities are recorded outside the block recipe. Arena placeholders do not perform world transfers or matchmaking. - -## SMASH gallery - -Six seven-by-five selection alcoves line the north gallery at Z=-140..-136, with centres at X **-42, -32, -22, -12, -2 and 8**. A seven-block-deep clear aisle at Z=-135..-129 connects them. Each alcove has a small decorative island relief, a framed display and a reachable selection surface. Slots **01–06 remain unassigned**; they do not advertise invented arena worlds, live queues or active minigames. - -The western display occupies **X=-42..-24, Z=-124..-110**, a 19-by-15 rectangle. Miniature planted islands stand above a solid illuminated base, enclosed by continuous pale stone and glass. Twelve full brown-glass floor lenses admit concealed light inside the display while maintaining its intact base. It is a closed display on an intact floor, with no playable void below it. The nearby west gallery explains damage, knockback and double jumping. The eastern wing provides waiting benches and an orange terracotta floor accent. No SMASH arenas are included in this construction stage. - -![Completed SMASH gallery and enclosed island display](references/station-stage07/station-smash.png) - -## Verification and preservation - -The completed stage required **84,446 checked writes across 98 batches** and has **83,924 final distinct changed states**. The final observed world matched every planned state. All **572,326 observed voxels outside the edits** also matched the baseline, completing the check of the full 656,250-voxel survey. - -Independent observed-block checks pass for **4,796 vestibule walking columns** and **4,176 SMASH walking columns**, each with four blocks of clear headroom, and all **107 named furnishing and lift approaches**. Those counts include the exterior doorway columns; the interior generator alone reports 4,775 vestibule columns. Both cabins, both lift doorways, the main approach and the complete selection aisle were checked voxel by voxel. The fixture audit passed for 72 planned lantern supports, 78 persistent leaf states and thirteen rooted station/display trees. - -The enclosure audit uses a temporary virtual cap at the intentional south entrance, then floods from both public floors and lift cabins. The cap is an analysis boundary, not a block placed across the doorway. Full glass and known full cubes stop the flood; partial and unknown shapes are treated as passable. The observed building has no connection from either furnished floor to the attic or clock tower. This static geometry audit does not prove lift behavior, spectator restrictions or administrative access control. - -The complete visible-surface comparison passed for all **589,824 map columns**: 6,496 changed station columns and 583,328 unchanged outside columns. All **41,467 previously visible water columns** were preserved. The map was captured before the last lighting and nameplate polish. A separate final voxel comparison confirmed that all 602 subsequent polish differences lie below the exported roof surfaces, and that the actual final top blocks and surveyed blocks above them match the map for every station column. - -The final plugin build passed 97 Maven tests; seven station-verifier tests and ten foundation tests also passed. World and surface captures are sequential rather than atomic. The four final photographs are actual Minecraft client captures with heuristic render readiness; their metadata does not claim a verified server revision. Observed block checks and runtime interaction receipts provide the separate measurement evidence. - -`scripts/station-exterior.py` and `scripts/station-interior.py` generate the geometry. `scripts/build-shacraft-station.py` combines their owned regions, checks the observed baseline against the prior foundation checkpoint and compiles expected-state edits. `scripts/light-shacraft-station.py` generates the concealed lighting grid against the observed built station. `scripts/verify-station.py` independently checks the resulting circulation, enclosure and captured volume. Stage records are kept under `.runtime/station-stage07/`. - -One interrupted batch verification exposed Minecraft's explicit `deepslate[axis=y]` state. The original receipt and manifest were retained. A fresh partial survey confirmed the applied prefix, and a separate remaining recipe continued from that observed state. This was a state-serialization correction at three miniature island tips; the retained recovery report records the exact positions and hashes. - -The authoritative final recipe and metadata are `.runtime/station-stage07/final-v2/station.json` and `station.metadata.json`. The final observation is `after-complete.json.gz`, with `actual-complete-qa.json`, `complete-surface-verification.json`, `complete-surface-invariance.json` and `runtime-verification.json` recording the checks. The owner was returned to the vestibule at **(-5.5, 99, -92.5)** with spectator mode preserved. - -The restorable backup is `.runtime/projects/shacraft-station-complete-20260913/`: **650 world and plugin files, 237,341,328 bytes**, with every recorded SHA-256 hash verified. Saving was flushed and temporarily disabled during copying, then automatic saving was re-enabled at 15:42:17 local time. `backup-receipt.json` records its location and verification. - -The local Git archive preserves the final documents, generator and verifier snapshots, compact reports, reference plans, Minecraft photographs and surface maps. Complete world saves, raw voxel observations, private configuration, authentication, plugin binaries and placement journals remain in the separate local runtime backup. Earlier reference and construction checkpoints remain unchanged. diff --git a/docs/SHACRAFT-ZONE01-COMPLETE.md b/docs/SHACRAFT-ZONE01-COMPLETE.md deleted file mode 100644 index 6690ccb..0000000 --- a/docs/SHACRAFT-ZONE01-COMPLETE.md +++ /dev/null @@ -1,53 +0,0 @@ -# Shacraft zone 01 — finishing details and invisible boundary - -The arrival square is finished and its five approaches are closed with an invisible collision boundary. Players can explore the completed garden while the station and other districts remain outside the current playable area. The existing emblem, six planting islands, benches, lights and broad central sightline remain the focus. - -![Completed arrival square in Minecraft](references/shacraft-zone01-overview.png) - -## Visible finish - -Two low pale-stone urns with white flowers stand at X/Z **(-10, 35)** and **(10, 35)**. A low green nameboard at **(-8, 43)** carries `SHACRAFT` and `Площадь прибытия`. Its text is a separately managed display entity attached visually to the board, with no directional arrows toward closed roads. - -Five flush threshold bands use stone with small green-copper insets at the north, south, east, west and northwest approaches. Paving remains at block Y95, with the walking plane at Y96. Three full sandstone joint piers complete the road-to-plaza boundary, bringing the perimeter pier count to 49. - -The visible recipe contains 106 changed block states, followed by a sixteen-write urn refinement. Together with the enclosure, the editor made **11,518 checked writes in twenty batches**. All **11,502 final distinct changed states** matched the final observed world. - -![Welcome nameboard and planted stone urns](references/shacraft-zone01-welcome.png) - -## Invisible enclosure - -The boundary uses **11,396 barrier blocks**, recorded in a separate placement ledger so the enclosure can later be removed while keeping the garden finish. It includes side walls and a roof at block **Y116**, above the existing trees. The full-block floor at Y95 contributes to the enclosure. - -The compiler bends the boundary inward around existing partial-block stairs and rails instead of replacing those details. The intended interior excludes 87 boundary-adjacent voxels for these local folds. The independently derived collision membrane contains **17,276 full-cube positions**: 11,396 barriers and 5,880 existing or finished structural blocks. - -The [final observed containment report](references/shacraft-zone01-containment-verification.json) passed. The intended interior volume has one connected component containing 117,673 voxels. No interior voxel was reached from an exterior flood, and all **5,748 swept player-box boundary probes** were blocked. The probes use a 0.6 × 1.8 block player body; the complete static containment criterion is the full independently derived membrane. Unknown and partial blocks are conservatively treated as empty during the exterior flood. - -This guarantee concerns normal continuous collision-based movement. Spectator mode, block removal, operator commands, plugin teleports and arbitrary discontinuous teleport or pearl behavior are outside its scope. The boundary is not an administrative access-control system. - -![Arrival from the configured point inside the enclosure](references/shacraft-zone01-arrival.png) - -## Arrival, respawn and maps - -The configured `/lobby` destination is **(0.5, 96, 43.5)**, inside the southern welcome area. A scoped death-respawn handler returns players who died in the lobby world to that configured point, preventing Minecraft's highest-surface search from placing them on the invisible roof. It leaves other worlds and non-death transitions alone. The lobby spawn radius is zero. The scoped event behavior was unit tested; no player was killed for verification. - -The owner was returned to creative mode at the verified arrival point after inspection. The nameboard entity, spawn settings and owner return are recorded separately from the block journals in `operator-actions.json`. - -The server map exporter explicitly ignores `minecraft:barrier`, alongside air, when finding each visible surface. Exported metadata declares this policy, so maps show the garden beneath its invisible roof. Collision verification reads actual blocks separately and includes barriers. - -![Actual visible-surface map, with invisible barriers excluded by policy](references/shacraft-zone01-map.png) - -## Final verification and preservation - -The complete **622,098-voxel captured volume** matched the baseline plus final plan, including **610,596 voxels outside the edits**. The finishing audit passed for 537 persistent leaves, 260 supported flowers, sixteen lanterns, twelve rooted trees, **4,794 walking/headroom samples** and 32 bench-access columns. - -The complete visible-surface comparison matched all **589,824 map columns**. It covered 6,164 affected columns, with 87 visible surface changes; all 583,660 columns outside the affected area matched their baseline. All 41,467 previously visible water columns remained unchanged. The distinction between actual collision blocks and the declared visible-surface policy is preserved in the separate reports. - -These sequential captures are not atomic snapshots. The full-volume statement applies to the supplied observed bounds, and static geometric checks are not a human playtest. Perspective images are actual Minecraft client captures with heuristic render readiness. - -`scripts/finish-shacraft-zone01.py` compiles the visible finish, enclosure and boundary metadata. `scripts/verify-zone-containment.py` independently derives the collision membrane, checks containment and protects existing decoration. Stage records remain under `.runtime/zone01-stage05/`. - -The barrier ledger is separate from the finishing and urn-polish ledgers. Remove barriers through their checked ledger when reopening the other districts; the visible garden finish can remain. Undo the urn polish before undoing the earlier visible finish. The nameboard text and scoped spawn settings are separately recorded operator/configuration actions. - -The restorable backup is `.runtime/projects/shacraft-zone01-complete-20260913/`. The complete world container and matching plugin/receipt state were copied with saving temporarily disabled after an explicit flush, then automatic saving was re-enabled. All 137 recorded world-file hashes passed verification. - -The local Git archive preserves compact design and final reports, source snapshots, maps, photographs and the world-backup location. Raw voxel observations, complete world saves, private configuration, authentication, plugin binaries and journals remain outside Git. All previous references and construction checkpoints remain unchanged. diff --git a/docs/TERRAFORMING.md b/docs/TERRAFORMING.md index 3687ee5..a5613e9 100644 --- a/docs/TERRAFORMING.md +++ b/docs/TERRAFORMING.md @@ -1,6 +1,6 @@ # Terraforming tools -The terrain toolkit turns one small JSON recipe into a repeatable height field, a raster preview, and bounded edit plans. It prepares the terrain foundation for the Shacraft lobby: mountain ridges, cliff-backed building platforms, a lake bed, garden terraces and ravines. Buildings and fluids are separate work. +The terrain toolkit turns one small JSON recipe into a repeatable height field, a raster preview, and bounded edit plans. It supports small hills, graded routes, ridges, dry channels and local terrain adjustments. Buildings and fluids are separate work. ## Design and boundaries @@ -47,16 +47,14 @@ The terrain compiler and preview renderer run outside the server tick. Live read Build once with `./mvnw package` and `npm --prefix bridge test`. Offline preview needs the Java runtime installed by the project's bootstrap script; `MCB_JAVA_HOME` can override its path. -Render the proposed Shacraft terrain without a running server: +Render the included eight-by-eight hill recipe without a running server: ```bash -python3 scripts/terrain.py preview examples/terrain/shacraft-massif.json \ - --output docs/references/shacraft-terrain-heightmap.png +python3 scripts/terrain.py preview examples/terrain/small-hill.json \ + --output .runtime/small-hill-preview.png ``` -![Generated target heightmap](references/shacraft-terrain-heightmap.png) - -The example proposes a 768×768×192 envelope and therefore 27,648 possible tiles at 4096 blocks each. This is a layout experiment, not an instruction to apply all tiles to the current world. It must be assigned a new, suitable project area before use. The current Gothic hall site is not a valid destination for it. +The example covers X/Z 0..7 and Y80..87, with a rounded hill over seeded low-amplitude noise. The output is a target heightmap, not an observed world image. Inspect a suitable site and select the correct area before applying the recipe. Larger map-specific recipes and their reference images should be stored in the map project, outside this reusable toolkit. For an authorized small site, a local batch avoids sending every tile's polling traffic through model context. This Linux runner uses an exclusive manifest lock, saves requests before sending them, stops on conflicts and processes at most 64 explicitly selected tiles per invocation: @@ -80,7 +78,7 @@ Automated tests cover ordered features, negative elevations/coordinates, determi The opt-in `bridge/test/live-terrain.mjs` exercises the real MCP → Paper path in an isolated fixture: preview without writes, a 201-block hill, idempotent replay, checked undo, protected existing gold block, preserved cell, stale-plan conflict, empty mask, area rejection and unknown recipe ID. The local batch runner is also tested by apply → resume → undo with full air restoration. -A compact recipe and native PNG reduce context cost; they do not eliminate server work. Large sites still need many live reads and potentially large journals. No full 768×768 application, throughput target or crash/disk benchmark has been completed. This toolkit is ready for small terrain sections and progressive landscape work; a whole-map rollout should be benchmarked and staged separately. +A compact recipe and native PNG reduce context cost; they do not eliminate server work. Large sites still need many live reads and potentially large journals. No whole-map throughput target or crash/disk benchmark is claimed by these toolkit tests. This toolkit is ready for small terrain sections and progressive landscape work; a whole-map rollout should be benchmarked and staged separately. ## Relative brushes on existing terrain diff --git a/docs/builds/GOTHIC_HALL.md b/docs/builds/GOTHIC_HALL.md deleted file mode 100644 index d9fd96b..0000000 --- a/docs/builds/GOTHIC_HALL.md +++ /dev/null @@ -1,75 +0,0 @@ -# Gothic Hall - -## Polished version - -The current build contains **30,125 blocks**, including **53 lanterns** and **642 persistent leaf blocks**. The polishing pass changed 4,569 positions across 87 checked operations: 1,489 additions, 718 removals, and 2,362 replacements. - -The main roof now has six small dormers instead of ten large ones. A thin iron ridge and window mullions, quieter gray stone bands, and a gold block bell bring the architecture closer to the reference. Portal lights, courtyard lamps, three nave chandeliers, wing and tower lights, low planted beds, and six narrow trees complete the scene. The existing entrance, stairs, timber structure, and connecting passage remain usable. - -![Polished Gothic Hall in daylight](gothic-hall-polished-day.png) - -![Polished Gothic Hall at dusk](gothic-hall-polished-evening.png) - -![Lanterns and chandeliers inside the great hall](gothic-hall-polished-interior.png) - -These are unprocessed Minecraft framebuffer captures without shaders: [day metadata](gothic-hall-polished-day.capture.json), [evening metadata](gothic-hall-polished-evening.capture.json), and [interior metadata](gothic-hall-polished-interior.capture.json). The exterior views use the same position and angles as the original capture below, with the server view distance increased to 12 chunks. - -The new geometry lives in `polish_architecture.py` and `polish_scene.py` alongside the frozen original modules. After constructing the baseline and its 18-block finishing pass, run: - -```bash -python3 scripts/polish-gothic-hall.py plan -python3 scripts/polish-gothic-hall.py apply -python3 scripts/polish-gothic-hall.py verify -``` - -The manifest, ledger, and verification report are in `.runtime/gothic-hall/polish/`. Preparation checks the canonical baseline and the current blocks; a saved plan must still expect the same states. Every operation receives a durable idempotency key before application. The original manifests and ledgers are preserved. - -Final verification checked **30,843 positions**, including all 30,125 occupied positions and 718 removals to air, across 90 inspection tiles. It explicitly reported eight planted soil blocks observed as grass rather than dirt. This allowance is limited to the newly introduced planter soil during verification; preparation and application still require exact current states, and other mismatches stop the script without overwriting them. The final operation was `e7f0371d-e64c-48e0-86ae-13ac813dab98`. - -The world and journal were backed up locally before polishing, and the finished world was saved with `save-all flush`. The camera remains honest about its heuristic readiness: `serverRevisionVerified` is `false`; block verification is a separate server read. The old baseline verification commands below describe their historical stages and will report expected differences after this polishing pass. - -## Original baseline - -A reproducible build based on the [visual reference](../references/gothic-hall-v1.png): a large hall with galleries, a smaller two-story wing, a connecting passage, and a bell tower. Modules in `scripts/builds/gothic_hall/` generate the geometry; `scripts/build-gothic-hall.py` applies it through the Paper plugin's checked operations. - -This report records the original baseline before subsequent decorative polishing. It adapts the reference to the available palette: stone bricks, andesite, diorite, dark deepslate, wood, and tinted glass. No shaders were used. Detailed furnishing and landscaping were not part of this baseline; it includes a terrace, stairs, simple benches, and a raised platform. The bell is built from blocks, without a bell entity. - -The baseline was applied in the live world: **29,354 blocks in 87 batches**, with verification saved in `.runtime/gothic-hall/verification.json`. - -![Original Minecraft build without shaders](gothic-hall-built.png) - -A real Fabric camera capture from September 12, 2026, after the initial finishing pass. The PNG is unprocessed; [capture metadata](gothic-hall-built.capture.json). Viewpoint: `(0, -20, -63)`, yaw `25°`, pitch `16°`, FOV `85°`. The test server's view distance was five chunks for this original capture, so distant edges disappear into fog. The current local server has since been updated to 12 chunks for the polishing pass. Frame readiness is checked on the client; agreement between the blocks and the blueprint was verified separately by reading the world. - -The local coordinate origin is **origin = (-50, -60, -40)**. Add this origin to a local coordinate to obtain its world coordinate. All ranges below are inclusive; the main facades face north, toward `−Z`. - -- Terrace: local `X=1..63, Z=1..62, Y=0`, size **63 × 62**. World coordinates: `X=-49..13, Z=-39..22, Y=-60`. -- Large hall: main body `X=7..31, Z=13..56`, size **25 × 44**; with decoration it occupies `X=3..35, Z=8..57, Y=0..48`. Main floor at `Y=6`, galleries at `Y=16`, roof ridge at `Y=43`. Main entrance near world position **(-31, -53, -31)**. -- Side wing: main footprint `X=38..57, Z=16..41`, size **20 × 26**; including projections, `X=36..59, Z=13..43, Y=0..28`. Floors at `Y=0/8`, roof ridge at `Y=28`. Entrance near **(-3, -59, -26)**. -- Bell tower: including projections, `X=34..46, Z=42..56, Y=0..60`, size **13 × 15**, with 61 block levels. Its top is at world `Y=0`; entrance near **(-10, -58, 2)**. -- Connecting passage: `X=31..40, Z=33..43, Y=0..16`; the walkway in `Z=36..39` rises from the hall floor at `Y=6` to the wing floor at `Y=8`. - -Run commands from the repository root with Paper running, the project owner connected, and the building site's chunks loaded. The script reads project scope and the separate agent token from the private `.runtime/server/plugins/MinecraftBuilderMCP/config.yml`. - -```bash -python3 scripts/build-gothic-hall.py plan -python3 scripts/build-gothic-hall.py apply -python3 scripts/build-gothic-hall.py verify -``` - -`plan` saves `.runtime/gothic-hall/manifest.json` and checks the palette, project bounds, and exactness of geometry compression into recipes. It does not change the world. Before each new batch, `apply` checks that the target region is empty, then calls `build_prepare` and `build_apply`. Original block states in the saved plan must also be air. `verify` compares recorded blocks with the live world and saves a report; it does not check empty spaces outside the recorded mask. - -The resume ledger is `.runtime/gothic-hall/ledger.json`. It stores the blueprint hash, plans, idempotency keys, operation IDs, and their statuses. Rerunning `apply` resumes from this ledger using existing operations; completed batches are not rebuilt. The world ID and epoch are also checked. - -An occupied region, a manual change to an expected block, or an unfinished/conflicting operation stops application. `verify` reports mismatches while preserving manual edits. If application stops, inspect the reported operation and its server journal; do not delete the ledger or automatically create a new plan over the existing structure. Server plans live in `plugins/MinecraftBuilderMCP/journal/plans/` and are required by this script for application and verification. - -The initial finishing pass is stored separately from the original blueprint: `scripts/finish-gothic-hall.py` replaces 14 central pinnacle slabs with full stone blocks and four passage blocks with stairs. This removes gaps in the spires and makes the ascent between the buildings smooth. It uses a separate plan and `.runtime/gothic-hall/finish-ledger.json`. - -```bash -python3 scripts/finish-gothic-hall.py plan -python3 scripts/finish-gothic-hall.py apply -python3 scripts/finish-gothic-hall.py verify -``` - -After that finishing pass, use the last command: it verifies the original building with the 18 replacements applied. The original `build-gothic-hall.py verify` expects those blocks' previous states and will report mismatches. - -The finishing pass was applied as operation `682bb5bb-a1fe-4359-b687-06f7b1ce1005`. Reverification of all 29,354 blocks passed; the report is `.runtime/gothic-hall/finish-verification.json`. The world was saved with `save-all flush`. diff --git a/docs/builds/gothic-hall-built.capture.json b/docs/builds/gothic-hall-built.capture.json deleted file mode 100644 index 0692d6b..0000000 --- a/docs/builds/gothic-hall-built.capture.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "capturedAt": "2026-09-12T20:10:04.926155338Z", - "dimension": "minecraft:overworld", - "x": 0.0, - "y": -20.0, - "z": -63.0, - "eyeY": -18.380000114440918, - "yaw": 25.0, - "pitch": 16.0, - "fov": 85, - "readiness": "local_chunks_and_render_queue_stable", - "serverRevisionVerified": false, - "loadedChunkRadius": 1, - "stabilizationTicks": 21, - "stabilizationFrames": 3, - "afterOperationId": "682bb5bb-a1fe-4359-b687-06f7b1ce1005", - "status": "completed", - "mimeType": "image/png", - "width": 1280, - "height": 720, - "sourceWidth": 1280, - "sourceHeight": 720, - "captureId": "aa1dbef8-47f1-4985-b505-0bcc79e604b7", - "imageSha256": "b1dfd2fd671c20d7a0e4fd225843695a522f93a194cea18bfa313297e303774a", - "imageBytes": 670102, - "elapsedSeconds": 1.367, - "transport": "authenticated Paper HTTP camera_capture", - "testMode": "one owner/spectator client" -} diff --git a/docs/builds/gothic-hall-built.png b/docs/builds/gothic-hall-built.png deleted file mode 100644 index a5ddbb6..0000000 Binary files a/docs/builds/gothic-hall-built.png and /dev/null differ diff --git a/docs/builds/gothic-hall-polished-day.capture.json b/docs/builds/gothic-hall-polished-day.capture.json deleted file mode 100644 index d401d9e..0000000 --- a/docs/builds/gothic-hall-polished-day.capture.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "capturedAt": "2026-09-12T20:42:08.334749638Z", - "dimension": "minecraft:overworld", - "x": 0.0, - "y": -20.0, - "z": -63.0, - "eyeY": -18.380000114440918, - "yaw": 25.0, - "pitch": 16.0, - "fov": 85, - "readiness": "local_chunks_and_render_queue_stable", - "serverRevisionVerified": false, - "loadedChunkRadius": 1, - "stabilizationTicks": 24, - "stabilizationFrames": 3, - "afterOperationId": "e7f0371d-e64c-48e0-86ae-13ac813dab98", - "status": "completed", - "mimeType": "image/png", - "width": 1280, - "height": 720, - "sourceWidth": 1280, - "sourceHeight": 720, - "captureId": "c219512d-ce8a-4e75-ba24-77e470abb061", - "imageSha256": "bbefeae9f00e33adbda3a98fa85c91fae43d2590339a6036b0a352ddd75f7a7a", - "imageBytes": 813940, - "elapsedSeconds": 2.063, - "transport": "authenticated Paper HTTP camera_capture", - "testMode": "one owner/spectator client" -} diff --git a/docs/builds/gothic-hall-polished-evening.capture.json b/docs/builds/gothic-hall-polished-evening.capture.json deleted file mode 100644 index c70959e..0000000 --- a/docs/builds/gothic-hall-polished-evening.capture.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "capturedAt": "2026-09-12T20:40:31.617476861Z", - "dimension": "minecraft:overworld", - "x": 0.0, - "y": -20.0, - "z": -63.0, - "eyeY": -18.380000114440918, - "yaw": 25.0, - "pitch": 16.0, - "fov": 85, - "readiness": "local_chunks_and_render_queue_stable", - "serverRevisionVerified": false, - "loadedChunkRadius": 1, - "stabilizationTicks": 24, - "stabilizationFrames": 3, - "afterOperationId": "e7f0371d-e64c-48e0-86ae-13ac813dab98", - "status": "completed", - "mimeType": "image/png", - "width": 1280, - "height": 720, - "sourceWidth": 1280, - "sourceHeight": 720, - "captureId": "c1ac75c3-f21e-484e-be35-d783790cdeae", - "imageSha256": "637bd82eb213ed609cbc1625e62fec96858218df000f18998e22c96294222dfa", - "imageBytes": 677364, - "elapsedSeconds": 2.019, - "transport": "authenticated Paper HTTP camera_capture", - "testMode": "one owner/spectator client" -} diff --git a/docs/builds/gothic-hall-polished-evening.png b/docs/builds/gothic-hall-polished-evening.png deleted file mode 100644 index 92fdc6f..0000000 Binary files a/docs/builds/gothic-hall-polished-evening.png and /dev/null differ diff --git a/docs/builds/gothic-hall-polished-interior.capture.json b/docs/builds/gothic-hall-polished-interior.capture.json deleted file mode 100644 index 3072d56..0000000 --- a/docs/builds/gothic-hall-polished-interior.capture.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "capturedAt": "2026-09-12T20:37:56.024582621Z", - "dimension": "minecraft:overworld", - "x": -31.0, - "y": -51.0, - "z": -29.0, - "eyeY": -49.38000011444092, - "yaw": 0.0, - "pitch": -8.0, - "fov": 70, - "readiness": "local_chunks_and_render_queue_stable", - "serverRevisionVerified": false, - "loadedChunkRadius": 1, - "stabilizationTicks": 21, - "stabilizationFrames": 3, - "afterOperationId": "e7f0371d-e64c-48e0-86ae-13ac813dab98", - "status": "completed", - "mimeType": "image/png", - "width": 1280, - "height": 720, - "sourceWidth": 1280, - "sourceHeight": 720, - "captureId": "8c11e828-4678-40ee-8037-91dd50f11ec9", - "imageSha256": "4587c6169b11f94044bb46daea310d8d02f4ff1980106c896fc82298c656e516", - "imageBytes": 875403, - "elapsedSeconds": 1.478, - "transport": "authenticated Paper HTTP camera_capture", - "testMode": "one owner/spectator client" -} diff --git a/docs/builds/gothic-hall-polished-interior.png b/docs/builds/gothic-hall-polished-interior.png deleted file mode 100644 index a2d413d..0000000 Binary files a/docs/builds/gothic-hall-polished-interior.png and /dev/null differ diff --git a/docs/builds/gothic-hall-polished-day.png b/docs/images/build-demo.png similarity index 100% rename from docs/builds/gothic-hall-polished-day.png rename to docs/images/build-demo.png diff --git a/docs/recipes/decorations.json b/docs/recipes/decorations.json deleted file mode 100644 index b6102b1..0000000 --- a/docs/recipes/decorations.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "schema_version": 1, - "kind": "design_reference_catalog", - "reviewed_on": "2026-09-13", - "project": "Shacraft lobby", - "status": "Original design proposals; not executable build_prepare recipes and not newly built or playtested.", - "units": "Dimensions are width (X), height (Y), depth (Z) in blocks. Envelopes exclude separately specified circulation and buried support. Front is -Z; +Z points into the object. Adapt to surveyed world coordinates and rotate directional block states with the geometry.", - "capability_basis": "Material names checked against the saved Stage 07 project_context palette on 2026-09-13. Re-read live project_context before building. Material availability does not validate states, placement support or geometry.", - "source_notes": "source_ids identify conceptual inspiration. All dimensions, placement sequences and Shacraft adaptations below are original proposals, not source schematics.", - "sources": [ - {"id":"S01","title":"Interior Motives","url":"https://www.minecraft.net/en-us/article/interior-motives","use":"Purposeful interiors and agreement between interior and exterior architecture."}, - {"id":"S02","title":"Tranquil Towers","url":"https://www.minecraft.net/en-us/article/tranquil-towers","use":"Develop roof shape and silhouette before details."}, - {"id":"S03","title":"The Sky is No Limit","url":"https://www.minecraft.net/en-us/article/sky-no-limit","use":"Consistent palette and simple textures for a large composition."}, - {"id":"S04","title":"Taking Inventory: Glass Pane","url":"https://www.minecraft.net/en-us/article/taking-inventory--glass-pane","use":"Window depth; our sealed variant uses recessed full glass."}, - {"id":"S05","title":"Build With It: Quartz","url":"https://www.minecraft.net/en-us/article/build-with-it--quartz","use":"A pale material family can connect architecture and furnishings."}, - {"id":"S06","title":"Fantastic Furniture","url":"https://www.minecraft.net/en-us/article/fantastic-furniture","use":"Habitable furniture groups and independently finished partition faces."}, - {"id":"S07","title":"Cozy Chambers","url":"https://www.minecraft.net/en-us/article/cozy-chambers","use":"Wood, plants and lighting soften stone and copper interiors."}, - {"id":"S08","title":"Block of the Month: Decorated Pot","url":"https://www.minecraft.net/en-us/article/decorated-pot","use":"Flowerpot above decorated pot is a future material option, not supported by the current palette."}, - {"id":"S09","title":"Tutorial: Tips For Landscaping and Terraforming","url":"https://www.minecraft.net/en-us/article/tutorial--tips-for-landscaping-and-terraforming","use":"BlueNerd: connect paths, banks, vegetation and shaped terrain."}, - {"id":"S10","title":"Five simple path designs","url":"https://www.minecraft.net/en-us/article/five-simple-path-designs","use":"Zaypixel: route materials and nearby objects communicate place."}, - {"id":"S11","title":"Tutorial: Cottagecore Decor","url":"https://www.minecraft.net/en-us/article/tutorial--cottagecore-decor","use":"Kelpie the Fox: outdoor places to rest; reference uses Mizuno's 16 Craft."}, - {"id":"S12","title":"Taking Inventory: Lantern","url":"https://www.minecraft.net/en-us/article/taking-inventory--lantern","use":"Lanterns as recurring fixtures; historical spawning advice is not used."}, - {"id":"S13","title":"Build With It: Cobblestone!","url":"https://www.minecraft.net/en-us/article/build-with-it--cobblestone-","use":"Moss and vegetation can suggest age in masonry."}, - {"id":"S14","title":"Caves & Cliffs: Part I out today on Java","url":"https://www.minecraft.net/en-us/article/caves---cliffs--part-i-out-today-java","use":"Waxed copper, tinted glass light blocking, and introduced decorative blocks."}, - {"id":"S15","title":"Minecraft Java Edition 1.21.5: Spring to Life","url":"https://feedback.minecraft.net/hc/en-us/articles/35298208390797-Minecraft-Java-Edition-1-21-5-Spring-to-Life","use":"New plant orientations/densities and low-emission firefly bushes; not current MCP capabilities."}, - {"id":"S16","title":"Build with It: Diorite!","url":"https://www.minecraft.net/en-us/article/build-it--diorite-","use":"Texture intensity matters; polished variants can produce calmer surfaces."}, - {"id":"S17","title":"Minecraft Java Edition 26.2","url":"https://www.minecraft.net/en-us/article/minecraft-java-edition-26-2","use":"Current game has additional material families; game availability is distinct from MCP support."} - ], - "recipes": [ - { - "id":"arched-window-bay","category":"facade","name":"Recessed arched window bay", - "size":{"width":7,"height":10,"depth":3}, - "purpose":"Give a large cream facade readable shadow and a consistent structural rhythm.", - "materials":["minecraft:smooth_sandstone","minecraft:cut_sandstone","minecraft:smooth_sandstone_stairs","minecraft:smooth_sandstone_slab","minecraft:smooth_quartz","minecraft:brown_stained_glass"], - "steps":["Reserve a seven-wide bay between the actual structural axes; keep adjacent columns and floor decks intact.","At the glazing plane, use five-wide glass rows for the straight portion, then three and one at the crown. Fill every surrounding cell with full masonry so the plane stays sealed.","Place glazing one block behind the front wall plane; use the remaining front depth for a sill and paired jamb brackets.","Use sandstone stairs only as outer trim. Maintain full masonry or full glass behind partial-block trim.","Repeat the approved bay on the established grid; redesign corners and the main doorway deliberately."], - "clearance":"Keep projections outside the existing public route and its four-block headroom; do not narrow the seven-block station entry.", - "variants":["Plain rectangular head on a secondary facade.","One gold accent above the principal entrance only."], - "acceptance":["Closure plane has no unfilled cells, including arch shoulders.","Recess reads from a diagonal player-height camera.","No sill hides a sign or clips furniture.","Stair rotation and corner shape checked after neighboring blocks update."], - "avoid":["Different window silhouettes in every bay.","Assuming a pane or stair is a full containment wall."], - "source_ids":["S03","S04","S05"] - }, - { - "id":"cornice-bracket","category":"facade","name":"Cornice and paired bracket", - "size":{"width":9,"height":3,"depth":2}, - "purpose":"Explain the floor/eave line without filling the wall with ornament.", - "materials":["minecraft:smooth_sandstone","minecraft:smooth_sandstone_stairs","minecraft:smooth_sandstone_slab","minecraft:smooth_quartz"], - "steps":["Place a continuous stone band on the existing floor or eave line.","Use an inverted-stair underside and a thin slab cap, with at most two blocks of total outward depth.","Put the two bracket groups over structural supports and leave the central field quiet.","Continue the band around corners with an explicitly resolved return; prototype the corner before repeating."], - "clearance":"Remain above route headroom and outside signs' viewing rays; preserve roof drainage shapes and window tops.", - "variants":["Single-block projection on small wings.","A stronger cap at the tower base, using the same materials."], - "acceptance":["Horizontal band remains readable from the main approach.","Brackets align with actual supports.","Cap does not overwhelm the window opening."], - "avoid":["A thick shelf over every horizontal edge.","Unrelated gold trim on every bracket."], - "source_ids":["S02","S03","S05"] - }, - { - "id":"closed-dormer","category":"roof","name":"Closed green-copper dormer", - "size":{"width":7,"height":7,"depth":7}, - "purpose":"Add a secondary roof feature while keeping the clock tower dominant.", - "materials":["minecraft:smooth_sandstone","minecraft:brown_stained_glass","minecraft:waxed_oxidized_cut_copper","minecraft:waxed_oxidized_cut_copper_stairs","minecraft:waxed_oxidized_cut_copper_slab"], - "steps":["Fit a seven-wide sample to the actual roof pitch; use cream side cheeks and three-wide recessed glass.","Build the green pitched cap with a one-block overhang.","Keep a solid back and floor closure behind the apparent window; retain the existing public ceiling.","Test one dormer from the main approach before any repetition. Consider 14-18 blocks between centers only where the measured structural grid permits it."], - "clearance":"No opening into an unfinished attic, no overlap with tower faces or main roof ridge.", - "variants":["One larger central feature on a secondary wing.","Omit the dormer if it competes with the clock silhouette."], - "acceptance":["Tower remains the primary skyline feature.","Quiet copper roof planes remain visible between details.","Full closure survives the added roof cutout."], - "avoid":["Treating proposed spacing as a universal rule.","Adding attic access as part of a cosmetic edit."], - "source_ids":["S02","S03"] - }, - { - "id":"waiting-pocket","category":"interior","name":"Paired waiting benches", - "size":{"width":9,"height":4,"depth":7}, - "purpose":"Create a believable place to wait beside the main hall.", - "materials":["minecraft:spruce_stairs","minecraft:spruce_slab","minecraft:spruce_planks","minecraft:smooth_sandstone","minecraft:green_concrete","minecraft:lantern","minecraft:oak_leaves","minecraft:dirt"], - "steps":["Place two three-seat spruce benches facing a shared open pocket, with cream end supports.","Keep a compact table at one side rather than on the main desire line.","Use a flush green paving inset to group the furniture; this substitutes for unsupported carpet.","Add one planter and one supported lantern near the back, leaving the front visually open."], - "clearance":"Reserve an additional three-block-wide clear passing strip and four blocks of route headroom. Existing wider routes retain their width.", - "variants":["Single bench and planter in a smaller side recess.","Mirror the group around the room axis while varying the small prop."], - "acceptance":["Lift sign remains visible from the approach.","Seat fronts and table can be approached without stepping onto furniture.","Lamp and leaves remain stable after updates."], - "avoid":["Rows of isolated decorative chairs.","Plants in front of navigation signs."], - "source_ids":["S01","S06","S07"] - }, - { - "id":"information-counter","category":"interior","name":"Information counter", - "size":{"width":9,"height":5,"depth":4}, - "purpose":"Provide a clear information point that belongs in a station.", - "materials":["minecraft:spruce_planks","minecraft:spruce_slab","minecraft:smooth_sandstone","minecraft:smooth_sandstone_slab","minecraft:green_concrete","minecraft:glowstone","minecraft:brown_stained_glass"], - "steps":["Frame a seven-block counter between cream end piers; use spruce body and a thin cream slab top.","Align one green sign backing and one lit recess behind the counter.","Keep the back wall solid. Any future staff door remains a finished wall panel until the room exists.","Add actual text only through an existing authorized label mechanism; the block catalog does not create text entities."], - "clearance":"Reserve three clear queue blocks in front, separated from the entrance-to-lift route and its full width.", - "variants":["Short five-wide counter for a side room.","A small gold emblem only above the principal information point."], - "acceptance":["Queue does not cross the main route.","Counter is recognizable before reading the sign.","No false usable door into unfinished space."], - "avoid":["Several equally bright competing headers.","Claiming matchmaking or staff interaction exists merely because the counter is built."], - "source_ids":["S01","S05","S06"] - }, - { - "id":"smash-exhibit","category":"interior","name":"SMASH exhibit niche", - "size":{"width":7,"height":6,"depth":5}, - "purpose":"Communicate floating islands and knockback through one readable display.", - "materials":["minecraft:deepslate","minecraft:smooth_sandstone","minecraft:green_concrete","minecraft:gold_block","minecraft:grass_block","minecraft:dirt","minecraft:glowstone","minecraft:brown_stained_glass"], - "steps":["Build a full dark base, full back and light stone frame around a five-wide exhibit.","Choose one miniature island or one abstract impact motif as the subject.","Put one small gold focal detail near the subject and keep the rest of the frame consistent with adjacent niches.","Light the subject from concealed side, base or overhead recesses; preserve full-block base and closure.","Use one shared frame for all six future arena places; vary the subject and real destination text only when available."], - "clearance":"Reserve a three-block viewing strip outside the main circulation aisle; preserve the existing selection aisle.", - "variants":["Closed trophy pedestal with a single object.","Small landscape model for a future named arena."], - "acceptance":["Subject is legible from player eye level without entering the display.","No dark face hides the model's silhouette.","Placeholders clearly remain unassigned."], - "avoid":["Six unrelated palettes.","A visual void that is also an accidental physical hole."], - "source_ids":["S01","S06","S07"] - }, - { - "id":"avenue-edge","category":"path","name":"Formal avenue edge", - "size":{"width":9,"height":1,"depth":12}, - "purpose":"Connect station stonework to the garden through a continuous legible route.", - "materials":["minecraft:stone_bricks","minecraft:smooth_stone","minecraft:smooth_sandstone","minecraft:mossy_stone_bricks"], - "steps":["Keep seven center columns as continuous clear paving and one flush border column on each side.","Survey elevations along the whole segment before decoration; grade transitions as separate geometry.","Place furniture in additional two-to-three-block shoulders or wider pockets, not inside the nine-block strip.","Keep the station approach clean. Introduce small connected worn patches at outer edges beside planting.","Widen at entrances and follow actual destinations rather than adding arbitrary bends."], - "clearance":"Seven-block clear walking width and four-block headroom; this example must not narrow a wider established route.", - "variants":["Use local gray stone farther from the station.","Curve the outer border while keeping the tread continuous."], - "acceptance":["Entire route walkable at all grade changes.","Flush borders do not introduce a lip.","No lamp or planter creates a pinch point."], - "avoid":["Uniform random scatter of five paving materials.","Applying an organic-path zigzag rule to a formal station axis."], - "source_ids":["S09","S10","S13"] - }, - { - "id":"copper-lantern","category":"lighting","name":"Supported copper-arm lantern", - "size":{"width":3,"height":7,"depth":1}, - "purpose":"Establish one recognizable outdoor fixture family.", - "materials":["minecraft:stone_bricks","minecraft:stripped_spruce_log","minecraft:waxed_oxidized_cut_copper","minecraft:iron_chain","minecraft:lantern"], - "steps":["With the sample's first above-ground cell at y=0, place a full stone foot at (0,0,0), rooted into surveyed ground below.","Raise a post at x=0,z=0 through y=1..5; place a full-block copper arm across x=0..2 at y=6.","Place a vertical iron chain at (2,5,0) attached beneath the full arm, then a hanging lantern at (2,4,0).", - "Place support before attached details and inspect resulting block states.","Keep the entire post outside the clear path; use a mirrored pair only at a major entry."], - "clearance":"Four empty cells below the hanging lantern if ground is level with the sample. Recalculate for actual terrain; do not assume the same clearance over a rising stair.", - "variants":["Shorter ground-mounted fixture in a planted pocket with no walk-under route.","One gold marker at a major junction, outside this base recipe."], - "acceptance":["Full arm, chain axis and hanging lantern state remain valid after updates.","Night view shows the next step, intersection and sign between lamps.","Post cannot be used to bypass an existing containment boundary."], - "avoid":["Unsupported lanterns.","Treating a fixed lamp spacing as guaranteed illumination coverage."], - "source_ids":["S11","S12","S14"] - }, - { - "id":"layered-planter","category":"planting","name":"Layered planting pocket", - "size":{"width":7,"height":6,"depth":5}, - "purpose":"Produce a planted mass with a readable front, middle and background.", - "materials":["minecraft:dirt","minecraft:grass_block","minecraft:spruce_log","minecraft:spruce_leaves","minecraft:oak_leaves","minecraft:white_tulip","minecraft:pink_tulip","minecraft:oxeye_daisy","minecraft:smooth_sandstone"], - "steps":["Shape a low cream rim with solid soil behind it; support the entire pocket down to the existing terrain.","Put one small rooted tree toward the rear and two unequal shrub groups below it.","Use one dominant foliage species and two restrained flower colors near the front.","Leave patches of visible grass between groups; taper density toward the edge.","Save a seed and cluster anchors for repeatable variation; change group positions rather than every block independently."], - "clearance":"Keep the highest mass outside entrance and sign sightlines; no leaves overhang the protected walking clearance.", - "variants":["Formal clipped evergreen beside the station.","Looser shrubs without a tree beside the mountain path."], - "acceptance":["Flowers have valid soil.","Leaves use persistent=true and a stable read-back distance state.","Trunk has stable soil under it.","Foliage tint checked in the actual biome."], - "avoid":["A flower on every grass block.","Using leaves as the only containment boundary."], - "source_ids":["S07","S09","S11"] - }, - { - "id":"shore-pocket","category":"landscape","name":"Quiet shore with a stony recess", - "size":{"width":12,"height":4,"depth":8}, - "purpose":"Connect existing water and terrain with a believable local bank.", - "materials":["minecraft:stone","minecraft:andesite","minecraft:cobblestone","minecraft:mossy_cobblestone","minecraft:dirt","minecraft:grass_block","minecraft:oak_leaves"], - "steps":["Sample a short existing shoreline together with its water level and submerged bed.","Shape one quiet bank and one small stony recess; maintain continuous ground beneath the visible soil skin.","Put exposed rock at the outcrop and moss only in selected damp recesses.","Keep vegetation in sheltered land pockets and leave a clear view of the water.","This catalog specifies only land-side decor. Preserve water cells and the connected bed; stop if execution requires fluid placement or an unsupported brush through water."], - "clearance":"Preserve public route width, foundations, water level and any existing waterfront containment.", - "variants":["Grass bank with one isolated outcrop.","Stone recess beside a retaining wall, without changing its footing."], - "acceptance":["Water network and visible level preserved.","No new flow reaches a road or foundation after updates.","A below-water view shows a continuous bed; top-down appearance alone is insufficient."], - "avoid":["Repeating identical coves as tiles.","Assuming water exists in the MCP material palette."], - "source_ids":["S09","S13"] - }, - { - "id":"retaining-stair","category":"path","name":"Retaining wall and short stair", - "size":{"width":9,"height":5,"depth":10}, - "purpose":"Make a grade change feel supported and intentionally connected.", - "materials":["minecraft:stone_bricks","minecraft:stone_brick_stairs","minecraft:stone_brick_slab","minecraft:smooth_sandstone","minecraft:smooth_sandstone_slab","minecraft:mossy_stone_bricks","minecraft:dirt"], - "steps":["Survey lower and upper approaches first; adapt the proposed envelope to the actual rise.","Reserve five clear stair columns and two shoulder columns per side.","Use three or four one-block rises followed by a three-block-deep landing; fill every tread and retaining wall down to solid ground.","Return the side walls into the upper terrace instead of ending them as disconnected pillars.","Keep the cream cap continuous; limit moss to local soil-contact or shaded recesses."], - "clearance":"Five clear stair blocks and four-block headroom along the full ascending path, including caps and any overhead fixture.", - "variants":["A shorter flight for a lower terrace.","An additional landing and separate second flight when the actual rise requires it."], - "acceptance":["Stair facing matches direction of ascent.","Approach and landing elevations agree with the survey.","No hollow exposed underside or unsupported cap.","Shoulders do not create a climb-over route around containment."], - "avoid":["Solving a height mismatch with a decorative slab at the doorway.","Placing a standard stair module without measuring the destination."], - "source_ids":["S09","S10","S13"] - }, - { - "id":"luggage-bench","category":"props","name":"Bench and luggage group", - "size":{"width":7,"height":3,"depth":4}, - "purpose":"Tell a station story with a small, useful-looking prop group.", - "materials":["minecraft:spruce_stairs","minecraft:spruce_planks","minecraft:stripped_spruce_log","minecraft:smooth_sandstone","minecraft:green_concrete","minecraft:gold_block"], - "steps":["Put a three-seat bench against the back of a widened pocket, facing the plaza.","Group two wood-toned luggage shapes at one end, leaving seat fronts clear.","Reserve a green backing for one real destination or station identity plaque.","Use at most one small metallic accent in the group; omit it if it attracts more attention than the navigation."], - "clearance":"Provide a separate three-block passing strip in front and preserve any wider existing route.", - "variants":["Bench alone.","Bench with one planter instead of luggage."], - "acceptance":["Object reads as a resting place from the walking path.","Luggage does not intrude into the bench approach.","No label advertises an unfinished accessible destination."], - "avoid":["Crates in every empty corner.","Unsupported decorative item entities or invented text APIs."], - "source_ids":["S01","S06","S10"] - }, - { - "id":"concealed-light-lens","category":"lighting","name":"Full-block concealed light lens", - "size":{"width":3,"height":2,"depth":3}, - "purpose":"Illuminate a large room without adding a hanging fixture to every bay.", - "materials":["minecraft:glowstone","minecraft:brown_stained_glass","minecraft:smooth_sandstone"], - "steps":["Reserve a three-by-three floor sample outside patterned paving, tree roots, landings and interactive controls.","Keep full paving in the eight perimeter columns; in the center put glowstone below a full brown stained-glass block flush with the walking surface.","Keep supporting construction below the sample intact and record expected states before replacement.","Prototype one lens; increase coverage only after matching night and interior photographs reveal dark areas.","A ceiling variant reverses the emitter and visible lens positions within an intact full-block ceiling."], - "clearance":"Top of the floor lens remains flush. Ceiling version preserves all existing route headroom and sealed upper rooms.", - "variants":["Use ordinary full glass if the brown lens makes the sample too dark.","Concealed side lighting for an exhibit rather than more floor lights."], - "acceptance":["Full-block collision remains at the floor/ceiling surface.","Actual captured room and exhibit faces become legible.","No new bright fixture competes with the entrance or lift."], - "avoid":["Tinted glass: it blocks light and is not interchangeable with stained glass.","Assuming more fixtures automatically produce a better interior."], - "source_ids":["S07","S14"], - "local_precedent":"Stage 07 used concealed brown-glass lighting and real camera review; this reusable sample has not itself been instantiated." - }, - { - "id":"masonry-weathering","category":"surface","name":"Cause-based masonry weathering", - "size":{"width":12,"height":6,"depth":1}, - "purpose":"Add age and material variation while retaining large calm wall fields.", - "materials":["minecraft:stone_bricks","minecraft:cracked_stone_bricks","minecraft:mossy_stone_bricks","minecraft:andesite","minecraft:smooth_sandstone","minecraft:cut_sandstone"], - "steps":["Choose either the gray retaining-wall family or the cream facade family; do not mix both randomly.","Save a mask for soil contact, sheltered joints and selected worn edges.","For gray masonry, use small connected mossy patches in the mask and a few connected cracked areas away from structural focal edges.","For cream masonry, keep the field mostly smooth and use cut sandstone to express masonry bands or quiet local variation; do not add green flecks everywhere.","Preserve trim, signs and dominant shapes, then inspect at near, approach and whole-building distances."], - "clearance":"Surface substitution only; preserve structural collision, public closure and existing manual edits.", - "variants":["Clean maintained station entrance with minimal weathering.","More aged retaining wall near shaded soil contact."], - "acceptance":["Weathering locations have a visible cause.","Large forms still read from the approach.","Exact seed and patch anchors recorded for reproducibility."], - "avoid":["Independent random choice at every voxel.","A universal percentage of damage on every material.","Replacing load-bearing full cubes with stairs merely for texture."], - "source_ids":["S03","S13","S16"] - } - ] -} diff --git a/docs/references/gothic-hall-v1.png b/docs/references/gothic-hall-v1.png deleted file mode 100644 index 446da53..0000000 Binary files a/docs/references/gothic-hall-v1.png and /dev/null differ diff --git a/docs/references/gothic-hall-v1.prompt.txt b/docs/references/gothic-hall-v1.prompt.txt deleted file mode 100644 index c2ff4fa..0000000 --- a/docs/references/gothic-hall-v1.prompt.txt +++ /dev/null @@ -1,16 +0,0 @@ -Generated using the built-in image_gen tool. -Concept reference for minecraft-builder-mcp; not an in-game screenshot or a dimensioned construction plan. - -Use case: stylized-concept. -Asset type: architectural reference for a substantial building we will later construct in Minecraft Java. -Primary request: one impressive but realistically buildable Gothic civic guildhall / town hall, significantly larger and more sophisticated than a small watchtower. - -Subject: a coherent single building with a long three-storey great hall, a steep dark gabled roof, a prominent square bell tower with a tall pointed roof, a lower connected side wing and an entrance stair. Approximately 65 by 45 Minecraft blocks in footprint, highest tower around 55 blocks; communicate substantial scale through believable block sizes. Strong readable silhouette and balanced architectural hierarchy. The front has a deep pointed-arch entrance and a restrained series of tall, narrow RECESSED windows with stone jambs and lintels. Glass sits inside thick masonry: absolutely no protruding glass boxes. A few recessed paired lancet windows on the great hall. Buttresses, structural masonry, layered cornices and roofs made of explicit block steps; purposeful details with some calm wall areas. The attached wing and tower must visibly connect to usable interior volumes. - -Style/medium: beautiful high-quality Minecraft voxel architectural concept render, faithful cubic full-block, stair and slab construction at consistent voxel scale, crisp recognisable vanilla-like pixel textures. All edges obey the Minecraft grid. Not a smooth realistic European building with a token pixel filter. -Materials: predominantly grey stone bricks with restrained polished andesite and light stone trim, dark deepslate stair/slab roofs, dark oak and spruce for doors and inset timber features, dark glass deep in the walls. Restrained palette; strong depth and masonry shadows, no random patchwork. -Scene: the complete building stands on a small paved terrace at ground level, with minimal grass terrain and a quiet pale sky. Sparse setting keeps the architecture fully readable. -Composition: a single landscape image, elevated front three-quarter architectural view showing the entrance, long side facade, roof structure and tower; the full building and entire highest roof are inside the frame with breathing room. Moderate perspective, no fisheye. Building fills most of the composition. -Lighting: clear soft daylight with warm sunlight, readable shadow detail; avoid night, fog or overexposure. -Avoid: people, mobs, UI, HUD, captions, labels, text, watermarks, logos, floating islands, mountains obscuring the building, sprawling cities, enormous fantasy spires, excessive clutter or unbuildable smooth curves. -Generate a brand-new standalone reference, not an edit or screenshot of an existing build. diff --git a/docs/references/shacraft-arrival-garden-detail.png b/docs/references/shacraft-arrival-garden-detail.png deleted file mode 100644 index aba84b9..0000000 Binary files a/docs/references/shacraft-arrival-garden-detail.png and /dev/null differ diff --git a/docs/references/shacraft-arrival-garden-evening.png b/docs/references/shacraft-arrival-garden-evening.png deleted file mode 100644 index a4df83b..0000000 Binary files a/docs/references/shacraft-arrival-garden-evening.png and /dev/null differ diff --git a/docs/references/shacraft-arrival-garden-map.png b/docs/references/shacraft-arrival-garden-map.png deleted file mode 100644 index 082bbf8..0000000 Binary files a/docs/references/shacraft-arrival-garden-map.png and /dev/null differ diff --git a/docs/references/shacraft-arrival-garden-overview.png b/docs/references/shacraft-arrival-garden-overview.png deleted file mode 100644 index 626b955..0000000 Binary files a/docs/references/shacraft-arrival-garden-overview.png and /dev/null differ diff --git a/docs/references/shacraft-arrival-garden-verification.json b/docs/references/shacraft-arrival-garden-verification.json deleted file mode 100644 index 829039c..0000000 --- a/docs/references/shacraft-arrival-garden-verification.json +++ /dev/null @@ -1,589 +0,0 @@ -{ - "stage": "03-arrival-garden", - "writes": 4811, - "batches": 12, - "final_distinct_changed_blocks": 4734, - "actual_block_checks": { - "version": 1, - "mode": "actual_after_snapshot", - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "world_edits": 0, - "desired_blocks": 4734, - "baseline_expected_states_verified": 4734, - "actual_exact_state_mismatches": 0, - "actual_mismatch_examples": [], - "fixtures": { - "passed": true, - "checked": { - "leaves": 537, - "lanterns": 16, - "flowers": 258, - "rooted_trees": 12, - "root_connected_logs": 73 - }, - "failures": [] - }, - "walking": { - "checked_points": 4955, - "checked_route_edges": 0, - "failures": [], - "passed": true, - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "note": "Observed surface samples and vertical headroom; full cubes, slabs and explicit straight bottom-stair samples. Stair samples are a surface profile, not a full player-width collision simulation. Cached snapshot is not a live re-read." - }, - "navigation": { - "version": 1, - "source": { - "x": 0, - "z": 9 - }, - "world_edits": 0, - "method": "Four quarter-center surface samples per clear cell; cardinal half-block BFS, rise/drop <= 0.5 block.", - "limitations": "Planned surface topology only. No headroom, body-width collision, material state or live-world verification. Width checks are cardinal cross-sections using the dominant local tangent; full declared masks are also checked.", - "geometry_sha256": "b58820302ac02852731c49ac482b0b6684b2aa7fe319d7e9dfc3d8374c972491", - "planned_columns": 16309, - "clear_columns": 14478, - "surface_samples": 57912, - "reachable_samples": 57912, - "unreachable_clear_columns": [], - "unreachable_by_group": {}, - "station": { - "name": "clock-station", - "x": -6, - "z": -105, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 119.0 - }, - "routes": [ - { - "id": "station-axis", - "passed": true, - "declared_clear_width": 9, - "corridor_columns": 593, - "missing_corridor_columns": [], - "declared_clear_columns": 486, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 47, - "minimum_structural_cross_section": 9, - "minimum_clear_cross_section": 9, - "minimum_reachable_cross_section": 9, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0, - "centerline_jumps": [], - "endpoints": [ - { - "name": "station-axis:start", - "x": 0, - "z": -37, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 45.5 - }, - { - "name": "station-axis:end", - "x": -6, - "z": -77, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 91.0 - } - ] - }, - { - "id": "south-axis-local", - "passed": true, - "declared_clear_width": 9, - "corridor_columns": 685, - "missing_corridor_columns": [], - "declared_clear_columns": 565, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 59, - "minimum_structural_cross_section": 9, - "minimum_clear_cross_section": 9, - "minimum_reachable_cross_section": 9, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "south-axis-local:start", - "x": 0, - "z": 57, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 48.0 - }, - { - "name": "south-axis-local:end", - "x": 1, - "z": 110, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 102.0 - } - ] - }, - { - "id": "portal-radial-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 531, - "missing_corridor_columns": [], - "declared_clear_columns": 376, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 70, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "portal-radial-local:start", - "x": -35, - "z": -14, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 57.0 - }, - { - "name": "portal-radial-local:end", - "x": -72, - "z": -46, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 126.0 - } - ] - }, - { - "id": "lake-radial-local", - "passed": true, - "declared_clear_width": 5, - "corridor_columns": 403, - "missing_corridor_columns": [], - "declared_clear_columns": 297, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 63, - "minimum_structural_cross_section": 5, - "minimum_clear_cross_section": 5, - "minimum_reachable_cross_section": 5, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "lake-radial-local:start", - "x": -42, - "z": 18, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 50.5 - }, - { - "name": "lake-radial-local:end", - "x": -90, - "z": 32, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 112.5 - } - ] - }, - { - "id": "east-radial-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 451, - "missing_corridor_columns": [], - "declared_clear_columns": 341, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 49, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "east-radial-local:start", - "x": 42, - "z": 16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 49.0 - }, - { - "name": "east-radial-local:end", - "x": 82, - "z": 8, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 90.5 - } - ] - }, - { - "id": "ring-northwest-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 468, - "missing_corridor_columns": [], - "declared_clear_columns": 350, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 53, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "ring-northwest-local:start", - "x": -40, - "z": -65, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 113.0 - }, - { - "name": "ring-northwest-local:end", - "x": -80, - "z": -77, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 165.0 - } - ] - }, - { - "id": "ring-northeast-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 505, - "missing_corridor_columns": [], - "declared_clear_columns": 374, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 60, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "ring-northeast-local:start", - "x": 40, - "z": -95, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 151.0 - }, - { - "name": "ring-northeast-local:end", - "x": 82, - "z": -78, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 210.0 - } - ] - }, - { - "id": "station-entrance-stair", - "passed": true, - "declared_clear_width": 21, - "corridor_columns": 161, - "missing_corridor_columns": [], - "declared_clear_columns": 147, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 7, - "minimum_structural_cross_section": 21, - "minimum_clear_cross_section": 21, - "minimum_reachable_cross_section": 21, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "station-entrance-stair:start", - "x": -6, - "z": -77, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 91.0 - }, - { - "name": "station-entrance-stair:end", - "x": -6, - "z": -83, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 97.0 - } - ] - } - ], - "passed": true, - "decorated_ground_columns_excluded": 662, - "bench_front_access": [ - { - "name": "bench--17--17-front--1", - "x": -16, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 40.0 - }, - { - "name": "bench--17--17-front-0", - "x": -17, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 41.0 - }, - { - "name": "bench--17--17-front-1", - "x": -18, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 42.0 - }, - { - "name": "bench-17--17-front--1", - "x": 18, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 42.5 - }, - { - "name": "bench-17--17-front-0", - "x": 17, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 41.5 - }, - { - "name": "bench-17--17-front-1", - "x": 16, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 40.5 - }, - { - "name": "bench--32-2-front--1", - "x": -31, - "z": 1, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 38.0 - }, - { - "name": "bench--32-2-front-0", - "x": -31, - "z": 2, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 37.0 - }, - { - "name": "bench--32-2-front-1", - "x": -31, - "z": 3, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 36.0 - }, - { - "name": "bench-32-2-front--1", - "x": 31, - "z": 3, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 36.5 - }, - { - "name": "bench-32-2-front-0", - "x": 31, - "z": 2, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 37.5 - }, - { - "name": "bench-32-2-front-1", - "x": 31, - "z": 1, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 38.5 - }, - { - "name": "bench--22-35-front--1", - "x": -23, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 47.5 - }, - { - "name": "bench--22-35-front-0", - "x": -22, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 46.5 - }, - { - "name": "bench--22-35-front-1", - "x": -21, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 45.5 - }, - { - "name": "bench-22-35-front--1", - "x": 21, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 46.0 - }, - { - "name": "bench-22-35-front-0", - "x": 22, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 47.0 - }, - { - "name": "bench-22-35-front-1", - "x": 23, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 48.0 - } - ] - }, - "passed": true, - "note": "Static support, leaf-distance and point-sampled navigation checks; not a complete moving-player collision simulation. Actual mode uses sequential observed snapshots.", - "input_sha256": { - "before": "770a08974e5bf9793c56be780576947dcceb2cd5b8400f479a5356e8509b3146", - "layout": "699060010db084dc05c47400572ac4440784d1a0c3dbd877122decd183048923", - "metadata": "400f672864117b62ed173600932ca04ab15a0c548587bb3d39f34eaa91484e89", - "walk": "453c5ee1e699a59be845db22abc5e481915768ab229a79c0cc6019d8b3e39d0d", - "navigation": "1baf3c4ef2c1ddfca22240395fcde97b5210d97c9afe5cb2b6c3275154cfe044", - "after": "5310988e559868a7e18a98d2e951d87e789798d6a1027df491ede65bb4bcdff8" - } - }, - "actual_surface_checks": { - "version": 1, - "world": "shacraft_lobby_v2", - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "source": "Full live Paper surface maps plus observed baseline voxels and desired edit overlay", - "verified_surface_columns": 589824, - "affected_columns": 2823, - "outside_edit_columns_verified": 587001, - "changed_surface_columns": 2680, - "surface_mismatches": 0, - "outside_edit_surface_mismatches": 0, - "mismatch_examples": [], - "original_visible_water_columns": 41467, - "visible_water_surface_changes": 0, - "water_surface_change_examples": [], - "expected_states_checked_against_baseline": 4734, - "placed_or_replaced_voxels": 4702, - "removed_voxels": 32, - "lower_snapshot_voxels_consulted_after_cuts": 12, - "observed_water_voxels_replaced": [], - "water_preservation_passed": true, - "passed": true, - "capture_started_at": "2026-09-13T02:35:54.623721211Z", - "capture_finished_at": "2026-09-13T02:37:49.861979626Z", - "atomic_snapshot": false, - "note": "Sequential map captures; edits must be idle during each capture. This checks every visible surface and the observed water blocks in the edit. Hidden untouched blocks outside the voxel survey are not inferred. Live voxel and headroom checks are separate.", - "inputs_sha256": { - "before_map": "25b7eb12af89e0f9931a56f31cc8eca952b817a34218ffdc4fbf3636e1a3e917", - "after_map": "d0dde2583dfbb2f5b37b45a2d1cd5c239b8bfd2e72d4c1434ac280e99f8ed2af", - "baseline_voxels": "770a08974e5bf9793c56be780576947dcceb2cd5b8400f479a5356e8509b3146", - "desired_layout": "699060010db084dc05c47400572ac4440784d1a0c3dbd877122decd183048923" - } - }, - "camera_readiness": "Real Fabric frames; heuristic render readiness, server block state verified separately." -} diff --git a/docs/references/shacraft-balustrade-detail.png b/docs/references/shacraft-balustrade-detail.png deleted file mode 100644 index 14ce8d5..0000000 Binary files a/docs/references/shacraft-balustrade-detail.png and /dev/null differ diff --git a/docs/references/shacraft-balustrade-exterior.png b/docs/references/shacraft-balustrade-exterior.png deleted file mode 100644 index bddf1b4..0000000 Binary files a/docs/references/shacraft-balustrade-exterior.png and /dev/null differ diff --git a/docs/references/shacraft-balustrade-map.png b/docs/references/shacraft-balustrade-map.png deleted file mode 100644 index 62ee9b3..0000000 Binary files a/docs/references/shacraft-balustrade-map.png and /dev/null differ diff --git a/docs/references/shacraft-balustrade-overview.png b/docs/references/shacraft-balustrade-overview.png deleted file mode 100644 index c53587e..0000000 Binary files a/docs/references/shacraft-balustrade-overview.png and /dev/null differ diff --git a/docs/references/shacraft-balustrade-verification.json b/docs/references/shacraft-balustrade-verification.json deleted file mode 100644 index 5904bc7..0000000 --- a/docs/references/shacraft-balustrade-verification.json +++ /dev/null @@ -1,695 +0,0 @@ -{ - "version": 1, - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "mode": "actual_after_snapshot", - "passed": true, - "world_edits": 0, - "desired_blocks": 710, - "guard": { - "passed": true, - "columns": 302, - "piers": 46, - "cardinal_components": [ - 31, - 46, - 71, - 74, - 80 - ], - "grounded_footings": 22, - "adjacent_road_rails": 6, - "failures": [] - }, - "protected_road_columns": 2927, - "protection_failures": [], - "preserved_fixture_states": 1172, - "lost_fixtures": [], - "fixtures": { - "passed": true, - "checked": { - "leaves": 537, - "lanterns": 16, - "flowers": 258, - "rooted_trees": 12, - "root_connected_logs": 73 - }, - "failures": [] - }, - "walking": { - "checked_points": 4815, - "checked_route_edges": 0, - "failures": [], - "passed": true, - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "note": "Observed surface samples and vertical headroom; full cubes, slabs and explicit straight bottom-stair samples. Stair samples are a surface profile, not a full player-width collision simulation. Cached snapshot is not a live re-read." - }, - "navigation": { - "version": 1, - "source": { - "x": 0, - "z": 9 - }, - "world_edits": 0, - "method": "Four quarter-center surface samples per clear cell; cardinal half-block BFS, rise/drop <= 0.5 block.", - "limitations": "Planned surface topology only. No headroom, body-width collision, material state or live-world verification. Width checks are cardinal cross-sections using the dominant local tangent; full declared masks are also checked.", - "geometry_sha256": "4a2b951946c91a09dcef9606bfebd8eba51c34f21e827b019dc0a5f768cf4148", - "planned_columns": 16309, - "clear_columns": 14338, - "surface_samples": 57352, - "reachable_samples": 57352, - "unreachable_clear_columns": [], - "unreachable_by_group": {}, - "station": { - "name": "clock-station", - "x": -6, - "z": -105, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 119.0 - }, - "routes": [ - { - "id": "station-axis", - "passed": true, - "declared_clear_width": 9, - "corridor_columns": 593, - "missing_corridor_columns": [], - "declared_clear_columns": 486, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 47, - "minimum_structural_cross_section": 9, - "minimum_clear_cross_section": 9, - "minimum_reachable_cross_section": 9, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0, - "centerline_jumps": [], - "endpoints": [ - { - "name": "station-axis:start", - "x": 0, - "z": -37, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 45.5 - }, - { - "name": "station-axis:end", - "x": -6, - "z": -77, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 91.0 - } - ] - }, - { - "id": "south-axis-local", - "passed": true, - "declared_clear_width": 9, - "corridor_columns": 685, - "missing_corridor_columns": [], - "declared_clear_columns": 565, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 59, - "minimum_structural_cross_section": 9, - "minimum_clear_cross_section": 9, - "minimum_reachable_cross_section": 9, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "south-axis-local:start", - "x": 0, - "z": 57, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 48.0 - }, - { - "name": "south-axis-local:end", - "x": 1, - "z": 110, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 102.0 - } - ] - }, - { - "id": "portal-radial-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 531, - "missing_corridor_columns": [], - "declared_clear_columns": 376, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 70, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "portal-radial-local:start", - "x": -35, - "z": -14, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 57.0 - }, - { - "name": "portal-radial-local:end", - "x": -72, - "z": -46, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 126.0 - } - ] - }, - { - "id": "lake-radial-local", - "passed": true, - "declared_clear_width": 5, - "corridor_columns": 403, - "missing_corridor_columns": [], - "declared_clear_columns": 297, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 63, - "minimum_structural_cross_section": 5, - "minimum_clear_cross_section": 5, - "minimum_reachable_cross_section": 5, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "lake-radial-local:start", - "x": -42, - "z": 18, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 50.5 - }, - { - "name": "lake-radial-local:end", - "x": -90, - "z": 32, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 112.5 - } - ] - }, - { - "id": "east-radial-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 451, - "missing_corridor_columns": [], - "declared_clear_columns": 341, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 49, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "east-radial-local:start", - "x": 42, - "z": 16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 49.0 - }, - { - "name": "east-radial-local:end", - "x": 82, - "z": 8, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 90.5 - } - ] - }, - { - "id": "ring-northwest-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 468, - "missing_corridor_columns": [], - "declared_clear_columns": 350, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 53, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "ring-northwest-local:start", - "x": -40, - "z": -65, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 113.0 - }, - { - "name": "ring-northwest-local:end", - "x": -80, - "z": -77, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 165.0 - } - ] - }, - { - "id": "ring-northeast-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 505, - "missing_corridor_columns": [], - "declared_clear_columns": 374, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 60, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "ring-northeast-local:start", - "x": 40, - "z": -95, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 151.0 - }, - { - "name": "ring-northeast-local:end", - "x": 82, - "z": -78, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 210.0 - } - ] - }, - { - "id": "station-entrance-stair", - "passed": true, - "declared_clear_width": 21, - "corridor_columns": 161, - "missing_corridor_columns": [], - "declared_clear_columns": 147, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 7, - "minimum_structural_cross_section": 21, - "minimum_clear_cross_section": 21, - "minimum_reachable_cross_section": 21, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "station-entrance-stair:start", - "x": -6, - "z": -77, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 91.0 - }, - { - "name": "station-entrance-stair:end", - "x": -6, - "z": -83, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 97.0 - } - ] - } - ], - "passed": true, - "bench_access": [ - { - "name": "bench-access--31-1", - "x": -31, - "z": 1, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 38.0 - }, - { - "name": "bench-access--31-2", - "x": -31, - "z": 2, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 37.0 - }, - { - "name": "bench-access--31-3", - "x": -31, - "z": 3, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 36.0 - }, - { - "name": "bench-access--23-32", - "x": -23, - "z": 32, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 45.5 - }, - { - "name": "bench-access--23-33", - "x": -23, - "z": 33, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 46.5 - }, - { - "name": "bench-access--23-34", - "x": -23, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 47.5 - }, - { - "name": "bench-access--22-33", - "x": -22, - "z": 33, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 45.5 - }, - { - "name": "bench-access--22-34", - "x": -22, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 46.5 - }, - { - "name": "bench-access--21-34", - "x": -21, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 45.5 - }, - { - "name": "bench-access--18--16", - "x": -18, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 42.0 - }, - { - "name": "bench-access--18--15", - "x": -18, - "z": -15, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 41.0 - }, - { - "name": "bench-access--18--14", - "x": -18, - "z": -14, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 40.0 - }, - { - "name": "bench-access--17--16", - "x": -17, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 41.0 - }, - { - "name": "bench-access--17--15", - "x": -17, - "z": -15, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 40.0 - }, - { - "name": "bench-access--16--16", - "x": -16, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 40.0 - }, - { - "name": "bench-access--16--15", - "x": -16, - "z": -15, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 39.0 - }, - { - "name": "bench-access-16--16", - "x": 16, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 40.5 - }, - { - "name": "bench-access-16--15", - "x": 16, - "z": -15, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 39.5 - }, - { - "name": "bench-access-17--16", - "x": 17, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 41.5 - }, - { - "name": "bench-access-17--15", - "x": 17, - "z": -15, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 40.5 - }, - { - "name": "bench-access-18--16", - "x": 18, - "z": -16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 42.5 - }, - { - "name": "bench-access-18--15", - "x": 18, - "z": -15, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 41.5 - }, - { - "name": "bench-access-18--14", - "x": 18, - "z": -14, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 40.5 - }, - { - "name": "bench-access-21-34", - "x": 21, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 46.0 - }, - { - "name": "bench-access-22-33", - "x": 22, - "z": 33, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 46.0 - }, - { - "name": "bench-access-22-34", - "x": 22, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 47.0 - }, - { - "name": "bench-access-23-32", - "x": 23, - "z": 32, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 46.0 - }, - { - "name": "bench-access-23-33", - "x": 23, - "z": 33, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 47.0 - }, - { - "name": "bench-access-23-34", - "x": 23, - "z": 34, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 48.0 - }, - { - "name": "bench-access-31-1", - "x": 31, - "z": 1, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 38.5 - }, - { - "name": "bench-access-31-2", - "x": 31, - "z": 2, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 37.5 - }, - { - "name": "bench-access-31-3", - "x": 31, - "z": 3, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 36.5 - } - ] - }, - "volume": { - "passed": true, - "outside_desired_voxels": 555598, - "desired_voxels": 710, - "mismatch_examples": [], - "scope_note": "Every voxel inside the supplied before snapshot; no claim for unobserved exterior voxels." - }, - "note": "Point-sampled body headroom and navigation; not a full moving-player collision simulation.", - "input_sha256": { - "layout": "6ec0e65027080207dc8e5f7b19afaa7b33f6ddb723de5c3632cc6097592da581", - "metadata": "242507f39a354f04281bdf4a32a2fe05d9c144af9d1b7c7e7e8ddecc17976114", - "garden_plan": "699060010db084dc05c47400572ac4440784d1a0c3dbd877122decd183048923", - "garden_metadata": "400f672864117b62ed173600932ca04ab15a0c548587bb3d39f34eaa91484e89", - "garden_walk": "453c5ee1e699a59be845db22abc5e481915768ab229a79c0cc6019d8b3e39d0d", - "navigation": "1baf3c4ef2c1ddfca22240395fcde97b5210d97c9afe5cb2b6c3275154cfe044", - "before": "3172d03627e625d759a3b9ce881282e26db2926c35dd68ac0036047ae33c3488", - "after": "c77e8f953f257822f6f6489d397ab39f40459c18183743dbdbea3e168a193c65" - } -} \ No newline at end of file diff --git a/docs/references/shacraft-foundations-map.png b/docs/references/shacraft-foundations-map.png deleted file mode 100644 index 4167fb0..0000000 Binary files a/docs/references/shacraft-foundations-map.png and /dev/null differ diff --git a/docs/references/shacraft-foundations-overview.png b/docs/references/shacraft-foundations-overview.png deleted file mode 100644 index 6a9a0ec..0000000 Binary files a/docs/references/shacraft-foundations-overview.png and /dev/null differ diff --git a/docs/references/shacraft-foundations-stairs.png b/docs/references/shacraft-foundations-stairs.png deleted file mode 100644 index 7f35075..0000000 Binary files a/docs/references/shacraft-foundations-stairs.png and /dev/null differ diff --git a/docs/references/shacraft-foundations-verification.json b/docs/references/shacraft-foundations-verification.json deleted file mode 100644 index f29f6bc..0000000 --- a/docs/references/shacraft-foundations-verification.json +++ /dev/null @@ -1,551 +0,0 @@ -{ - "stage": "02-foundations-zones-01-02", - "total_checked_writes": 79081, - "journalled_batches": 67, - "final_changed_voxels": 78673, - "checks": { - "surface-verification": { - "version": 1, - "world": "shacraft_lobby_v2", - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "source": "Full live Paper surface maps plus observed baseline voxels and desired edit overlay", - "verified_surface_columns": 589824, - "affected_columns": 16901, - "outside_edit_columns_verified": 572923, - "changed_surface_columns": 16901, - "surface_mismatches": 0, - "outside_edit_surface_mismatches": 0, - "mismatch_examples": [], - "original_visible_water_columns": 41467, - "visible_water_surface_changes": 0, - "water_surface_change_examples": [], - "expected_states_checked_against_baseline": 78673, - "placed_or_replaced_voxels": 77950, - "removed_voxels": 723, - "lower_snapshot_voxels_consulted_after_cuts": 12, - "observed_water_voxels_replaced": [], - "water_preservation_passed": true, - "passed": true, - "capture_started_at": "2026-09-13T01:47:40.367423160Z", - "capture_finished_at": "2026-09-13T01:49:35.666216379Z", - "atomic_snapshot": false, - "note": "Sequential map captures; edits must be idle during each capture. This checks every visible surface and the observed water blocks in the edit. Hidden untouched blocks outside the voxel survey are not inferred. Live voxel and headroom checks are separate.", - "inputs_sha256": { - "before_map": "45612085877b44b0fc58a67f3c140049452b3fc8e8798ff816bd368852a2eaa2", - "after_map": "b4bc17c20864543fab5b03dd1f6ccecba2b79c261fa45f3c8497f1824fb8ccfa", - "baseline_voxels": "d53662eac3c06ed79d86ba5da9ce0e95c32ade146f6eb9f4f30782ec51ae8677", - "desired_layout": "22da24b01fa5d14f278253e48d97721ffb5341543b46827c17617020c1b47374" - } - }, - "walk-verification": { - "checked_points": 15708, - "checked_route_edges": 0, - "failures": [], - "passed": true, - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "note": "Observed surface samples and vertical headroom; full cubes, slabs and explicit straight bottom-stair samples. Stair samples are a surface profile, not a full player-width collision simulation. Cached snapshot is not a live re-read." - }, - "block-verification": { - "version": 1, - "source": "Complete final observed block-state survey", - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "verified_desired_blocks": 78673, - "mismatches": 0, - "mismatch_examples": [], - "passed": true, - "verified_by_desired_material": { - "minecraft:deepslate_bricks": 2983, - "minecraft:polished_andesite": 2635, - "minecraft:stone_brick_stairs": 724, - "minecraft:stone": 53085, - "minecraft:stone_bricks": 4399, - "minecraft:smooth_sandstone": 5776, - "minecraft:smooth_stone": 6343, - "minecraft:stone_brick_wall": 670, - "minecraft:grass_block": 561, - "minecraft:air": 723, - "minecraft:chiseled_stone_bricks": 27, - "minecraft:smooth_stone_slab": 27, - "minecraft:lantern": 27, - "minecraft:smooth_quartz": 236, - "minecraft:green_concrete": 457 - }, - "verified_by_group": { - "lake-radial-local-foundation": 963, - "lake-radial-local-paving": 403, - "edge-balustrades": 651, - "remove-obsolete-survey": 572, - "ring-northwest-local-foundation": 1116, - "ring-northwest-local-paving": 468, - "ring-northwest-local-clearance": 30, - "station-west-pilasters": 184, - "clock-station-foundation": 31398, - "station-west-arch-stones": 20, - "clock-station-paving": 5508, - "lamp-piers": 81, - "lamps": 27, - "station-west-blind-arcade": 146, - "portal-radial-local-foundation": 1054, - "portal-radial-local-paving": 531, - "portal-radial-local-clearance": 219, - "station-structural-bands": 256, - "lake-radial-local-clearance": 4, - "arrival-hex-foundation": 14211, - "arrival-hex-paving": 4741, - "station-forecourt-foundation": 3113, - "station-forecourt-paving": 1298, - "clock-station-clearance": 44, - "station-entrance-stair-foundation": 418, - "station-entrance-stair-paving": 161, - "station-forecourt-clearance": 86, - "arrival-medallion": 693, - "arrival-hex-clearance": 203, - "station-axis-foundation": 1166, - "station-axis-paving": 582, - "station-axis-clearance": 83, - "south-axis-local-foundation": 1658, - "south-axis-local-paving": 685, - "south-axis-local-clearance": 27, - "ring-northeast-local-foundation": 2663, - "station-ne-connection-foundation": 143, - "station-ne-connection-paving": 27, - "ring-northeast-local-paving": 505, - "east-radial-local-foundation": 1963, - "east-radial-local-paving": 451, - "ring-northeast-local-clearance": 8, - "east-radial-local-clearance": 8, - "temporary-bridge-threshold": 86, - "temporary-bridge-gates": 19 - }, - "desired_layout_sha256": "22da24b01fa5d14f278253e48d97721ffb5341543b46827c17617020c1b47374", - "snapshot_started_at": "2026-09-13T01:47:42.024943+00:00", - "snapshot_finished_at": "2026-09-13T01:48:27.217492+00:00", - "atomic_snapshot": false, - "note": "Full block states include slab/stair orientation, waterlogging and other properties. Sequential read snapshot; edits were completed before capture." - }, - "navigation-verification": { - "version": 1, - "source": { - "x": 0, - "z": 9 - }, - "world_edits": 0, - "method": "Four quarter-center surface samples per clear cell; cardinal half-block BFS, rise/drop <= 0.5 block.", - "limitations": "Planned surface topology only. No headroom, body-width collision, material state or live-world verification. Width checks are cardinal cross-sections using the dominant local tangent; full declared masks are also checked.", - "geometry_sha256": "e5c78e645100261d1720e50ea23eaa65650eb796341facb9a50bed0ab392e736", - "planned_columns": 16309, - "clear_columns": 15140, - "surface_samples": 60560, - "reachable_samples": 60560, - "unreachable_clear_columns": [], - "unreachable_by_group": {}, - "station": { - "name": "clock-station", - "x": -6, - "z": -105, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 119.0 - }, - "routes": [ - { - "id": "station-axis", - "passed": true, - "declared_clear_width": 9, - "corridor_columns": 593, - "missing_corridor_columns": [], - "declared_clear_columns": 486, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 47, - "minimum_structural_cross_section": 9, - "minimum_clear_cross_section": 9, - "minimum_reachable_cross_section": 9, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0, - "centerline_jumps": [], - "endpoints": [ - { - "name": "station-axis:start", - "x": 0, - "z": -37, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 45.5 - }, - { - "name": "station-axis:end", - "x": -6, - "z": -77, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 91.0 - } - ] - }, - { - "id": "south-axis-local", - "passed": true, - "declared_clear_width": 9, - "corridor_columns": 685, - "missing_corridor_columns": [], - "declared_clear_columns": 565, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 59, - "minimum_structural_cross_section": 9, - "minimum_clear_cross_section": 9, - "minimum_reachable_cross_section": 9, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "south-axis-local:start", - "x": 0, - "z": 57, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 48.0 - }, - { - "name": "south-axis-local:end", - "x": 1, - "z": 110, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 102.0 - } - ] - }, - { - "id": "portal-radial-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 531, - "missing_corridor_columns": [], - "declared_clear_columns": 376, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 70, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "portal-radial-local:start", - "x": -35, - "z": -14, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 57.0 - }, - { - "name": "portal-radial-local:end", - "x": -72, - "z": -46, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 126.0 - } - ] - }, - { - "id": "lake-radial-local", - "passed": true, - "declared_clear_width": 5, - "corridor_columns": 403, - "missing_corridor_columns": [], - "declared_clear_columns": 297, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 63, - "minimum_structural_cross_section": 5, - "minimum_clear_cross_section": 5, - "minimum_reachable_cross_section": 5, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "lake-radial-local:start", - "x": -42, - "z": 18, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 50.5 - }, - { - "name": "lake-radial-local:end", - "x": -90, - "z": 32, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 112.5 - } - ] - }, - { - "id": "east-radial-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 451, - "missing_corridor_columns": [], - "declared_clear_columns": 341, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 49, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "east-radial-local:start", - "x": 42, - "z": 16, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 49.0 - }, - { - "name": "east-radial-local:end", - "x": 82, - "z": 8, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 90.5 - } - ] - }, - { - "id": "ring-northwest-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 468, - "missing_corridor_columns": [], - "declared_clear_columns": 350, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 53, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "ring-northwest-local:start", - "x": -40, - "z": -65, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 113.0 - }, - { - "name": "ring-northwest-local:end", - "x": -80, - "z": -77, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 165.0 - } - ] - }, - { - "id": "ring-northeast-local", - "passed": true, - "declared_clear_width": 7, - "corridor_columns": 505, - "missing_corridor_columns": [], - "declared_clear_columns": 374, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 60, - "minimum_structural_cross_section": 7, - "minimum_clear_cross_section": 7, - "minimum_reachable_cross_section": 7, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "ring-northeast-local:start", - "x": 40, - "z": -95, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 151.0 - }, - { - "name": "ring-northeast-local:end", - "x": 82, - "z": -78, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 210.0 - } - ] - }, - { - "id": "station-entrance-stair", - "passed": true, - "declared_clear_width": 21, - "corridor_columns": 161, - "missing_corridor_columns": [], - "declared_clear_columns": 147, - "nonclear_declared_columns": [], - "unreachable_declared_columns": [], - "cross_sections": 7, - "minimum_structural_cross_section": 21, - "minimum_clear_cross_section": 21, - "minimum_reachable_cross_section": 21, - "narrow_cross_sections": [], - "centerline_invalid_steps": [], - "centerline_maximum_boundary_step": 0.5, - "centerline_jumps": [], - "endpoints": [ - { - "name": "station-entrance-stair:start", - "x": -6, - "z": -77, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 91.0 - }, - { - "name": "station-entrance-stair:end", - "x": -6, - "z": -83, - "surface_samples": 4, - "reachable_samples": 4, - "passed": true, - "shortest_surface_route_blocks": 97.0 - } - ] - } - ], - "passed": true, - "final_walkable_mask": "Only columns explicitly covered by the passed actual support/headroom verification", - "non_walkable_perimeter_floor_columns_excluded": 454, - "non_walkable_perimeter_floor_examples": [ - [ - -51, - -145 - ], - [ - -50, - -145 - ], - [ - -49, - -145 - ], - [ - -48, - -145 - ], - [ - -47, - -145 - ], - [ - -46, - -145 - ], - [ - -45, - -145 - ], - [ - -24, - -145 - ], - [ - -23, - -145 - ], - [ - -22, - -145 - ] - ] - }, - "lake-native-toe-verification": { - "checked_points": 3, - "checked_route_edges": 0, - "failures": [], - "passed": true, - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "note": "Observed surface samples and vertical headroom; full cubes, slabs and explicit straight bottom-stair samples. Stair samples are a surface profile, not a full player-width collision simulation. Cached snapshot is not a live re-read.", - "observed_supports": [ - { - "x": -91, - "z": 31, - "y": 83, - "state": "minecraft:white_concrete" - }, - { - "x": -91, - "z": 32, - "y": 83, - "state": "minecraft:grass_block[snowy=false]" - }, - { - "x": -91, - "z": 33, - "y": 83, - "state": "minecraft:grass_block[snowy=false]" - } - ], - "source": "live targeted RPC scan of native shore immediately outside the edit boundary" - } - }, - "archive": "/home/emil/Desktop/Shacraft-Lobby-Archive", - "world_backup": "/home/emil/Desktop/minecraft-builder-mcp/.runtime/projects/shacraft-foundations-01-02-20260913" -} diff --git a/docs/references/shacraft-foundations-west.png b/docs/references/shacraft-foundations-west.png deleted file mode 100644 index b326920..0000000 Binary files a/docs/references/shacraft-foundations-west.png and /dev/null differ diff --git a/docs/references/shacraft-layout-arrival.png b/docs/references/shacraft-layout-arrival.png deleted file mode 100644 index 1306757..0000000 Binary files a/docs/references/shacraft-layout-arrival.png and /dev/null differ diff --git a/docs/references/shacraft-layout-final.png b/docs/references/shacraft-layout-final.png deleted file mode 100644 index 04c1ba1..0000000 Binary files a/docs/references/shacraft-layout-final.png and /dev/null differ diff --git a/docs/references/shacraft-layout-surface.png b/docs/references/shacraft-layout-surface.png deleted file mode 100644 index 06cca13..0000000 Binary files a/docs/references/shacraft-layout-surface.png and /dev/null differ diff --git a/docs/references/shacraft-layout-verification.json b/docs/references/shacraft-layout-verification.json deleted file mode 100644 index 0781a95..0000000 --- a/docs/references/shacraft-layout-verification.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "world": "shacraft_lobby_v2", - "source": "live Paper surface maps + bounded RPC water reads", - "verified_surface_columns": 589824, - "surface_mismatches": 0, - "unique_marker_blocks": 16967, - "planned_writes": 17040, - "original_water_columns": 42399, - "water_columns_obscured_by_markers": 932, - "water_obscured_by_markers_verified_intact": true, - "height_unchanged_columns": 587098, - "capture_started_at": "2026-09-13T01:03:10.136933937Z", - "capture_finished_at": "2026-09-13T01:05:10.066341053Z", - "atomic_snapshot": false, - "note": "World edits were idle during each capture. Terrain follows its original heights; raised markers represent future structures, not finished traversable paths." -} diff --git a/docs/references/shacraft-lobby-generation-report.json b/docs/references/shacraft-lobby-generation-report.json deleted file mode 100644 index c681d68..0000000 --- a/docs/references/shacraft-lobby-generation-report.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "recipe_id": "b013cd330993b262ed0192659c9276af33e1c6fdb749a02ad332086a6d99e56a", - "chunks": 2304, - "mismatches": 0, - "water_columns": 59609, - "min_surface_y": 48, - "max_surface_y": 203, - "elapsed_ms": 144136, - "world": "shacraft_lobby", - "verified_columns": 589824 -} \ No newline at end of file diff --git a/docs/references/shacraft-lobby-world-heightmap.json b/docs/references/shacraft-lobby-world-heightmap.json deleted file mode 100644 index 4423182..0000000 --- a/docs/references/shacraft-lobby-world-heightmap.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "note": "Target surface only; no live world reads. Bounds clip writes, not height calculations. Cache holds 32 recipes until restart; save the recipe to regenerate the same ID.", - "world_verified": false, - "tile_budget": 4096, - "sampled_height_min": 22, - "tile_count": 27648, - "mode": "sculpt", - "status": "completed", - "tile_order": "x_then_z_then_y", - "tile_edge": 16, - "bounds": { - "worldId": "terrain", - "min": { - "x": -384, - "y": 16, - "z": -384 - }, - "max": { - "x": 383, - "y": 207, - "z": 383 - } - }, - "sampled_height_max": 203, - "terrain_id": "b013cd330993b262ed0192659c9276af33e1c6fdb749a02ad332086a6d99e56a", - "sample_grid": { - "height": 256, - "width": 256 - }, - "legend": "Dark green \u003d low; pale stone \u003d high; pink \u003d preserved at sampled surface", - "kind": "terrain_heightmap_preview", - "clipped_samples": 0, - "mimeType": "image/png", - "orientation": "north (-Z) up; east (+X) right" -} \ No newline at end of file diff --git a/docs/references/shacraft-lobby-world-heightmap.png b/docs/references/shacraft-lobby-world-heightmap.png deleted file mode 100644 index a5b899a..0000000 Binary files a/docs/references/shacraft-lobby-world-heightmap.png and /dev/null differ diff --git a/docs/references/shacraft-natural-v2-generation-report.json b/docs/references/shacraft-natural-v2-generation-report.json deleted file mode 100644 index 2eae73c..0000000 --- a/docs/references/shacraft-natural-v2-generation-report.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "elapsed_ms": 274728, - "max_surface_y": 204, - "min_surface_y": 48, - "water_columns": 42399, - "mismatches": 0, - "chunks": 2304, - "recipe_id": "fed10bf28fa544e7dba08d10c76a1130c67292856999b06a9fd672feeed39296", - "verified_columns": 589824, - "world": "shacraft_lobby_v2" -} \ No newline at end of file diff --git a/docs/references/shacraft-natural-v2-overview.png b/docs/references/shacraft-natural-v2-overview.png deleted file mode 100644 index 9bc909a..0000000 Binary files a/docs/references/shacraft-natural-v2-overview.png and /dev/null differ diff --git a/docs/references/shacraft-natural-v2-player-view.png b/docs/references/shacraft-natural-v2-player-view.png deleted file mode 100644 index 5156bf2..0000000 Binary files a/docs/references/shacraft-natural-v2-player-view.png and /dev/null differ diff --git a/docs/references/shacraft-terrain-heightmap.json b/docs/references/shacraft-terrain-heightmap.json deleted file mode 100644 index 4e0ccc9..0000000 --- a/docs/references/shacraft-terrain-heightmap.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "note": "Target surface only; no live world reads. Bounds clip writes, not height calculations. Cache holds 32 recipes until restart; save the recipe to regenerate the same ID.", - "world_verified": false, - "tile_budget": 4096, - "sampled_height_min": -43, - "tile_count": 27648, - "mode": "sculpt", - "status": "completed", - "tile_order": "x_then_z_then_y", - "tile_edge": 16, - "bounds": { - "worldId": "terrain", - "min": { - "x": -384, - "y": -48, - "z": -384 - }, - "max": { - "x": 383, - "y": 143, - "z": 383 - } - }, - "sampled_height_max": 139, - "terrain_id": "5f0c33cd7152806995906c6b9ee51024ddbe90c5e7f6585e653552df2f4aabf8", - "sample_grid": { - "height": 256, - "width": 256 - }, - "legend": "Dark green \u003d low; pale stone \u003d high; pink \u003d preserved at sampled surface", - "kind": "terrain_heightmap_preview", - "clipped_samples": 0, - "mimeType": "image/png", - "orientation": "north (-Z) up; east (+X) right" -} \ No newline at end of file diff --git a/docs/references/shacraft-terrain-heightmap.png b/docs/references/shacraft-terrain-heightmap.png deleted file mode 100644 index 0156800..0000000 Binary files a/docs/references/shacraft-terrain-heightmap.png and /dev/null differ diff --git a/docs/references/shacraft-terrain-study-perspective.png b/docs/references/shacraft-terrain-study-perspective.png deleted file mode 100644 index 43391e9..0000000 Binary files a/docs/references/shacraft-terrain-study-perspective.png and /dev/null differ diff --git a/docs/references/shacraft-terrain-study-plan.png b/docs/references/shacraft-terrain-study-plan.png deleted file mode 100644 index 273e6da..0000000 Binary files a/docs/references/shacraft-terrain-study-plan.png and /dev/null differ diff --git a/docs/references/shacraft-terrain-study.json b/docs/references/shacraft-terrain-study.json deleted file mode 100644 index f4df661..0000000 --- a/docs/references/shacraft-terrain-study.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "world_applied": false, - "water_level": 48, - "fields": [ - { - "name": "V1 \u2014 rectangular platforms", - "min_ground_y": 22.0, - "max_ground_y": 203.0, - "water_columns": 59609, - "slope_p95": 3.0 - }, - { - "name": "Study \u2014 ridges, soft hills, winding water", - "min_ground_y": 31.35810089111328, - "max_ground_y": 204.61903381347656, - "water_columns": 42399, - "slope_p95": 3.5214755985851562 - } - ] -} diff --git a/docs/references/shacraft-terrain-v1-ingame.png b/docs/references/shacraft-terrain-v1-ingame.png deleted file mode 100644 index b217a16..0000000 Binary files a/docs/references/shacraft-terrain-v1-ingame.png and /dev/null differ diff --git a/docs/references/shacraft-zone01-arrival.png b/docs/references/shacraft-zone01-arrival.png deleted file mode 100644 index c4b01d1..0000000 Binary files a/docs/references/shacraft-zone01-arrival.png and /dev/null differ diff --git a/docs/references/shacraft-zone01-containment-verification.json b/docs/references/shacraft-zone01-containment-verification.json deleted file mode 100644 index 2d2d257..0000000 --- a/docs/references/shacraft-zone01-containment-verification.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "version": 1, - "scope": { - "project_id": "shacraft-lobby-v2", - "world_id": "6679385a-b67f-4e88-b6be-33836ced6f2d", - "world_epoch": "b7626ccf-9c75-4b00-9cf3-b8a42231c754" - }, - "world_edits": 0, - "mode": "actual_after_snapshot", - "desired_blocks": 11502, - "interior_columns": 5888, - "independently_derived_wall_columns": 276, - "interior_voxels": 117673, - "interior_excluded_voxels": 87, - "volume_components": { - "components": [ - { - "voxels": 117673, - "nonfull_voxels": 117473, - "contains_source": true - } - ], - "source_in_volume": true, - "note": "Disconnected sealed components are reported, not treated as escapes. The entire required membrane and exterior flood are checked." - }, - "membrane": { - "passed": true, - "required_voxels": 17276, - "parts": { - "wall": 5457, - "floor": 5870, - "folded_boundary": 61, - "roof": 5888 - }, - "observed_materials": { - "barrier": 11396, - "smooth_sandstone": 4080, - "smooth_stone": 447, - "cut_sandstone": 425, - "polished_andesite": 137, - "stone_bricks": 16, - "waxed_oxidized_cut_copper": 12, - "chiseled_stone_bricks": 16, - "grass_block": 377, - "dirt": 12, - "green_concrete": 356, - "gold_block": 2 - }, - "nonfull_voxels": 0, - "examples": [], - "method": "Every voxel in N6(interior volume) minus interior volume must be an observed full collision cube." - }, - "exterior_flood": { - "passed": true, - "observed_voxels": 220584, - "exterior_reached_voxels": 78339, - "interior_reached_from_exterior": 0, - "source_reached_from_exterior": false, - "unknown_or_partial_blocks_treated_as_empty": { - "stone_brick_wall": 293, - "stone_brick_stairs": 42, - "smooth_sandstone_slab": 505, - "iron_bars": 128, - "waxed_oxidized_cut_copper_stairs": 64, - "iron_chain": 16, - "spruce_leaves": 479, - "lantern": 16, - "oxeye_daisy": 60, - "azure_bluet": 39, - "white_tulip": 62, - "oak_leaves": 58, - "allium": 50, - "spruce_fence": 18, - "spruce_stairs": 18, - "pink_tulip": 49, - "smooth_sandstone_stairs": 8, - "waxed_oxidized_cut_copper_slab": 3 - }, - "leak_examples": [], - "method": "Six-neighbor free-voxel exterior flood; only known full collision cubes obstruct it." - }, - "player_probes": { - "passed": true, - "player_width": 0.6, - "player_height": 1.8, - "source_headroom_observed_air": true, - "swept_crossing_probes": { - "diagonal": 3288, - "cardinal": 2280, - "folded_boundary": 178, - "floor": 1, - "roof": 1 - }, - "unblocked_probes": 0, - "examples": [], - "note": "Supplementary swept-AABB boundary probes; the independently derived full membrane is the complete static containment criterion." - }, - "preserved_decor": { - "passed": true, - "protected_observed_states": 1016, - "changed_states": 0, - "examples": [] - }, - "volume": { - "passed": true, - "outside_desired_voxels": 610596, - "desired_voxels": 11502, - "mismatch_examples": [], - "scope_note": "Every voxel inside the supplied before snapshot; no claim for unobserved exterior voxels." - }, - "passed": true, - "limitations": "Static continuous collision containment for normal nonspectator players only. No protection against spectator, breaking/removing blocks, operator commands, plugin teleports or arbitrary discontinuous teleport/pearl behavior. Actual snapshots are sequential, not atomic.", - "input_sha256": { - "before": "9332c546f50b174ec39b01430525eb903c926b43cbdfaa956a539759fbf0f7b2", - "after": "5fdbffc42df7a799e792bf5b31c1c45f5a7e02e09844860898bcdd2f79e199b3", - "layout": "95b430dd0815c8b0d1b87932e9ac1033413f3e82766dd1d4e67aefe4cbaecfa8", - "metadata": "12caa7f14f15128736a0525a765f67a9d9f6b343fc912b36cd41d2e76ab39904" - } -} \ No newline at end of file diff --git a/docs/references/shacraft-zone01-map.png b/docs/references/shacraft-zone01-map.png deleted file mode 100644 index 9aad56f..0000000 Binary files a/docs/references/shacraft-zone01-map.png and /dev/null differ diff --git a/docs/references/shacraft-zone01-overview.png b/docs/references/shacraft-zone01-overview.png deleted file mode 100644 index e6d3ad6..0000000 Binary files a/docs/references/shacraft-zone01-overview.png and /dev/null differ diff --git a/docs/references/shacraft-zone01-welcome.png b/docs/references/shacraft-zone01-welcome.png deleted file mode 100644 index b3e0d17..0000000 Binary files a/docs/references/shacraft-zone01-welcome.png and /dev/null differ diff --git a/docs/references/station-stage07/station-front.capture.json b/docs/references/station-stage07/station-front.capture.json deleted file mode 100644 index 11d8c65..0000000 --- a/docs/references/station-stage07/station-front.capture.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "capturedAt": "2026-09-13T12:33:57.784380033Z", - "dimension": "minecraft:shacraft_lobby_v2", - "x": -6.0, - "y": 113.0, - "z": -24.0, - "eyeY": 114.61999988555908, - "yaw": 180.0, - "pitch": -8.0, - "fov": 95, - "readiness": "local_chunks_and_render_queue_stable", - "serverRevisionVerified": false, - "loadedChunkRadius": 1, - "stabilizationTicks": 40, - "stabilizationFrames": 3, - "status": "completed", - "mimeType": "image/png", - "width": 1280, - "height": 720, - "sourceWidth": 1280, - "sourceHeight": 720, - "captureId": "4a5e4d45-78f8-4f3b-9c2a-a0a665c29fe4", - "imageSha256": "b3f36b132e62a661e8269b405dcc6e3e48ab3c20eb58894e333d2bbf92cbf262", - "imageBytes": 856573, - "elapsedSeconds": 5.063, - "transport": "authenticated Paper HTTP camera_capture", - "testMode": "one owner/spectator client" -} diff --git a/docs/references/station-stage07/station-front.png b/docs/references/station-stage07/station-front.png deleted file mode 100644 index a2aba61..0000000 Binary files a/docs/references/station-stage07/station-front.png and /dev/null differ diff --git a/docs/references/station-stage07/station-overview.capture.json b/docs/references/station-stage07/station-overview.capture.json deleted file mode 100644 index 4e1c059..0000000 --- a/docs/references/station-stage07/station-overview.capture.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "capturedAt": "2026-09-13T12:37:51.019066137Z", - "dimension": "minecraft:shacraft_lobby_v2", - "x": 70.0, - "y": 151.0, - "z": -39.0, - "eyeY": 152.61999988555908, - "yaw": 133.0, - "pitch": 22.0, - "fov": 75, - "readiness": "local_chunks_and_render_queue_stable", - "serverRevisionVerified": false, - "loadedChunkRadius": 1, - "stabilizationTicks": 90, - "stabilizationFrames": 3, - "status": "completed", - "mimeType": "image/png", - "width": 1280, - "height": 720, - "sourceWidth": 1280, - "sourceHeight": 720, - "captureId": "c352f761-9dd9-409a-94f1-fc4fd364d7cc", - "imageSha256": "c06c1fbd498b0376f639e788f142c91bdb0e3509fa7ff3ce5846748311dd444e", - "imageBytes": 1302879, - "elapsedSeconds": 9.867, - "transport": "authenticated Paper HTTP camera_capture", - "testMode": "one owner/spectator client" -} diff --git a/docs/references/station-stage07/station-overview.png b/docs/references/station-stage07/station-overview.png deleted file mode 100644 index 6108016..0000000 Binary files a/docs/references/station-stage07/station-overview.png and /dev/null differ diff --git a/docs/references/station-stage07/station-smash.capture.json b/docs/references/station-stage07/station-smash.capture.json deleted file mode 100644 index 6264c40..0000000 --- a/docs/references/station-stage07/station-smash.capture.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "capturedAt": "2026-09-13T12:37:05.830431206Z", - "dimension": "minecraft:shacraft_lobby_v2", - "x": -46.0, - "y": 119.0, - "z": -104.0, - "eyeY": 120.61999988555908, - "yaw": 225.0, - "pitch": 12.0, - "fov": 85, - "readiness": "local_chunks_and_render_queue_stable", - "serverRevisionVerified": false, - "loadedChunkRadius": 1, - "stabilizationTicks": 40, - "stabilizationFrames": 3, - "status": "completed", - "mimeType": "image/png", - "width": 1280, - "height": 720, - "sourceWidth": 1280, - "sourceHeight": 720, - "captureId": "74fc1c80-5328-4dd4-90c7-4ce53661d814", - "imageSha256": "b5d319d2f216b6d6c8f9496e612a2e0066718c660ccad09fc959fedd9ed14354", - "imageBytes": 1259152, - "elapsedSeconds": 5.135, - "transport": "authenticated Paper HTTP camera_capture", - "testMode": "one owner/spectator client" -} diff --git a/docs/references/station-stage07/station-smash.png b/docs/references/station-stage07/station-smash.png deleted file mode 100644 index a737481..0000000 Binary files a/docs/references/station-stage07/station-smash.png and /dev/null differ diff --git a/docs/references/station-stage07/station-vestibule.capture.json b/docs/references/station-stage07/station-vestibule.capture.json deleted file mode 100644 index 4598cc8..0000000 --- a/docs/references/station-stage07/station-vestibule.capture.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "capturedAt": "2026-09-13T12:29:28.293904723Z", - "dimension": "minecraft:shacraft_lobby_v2", - "x": -5.5, - "y": 100.0, - "z": -91.5, - "eyeY": 101.61999988555908, - "yaw": 180.0, - "pitch": -6.0, - "fov": 85, - "readiness": "local_chunks_and_render_queue_stable", - "serverRevisionVerified": false, - "loadedChunkRadius": 1, - "stabilizationTicks": 150, - "stabilizationFrames": 3, - "status": "completed", - "mimeType": "image/png", - "width": 1280, - "height": 720, - "sourceWidth": 1280, - "sourceHeight": 720, - "captureId": "14496563-40e4-4c8b-a7cc-d111ad178b9d", - "imageSha256": "b420b4b71b22812a4f5bfc9464d1192856dd8d166e66526b579ff6c3e19ea905", - "imageBytes": 1024197, - "elapsedSeconds": 16.526, - "transport": "authenticated Paper HTTP camera_capture", - "testMode": "one owner/spectator client" -} diff --git a/docs/references/station-stage07/station-vestibule.png b/docs/references/station-stage07/station-vestibule.png deleted file mode 100644 index d84a461..0000000 Binary files a/docs/references/station-stage07/station-vestibule.png and /dev/null differ diff --git a/docs/references/zone02-interior-v1/00-coordinate-plan.pdf b/docs/references/zone02-interior-v1/00-coordinate-plan.pdf deleted file mode 100644 index 1389f22..0000000 Binary files a/docs/references/zone02-interior-v1/00-coordinate-plan.pdf and /dev/null differ diff --git a/docs/references/zone02-interior-v1/00-coordinate-plan.png b/docs/references/zone02-interior-v1/00-coordinate-plan.png deleted file mode 100644 index 812f80b..0000000 Binary files a/docs/references/zone02-interior-v1/00-coordinate-plan.png and /dev/null differ diff --git a/docs/references/zone02-interior-v1/01-vestibule.png b/docs/references/zone02-interior-v1/01-vestibule.png deleted file mode 100644 index 74c9f5d..0000000 Binary files a/docs/references/zone02-interior-v1/01-vestibule.png and /dev/null differ diff --git a/docs/references/zone02-interior-v1/02-smash-floor.png b/docs/references/zone02-interior-v1/02-smash-floor.png deleted file mode 100644 index 359c976..0000000 Binary files a/docs/references/zone02-interior-v1/02-smash-floor.png and /dev/null differ diff --git a/docs/references/zone02-interior-v1/03-cutaway.png b/docs/references/zone02-interior-v1/03-cutaway.png deleted file mode 100644 index 0e6fd1a..0000000 Binary files a/docs/references/zone02-interior-v1/03-cutaway.png and /dev/null differ diff --git a/docs/references/zone02-interior-v1/README.md b/docs/references/zone02-interior-v1/README.md deleted file mode 100644 index 2803fa9..0000000 --- a/docs/references/zone02-interior-v1/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Shacraft clock station: vestibule and SMASH - -Design v1, 13 September 2026. **Proposal only; no world edits were made.** - -This design fits the saved, existing zone 02 foundation in `shacraft_lobby_v2`. The current station has a deck and setting-out bands, not a completed above-ground building. Its fixed overall envelope is 115 by 63 blocks; the occupied footprint is irregular. The final stage 02 geometry contains 5,764 station columns, including the east-edge recess. The earlier ideal outline contained 5,823 columns and is not the exact placement authority. - -## Drawings - -- [Coordinate plans and section](00-coordinate-plan.png) · [printable PDF](00-coordinate-plan.pdf) -- [First-floor vestibule](01-vestibule.png) -- [Second-floor SMASH hall](02-smash-floor.png) -- [Clock station cutaway](03-cutaway.png) -- [Exact coordinate specification and checks](layout.json) -- [Full image prompts](prompts.json) - -The three perspective images were generated with the **built-in image_gen tool**. They establish architectural character and atmosphere. The coordinate plan and JSON govern dimensions and placement: generated ornament, block counts, lettering, map thumbnails and roof shapes are illustrative. In particular, the invented slogans in the SMASH image are not approved Shacraft copy. The technical plan was drawn deterministically with Pillow from the saved foundation cells. - -## Spatial arrangement - -The south entrance keeps the existing approach axis at X=-6. The exterior staircase is 21 blocks clear; the entrance and principal approach aisle are seven blocks clear. A green copper and warm brass-colored lift surround is visible from the doorway, with a small directory beside it. Benches and planting occupy the side galleries. Column positions align vertically; pale stone arches and spruce ceiling panels continue the original clock-station reference. - -The first-floor paving remains at block Y98, with feet at Y99. The two-block intermediate deck occupies Y111 and Y112; SMASH is walked at Y113. This gives 12 blocks of structural clear height below the intermediate deck and 11 above it, before the next ceiling begins at Y124. Hanging lamps and decorative beams may reduce local headroom; keep at least four clear blocks over public circulation. The proposed main roof has eaves at Y126 and ridge at Y140; the clock-tower peak is Y158. These are proposed heights, not measurements of an existing shell. - -The lift housing occupies X=-10..-2, Z=-124..-116 on both floors. Two-block walls leave a 5 by 5 clear cabin, with a five-block opening facing south. The suggested eventual behavior is a personal floor selection and transition between enclosed stationary cabins, preventing one player's selection from moving everyone else. Lift controls, animation and world transfers are not implemented by this design. Future floors can reuse the shaft alignment, but their height, roof integration and circulation need a further design pass. - -## SMASH floor - -The user confirmed the Shotbow-style mode: accumulating damage increases knockback, players double-jump and attempt to knock opponents off the arena. See [Shotbow's getting-started guide](https://wiki.shotbow.net/SMASH_Getting_Started). - -Six seven-by-five selection alcoves line the north wall, with three blocks between adjacent alcoves and a seven-block clear aisle in front. Each contains an arena illustration and an ordinary sign at reachable height. Slots 01–06 are **unassigned placeholders**: arena names, destinations and live status are not invented here. Later, each sign can connect to its arena world or queue. - -A 19 by 15 decorative island diorama occupies the western part of the main hall. Its small islands and static figures explain knockback visually. The display sits above a solid floor, behind a stone-and-glass enclosure. It is scenery, not a playable arena or a hole through the building. The west gallery explains damage, knockback and double jumping; the east gallery provides waiting space. Burnt-orange and dark-red accents distinguish SMASH while retaining Shacraft's green copper, cream stone, spruce and warm lighting. - -## Evidence and limits - -`draw-plan.py` checks all specified columns, benches, the lift housing, display and selection bays against a two-block setback inside the saved foundation. It checks for furnishing overlaps and keeps the seven-block entrance aisle and bay-front aisle clear. These are design checks, not a collision test of a constructed building. - -Sources are `.runtime/foundations-stage02/foundations-final.geometry.json` and the saved `zone01-complete.json` surface export; its capture time is recorded in `layout.json`. That export is not an atomic or fresh live scan. It shows only deck, parapet and lamp heights in this footprint. Before building, inspect current voxels again, preserve manual changes, save a backup and implement with conflict-aware batches. Zone 01's existing containment remains in place until a separate, checked opening of the station route. - -For a design rebuild in the main repository, run `python3 docs/references/zone02-interior-v1/draw-plan.py` with Pillow available and the source geometry/export retained. Roof overhangs, arches, window bays and decorative block palettes remain to be detailed during the building pass within the selected region. No additional minigame rooms or playable arenas are included in this stage. diff --git a/docs/references/zone02-interior-v1/SHA256SUMS b/docs/references/zone02-interior-v1/SHA256SUMS deleted file mode 100644 index e02d54c..0000000 --- a/docs/references/zone02-interior-v1/SHA256SUMS +++ /dev/null @@ -1,10 +0,0 @@ -0aa7b7be08876532e84c3567ab1c4ee1fc6a55f8232e6ad47398f7de43505653 00-coordinate-plan.pdf -9b549bd047b54521e6d2edaa3d09bd7c128cb1d31fd1e170b16d0a9b00d93dc3 00-coordinate-plan.png -5ba4142fa4e0cbcc21770e438ca963762c157e242598908c9326f0479b3c3d86 01-vestibule.png -c23c425ffefa5626e56ab83548e1b2c3f8ed38ab7f686d2d00da93e15d86d41b 02-smash-floor.png -cd8232d0c27ea25b60510128ebdca629b8872288e9ee95db6fa919c34ae0273f 03-cutaway.png -37702b2c8337cba6d301d37c7d88151ad930fb6c474954733cfe0771a820a75a README.md -1c90cf98558c079ba3ef3ebd3cd2d8dee17cecc01535875a9ab1f23a99577d03 draw-plan.py -6341286d9f66171788fc06dfa8368d0ce9cc195de398963465a87337dff5c091 generation-sources.json -135b16fcaaab87030cd78bed3129a0edfeb2aaa8de519245e1746879e45d6b83 layout.json -a361e3016e38e95d8dbc865c70d8d256f48f085ac8f1f37c866ec403b445d605 prompts.json diff --git a/docs/references/zone02-interior-v1/draw-plan.py b/docs/references/zone02-interior-v1/draw-plan.py deleted file mode 100644 index 4fb4bab..0000000 --- a/docs/references/zone02-interior-v1/draw-plan.py +++ /dev/null @@ -1,180 +0,0 @@ -"""Render the proposed station layout against the saved, block-exact foundation. - -Read-only design utility; it never connects to or writes the Minecraft world. -All X/Z boxes are inclusive block coordinates. Pillow is required. -""" -import json -from pathlib import Path -from collections import Counter -from PIL import Image, ImageDraw, ImageFont - -OUT = Path(__file__).resolve().parent -ROOT = OUT.parents[2] -SOURCE = ROOT / '.runtime/foundations-stage02/foundations-final.geometry.json' -geometry = json.loads(SOURCE.read_text()) -foot = {(c['x'], c['z']) for c in geometry['cells'] if c['group'] == 'clock-station'} -inner = {p for p in foot if all((p[0]+dx,p[1]+dz) in foot for dx in range(-2,3) for dz in range(-2,3))} -walls = foot-inner - -def cells(box): - a,b,c,d = box - return {(x,z) for x in range(a,b+1) for z in range(c,d+1)} - -core = [-10,-2,-124,-116] -cabin = [-8,-4,-122,-118] -bays = [[x-3,x+3,-140,-136] for x in [-42,-32,-22,-12,-2,8]] -display = [-42,-24,-124,-110] -pillars = [[x,x+1,z,z+1] for x in [-69,-49,-19,17,31] for z in [-127,-104]] -benches = [[x,x+7,z,z+1] for x in [-67,24] for z in [-132,-108]] -features = {'lift housing':core,'lift cabin':cabin,'display':display} -features.update({f'arena bay {i+1}':b for i,b in enumerate(bays)}) -features.update({f'column {i+1}':b for i,b in enumerate(pillars)}) -features.update({f'bench {i+1}':b for i,b in enumerate(benches)}) -for name,box in features.items(): - assert cells(box) <= inner, (name,'outside two-block perimeter setback') -solid_features = [core,display,*bays,*pillars,*benches] -for i,a in enumerate(solid_features): - for b in solid_features[i+1:]: - assert not cells(a)&cells(b), ('furniture overlap',a,b) -route = cells([-9,-3,-115,-85]) -assert route <= foot -assert not route & set().union(*(cells(b) for b in solid_features)) -bay_aisle = cells([-46,11,-135,-129]) -assert bay_aisle <= inner -assert not bay_aisle & set().union(*(cells(b) for b in solid_features)) -map_path = ROOT / '.runtime/server/plugins/ShacraftTerrain/maps/zone01-complete.json' -surface=json.loads(map_path.read_text()) -hist=Counter(surface['surface_y'][(z-surface['min_z'])*surface['width']+x-surface['min_x']] for x,z in foot) -design = { - 'status':'PROPOSAL ONLY. Foundation is surveyed; walls, interiors and upper levels are not built.', - 'world':'shacraft_lobby_v2','coordinate_convention':'X/Z boxes include both endpoints. Floor Y is block Y; walk Y is top of that floor.', - 'foundation_source':str(SOURCE.relative_to(ROOT)), 'surface_capture_finished_at':surface['capture_finished_at'], - 'foundation_columns':len(foot),'foundation_bounds':[-74,40,-145,-83], 'foundation_surface_height_counts':dict(hist), - 'floors':[{'name':'1 Vestibule','floor_block_y':98,'walk_y':99,'ceiling_underside_y':111,'clear_height':12}, - {'name':'2 Smash','floor_block_y':112,'walk_y':113,'ceiling_underside_y':124,'clear_height':11}], - 'intermediate_deck_block_y':[111,112], 'perimeter_wall_thickness':2, - 'main_roof_eaves_y':126,'main_roof_ridge_y':140,'clocktower_peak_y':158, - 'entrance_axis_x':-6,'entrance_opening_x':[-9,-3],'entrance_threshold_z':[-84,-83], - 'lift':{'housing':core,'clear_cabin':cabin,'door_clear_x':[-8,-4],'door_z':[-117,-116],'opens':'south','same_location_on_both_floors':True}, - 'smash_display':display,'smash_selection_bays':bays,'aligned_columns':pillars,'wing_benches':benches, - 'reserved_clear_aisles':{'entry':[-9,-3,-115,-85],'bay_front':[-46,11,-135,-129]}, - 'checks':{'features_inside_two_block_setback':True,'solid_features_do_not_overlap':True,'entry_route_clear_width':7,'bay_front_clear_depth':7}, - 'limitations':['Saved surface export is not a fresh live scan.', 'Containment and overlap checks cover the proposed plan, not constructed voxels.', - 'Generated perspectives are artistic references; this coordinate specification governs placement.', 'Arena sign slots are unassigned, not working destinations.', - 'Future floors need an explicit roof/height design before construction. Only the lift alignment is reserved.']} -(OUT/'layout.json').write_text(json.dumps(design,indent=2)+'\n') - -W,H=2000,1530 -im=Image.new('RGB',(W,H),'#f5f1e7'); d=ImageDraw.Draw(im) -REG='/usr/share/fonts/truetype/ibm-plex/IBMPlexSans-Regular.ttf' -BOLD='/usr/share/fonts/truetype/ibm-plex/IBMPlexSans-SemiBold.ttf' -SERIF='/usr/share/fonts/truetype/ibm-plex/IBMPlexSerif-Regular.ttf' -def text(x,y,s,size=22,color='#203e36',bold=False,anchor=None): - d.text((x,y),s,font=ImageFont.truetype(BOLD if bold else REG,size),fill=color,anchor=anchor) -text(65,35,'SHACRAFT / CLOCK STATION',44,bold=True) -text(65,93,'01 VESTIBULE + 02 SMASH / COORDINATE PLAN / DESIGN v1',22) -text(65,133,'Fixed foundation: 115 x 63 blocks overall. North is up. Both plans use the same 1-block grid.',21) -green='#376956';orange='#bd633b';wall='#58635c';floor='#e8dfcc';gold='#d0aa54' - -def plan(ox,title,upper=False): - oy=255; sc=7 - def xy(x,z): return ox+(x+74)*sc,oy+(z+145)*sc - def box(b,fill,outline=None): - a,c=xy(b[0],b[2]);bb,dd=xy(b[1]+1,b[3]+1) - d.rectangle((a,c,bb-1,dd-1),fill=fill,outline=outline) - def label(x,z,s,size=17,col='#203e36'): - a,b=xy(x,z);text(a,b,s,size,col,anchor='mm') - text(ox,196,title,28,bold=True) - for x,z in foot: box([x,x,z,z], wall if (x,z) in walls else floor) - # Discrete occupied columns avoid polygon boundary ambiguity. - for x,z in inner: - a,b=xy(x,z) - d.line((a,b,a+sc,b),fill='#ddd5c4') - d.line((a,b,a,b+sc),fill='#ddd5c4') - for b in pillars: box(b,'#7b8177') - for b in benches: box(b,'#957753') - box(core,green);box(cabin,'#cbdcd0') - box([-8,-4,-117,-116],'#cbdcd0') - label(-5.5,-120,'L',21) - if upper: - box(display,orange) - label(-32.5,-118,'DISPLAY',16,'#ffffff') - label(-32.5,-114,'19 x 15',16,'#ffffff') - for i,b in enumerate(bays): - box(b,gold);label((b[0]+b[1]+1)/2,-137.5,f'{i+1:02}',16) - label(-17,-132,'7-BLOCK CLEAR AISLE',17) - label(-60,-118,'RULES',17) - label(28,-118,'WAIT',17) - label(-5,-94,'SOUTH GALLERY',17) - else: - label(-58,-118,'WAITING',17) - label(28,-118,'WAITING',17) - label(-34,-117,'VESTIBULE',19) - box([1,7,-113,-111],gold) - label(4,-107,'DIRECTORY',15) - # South doorway through both perimeter wall blocks. - box([-9,-3,-84,-83],floor) - a,b=xy(-5.5,-88);c,e=xy(-5.5,-111) - d.line((a,b,c,e),fill=green,width=4) - d.polygon([(c,e),(c-8,e+14),(c+8,e+14)],fill=green) - label(-5.5,-97,'7 wide',15) - # Outside dimensions and orientation. - text(ox+402,oy-21,'115 blocks',19,anchor='mm') - d.line((ox,oy-8,ox+805,oy-8),fill=wall,width=2) - for x in [ox,ox+805]: d.line((x,oy-14,x,oy-2),fill=wall,width=2) - text(ox+842,oy+213,'63',21,anchor='mm');text(ox+842,oy+240,'blocks',16,anchor='mm') - d.line((ox+817,oy,ox+817,oy+441),fill=wall,width=2) - for z in [-145,-125,-105,-83]: - a,b=xy(-74,z);text(a-12,b,str(z),15,anchor='rm') - for x in [-74,-50,-25,0,40]: - a,b=xy(x,-82);text(a,b+18,str(x),16,anchor='mm') - text(ox+23,oy+398,'N',23,bold=True) - d.line((ox+31,oy+397,ox+31,oy+358),fill=green,width=3) - d.polygon([(ox+31,oy+352),(ox+23,oy+366),(ox+39,oy+366)],fill=green) - text(ox,oy+491,'X: east-west / Z: north-south / unit: one block',18) - -plan(105,'01 / VESTIBULE • WALK Y99') -plan(1100,'02 / SMASH • WALK Y113',True) -d.line((65,792,1935,792),fill='#bdb8aa',width=2) -text(65,825,'SECTION / ENTRANCE AXIS X = -6',27,bold=True) -text(1060,825,'DESIGN RULES',27,bold=True) -# North at left, south at right; Y shown as world surface elevations. -sx,sy,scale=105,1200,11 -def section_box(z0,z1,y0,y1,fill): - d.rectangle((sx+(z0+145)*scale,sy-(y1-98)*scale,sx+(z1+145)*scale-1,sy-(y0-98)*scale-1),fill=fill) -section_box(-145,-82,98,99,wall) -section_box(-145,-82,111,113,wall) -section_box(-145,-82,124,126,wall) -section_box(-145,-143,99,124,wall) -section_box(-84,-82,99,124,wall) -section_box(-84,-82,99,105,'#f5f1e7') -for y in [99,113]: - top=111 if y==99 else 124 - section_box(-124,-122,y,top,green) - section_box(-118,-116,y+6,top,green) - section_box(-122,-118,y,y+0.25,gold) -for y,lab in [(99,'Y99 / level 1'),(111,'Y111 / ceiling'),(113,'Y113 / level 2'),(124,'Y124 / ceiling')]: - yy=sy-(y-98)*scale - d.line((sx-12,yy,sx+720,yy),fill='#b4afa2',width=1) - text(sx+735,yy,lab,18,anchor='lm') -text(455,1072,'12 clear',23,anchor='mm') -text(455,924,'11 clear',23,anchor='mm') -text(105,1234,'NORTH',18);text(735,1234,'SOUTH / ENTRY',18) -notes=[ - 'Fixed footprint; 2-block perimeter walls.', - 'Lift: 9 x 9 housing; 5 x 5 clear cabin.', - 'Same shaft and south-facing door on both floors.', - 'Six 7 x 5 sign bays; destinations remain unassigned.', - 'SMASH display is scenery on a solid, enclosed floor.', - 'Gameplay takes place in separate arena worlds.', - 'Future stories are not furnished or dimensioned here.', - 'Concept images illustrate style; layout.json governs placement.' -] -for i,s in enumerate(notes): text(1060,878+i*43,s,21) -text(65,1320,'VERIFIED AGAINST THE SAVED FOUNDATION',22,bold=True) -text(65,1358,f'{len(foot):,} foundation columns • all specified furnishings inside the wall setback • no furnishing overlaps',21) -text(65,1394,'This is a design drawing, not a live scan or a completed building. Above-ground shell and floors are proposed.',21) -text(65,1452,'SOURCE: foundations-final.geometry.json + zone01-complete surface export / 13 SEP 2026',18) -im.save(OUT/'00-coordinate-plan.png') -im.save(OUT/'00-coordinate-plan.pdf',resolution=150) -print(json.dumps({'saved':str(OUT),'foundation_columns':len(foot),'height_counts':dict(hist),'checks':'passed'})) diff --git a/docs/references/zone02-interior-v1/generation-sources.json b/docs/references/zone02-interior-v1/generation-sources.json deleted file mode 100644 index 9c3cd04..0000000 --- a/docs/references/zone02-interior-v1/generation-sources.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "01-vestibule": "/home/emil/.codex/generated_images/01a096c0-89c4-73b3-b5c7-69c847aebebf/exec-20fd3d79-6c5c-47f8-b312-d0a557f7ca01.png", - "02-smash-floor": "/home/emil/.codex/generated_images/01a096c0-89c4-73b3-b5c7-69c847aebebf/exec-65601ecd-0dd8-46fd-bb8c-7a5a1793cc52.png", - "03-cutaway": "/home/emil/.codex/generated_images/01a096c0-89c4-73b3-b5c7-69c847aebebf/exec-875ff00a-540a-41e2-bf0c-b26fac132054.png" -} diff --git a/docs/references/zone02-interior-v1/layout.json b/docs/references/zone02-interior-v1/layout.json deleted file mode 100644 index b4e6c8d..0000000 --- a/docs/references/zone02-interior-v1/layout.json +++ /dev/null @@ -1,235 +0,0 @@ -{ - "status": "PROPOSAL ONLY. Foundation is surveyed; walls, interiors and upper levels are not built.", - "world": "shacraft_lobby_v2", - "coordinate_convention": "X/Z boxes include both endpoints. Floor Y is block Y; walk Y is top of that floor.", - "foundation_source": ".runtime/foundations-stage02/foundations-final.geometry.json", - "surface_capture_finished_at": "2026-09-13T11:16:27.647422004Z", - "foundation_columns": 5764, - "foundation_bounds": [ - -74, - 40, - -145, - -83 - ], - "foundation_surface_height_counts": { - "98": 5500, - "99": 252, - "102": 12 - }, - "floors": [ - { - "name": "1 Vestibule", - "floor_block_y": 98, - "walk_y": 99, - "ceiling_underside_y": 111, - "clear_height": 12 - }, - { - "name": "2 Smash", - "floor_block_y": 112, - "walk_y": 113, - "ceiling_underside_y": 124, - "clear_height": 11 - } - ], - "intermediate_deck_block_y": [ - 111, - 112 - ], - "perimeter_wall_thickness": 2, - "main_roof_eaves_y": 126, - "main_roof_ridge_y": 140, - "clocktower_peak_y": 158, - "entrance_axis_x": -6, - "entrance_opening_x": [ - -9, - -3 - ], - "entrance_threshold_z": [ - -84, - -83 - ], - "lift": { - "housing": [ - -10, - -2, - -124, - -116 - ], - "clear_cabin": [ - -8, - -4, - -122, - -118 - ], - "door_clear_x": [ - -8, - -4 - ], - "door_z": [ - -117, - -116 - ], - "opens": "south", - "same_location_on_both_floors": true - }, - "smash_display": [ - -42, - -24, - -124, - -110 - ], - "smash_selection_bays": [ - [ - -45, - -39, - -140, - -136 - ], - [ - -35, - -29, - -140, - -136 - ], - [ - -25, - -19, - -140, - -136 - ], - [ - -15, - -9, - -140, - -136 - ], - [ - -5, - 1, - -140, - -136 - ], - [ - 5, - 11, - -140, - -136 - ] - ], - "aligned_columns": [ - [ - -69, - -68, - -127, - -126 - ], - [ - -69, - -68, - -104, - -103 - ], - [ - -49, - -48, - -127, - -126 - ], - [ - -49, - -48, - -104, - -103 - ], - [ - -19, - -18, - -127, - -126 - ], - [ - -19, - -18, - -104, - -103 - ], - [ - 17, - 18, - -127, - -126 - ], - [ - 17, - 18, - -104, - -103 - ], - [ - 31, - 32, - -127, - -126 - ], - [ - 31, - 32, - -104, - -103 - ] - ], - "wing_benches": [ - [ - -67, - -60, - -132, - -131 - ], - [ - -67, - -60, - -108, - -107 - ], - [ - 24, - 31, - -132, - -131 - ], - [ - 24, - 31, - -108, - -107 - ] - ], - "reserved_clear_aisles": { - "entry": [ - -9, - -3, - -115, - -85 - ], - "bay_front": [ - -46, - 11, - -135, - -129 - ] - }, - "checks": { - "features_inside_two_block_setback": true, - "solid_features_do_not_overlap": true, - "entry_route_clear_width": 7, - "bay_front_clear_depth": 7 - }, - "limitations": [ - "Saved surface export is not a fresh live scan.", - "Containment and overlap checks cover the proposed plan, not constructed voxels.", - "Generated perspectives are artistic references; this coordinate specification governs placement.", - "Arena sign slots are unassigned, not working destinations.", - "Future floors need an explicit roof/height design before construction. Only the lift alignment is reserved." - ] -} diff --git a/docs/references/zone02-interior-v1/prompts.json b/docs/references/zone02-interior-v1/prompts.json deleted file mode 100644 index b584a00..0000000 --- a/docs/references/zone02-interior-v1/prompts.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mode": "built-in image_gen", - "prompts": { - "01-vestibule": "Use case: stylized-concept.\nAsset type: architectural Minecraft building reference for SHACRAFT, design proposal, not a game screenshot.\nProject: Alpine clock station / minigame selection hub on an already built irregular foundation. All geometry must look buildable using Minecraft blocks, stairs, slabs, fences and panes. Pale sandstone and cream masonry, dark stone plinths, spruce, oxidized green copper, muted brass, warm lanterns, forest-green SHACRAFT banners with a simple gold S. Restrained excellent craftsmanship, architectural clarity, no visual clutter. No sci-fi, no neon holograms, no modern shopping mall. Tiny Minecraft players only as 2-block height scale figures.\nHard spatial brief: foundation east-west 115 blocks overall and north-south 63 blocks maximum. Broad main hall 68 blocks wide by 48 deep, west pavilion 23 wide and east pavilion24 wide, both42 deep. A south entrance projection39 wide by16 deep is offset 11 blocks east of the whole building center. Entrance faces south and has a21-block-wide external staircase rising3 blocks. First floor feetY99, next floor feetY113; a two-block deck atY111..112 gives12 blocks of first-floor clear height. Second floor has11 blocks clear height, next ceiling beginsY124. Lift housing9x9 blocks with usable cabin5x5, on entrance axis x=-6, z=-120, opens south on BOTH floors. Interior rooms have2-block-thick perimeter walls and believable aligned masonry columns. Main ridgeY140; clocktower peakY158. Only two floors are furnished. Future floors remain a design reservation, no extra themed rooms.\n\nPrimary request: Draw ONE large, beautiful interior architectural perspective of the FIRST-FLOOR VESTIBULE, landscape16:9. Camera from south entrance facing north, eye height about3 blocks above floor, moderate wide-angle with straight credible verticals. In the center background, clearly visible forest-green/brass lift doorway9 blocks wide with5-block-wide opening, housed in a square structural core. A long7-block-clear cream stone aisle goes directly from entrance to lift. Immediately beside lift a small readable directory says only \"1 LOBBY\" and \"2 SMASH\". Show a substantial ceiling12 blocks above floor, ribbed masonry arches and deep spruce ceiling panels, no giant open roof or infinite atrium. Side aisles lead left and right into quieter waiting galleries with spruce benches, planters in stone urns and warm lamps. Floor has a restrained forest-green geometric compass mosaic OFF the straight route; do not block path with statue or fountain. Foreground side corners frame pale stone door jambs. Warm daylight through tall side windows. Keep actual usable scale of main hall, the view shows a section of its width rather than a falsely narrow corridor. Small centered title in a slim cream margin: \"SHACRAFT / 01 VESTIBULE\". Beautiful plausible Minecraft block design, not a real-world cathedral.", - "02-smash-floor": "Use case: stylized-concept.\nAsset type: architectural Minecraft building reference for SHACRAFT, design proposal, not a game screenshot.\nProject: Alpine clock station / minigame selection hub on an already built irregular foundation. All geometry must look buildable using Minecraft blocks, stairs, slabs, fences and panes. Pale sandstone and cream masonry, dark stone plinths, spruce, oxidized green copper, muted brass, warm lanterns, forest-green SHACRAFT banners with a simple gold S. Restrained excellent craftsmanship, architectural clarity, no visual clutter. No sci-fi, no neon holograms, no modern shopping mall. Tiny Minecraft players only as 2-block height scale figures.\nHard spatial brief: foundation east-west 115 blocks overall and north-south 63 blocks maximum. Broad main hall 68 blocks wide by 48 deep, west pavilion 23 wide and east pavilion24 wide, both42 deep. A south entrance projection39 wide by16 deep is offset 11 blocks east of the whole building center. Entrance faces south and has a21-block-wide external staircase rising3 blocks. First floor feetY99, next floor feetY113; a two-block deck atY111..112 gives12 blocks of first-floor clear height. Second floor has11 blocks clear height, next ceiling beginsY124. Lift housing9x9 blocks with usable cabin5x5, on entrance axis x=-6, z=-120, opens south on BOTH floors. Interior rooms have2-block-thick perimeter walls and believable aligned masonry columns. Main ridgeY140; clocktower peakY158. Only two floors are furnished. Future floors remain a design reservation, no extra themed rooms.\n\nPrimary request: Draw ONE large, beautiful interior architectural perspective of the SECOND-FLOOR SMASH SELECTION HALL, landscape16:9. Camera near south-east part of main hall looking north-west. Include the green/brass9x9 lift core on right, its opening on its south face visible obliquely, with clear sign \"1 LOBBY\". Left foreground is a19x15-block enclosed decorative display on an intact solid floor: three small voxel floating rock islands mounted over a dark inset plinth, two static small Minecraft figures, one knocked away on a restrained gold block motion arc. This is a MUSEUM DIORAMA with low glass-and-stone protective railing, not an actual combat arena, no lethal pit, no broken public floor. A seven-block-clear aisle passes around display.\nAlong north wall in background EXACTLY SIX recessed selection bays, each7 blocks wide, separated by3 blocks; they have spruce Minecraft signboards at player reach height with a small thumbnail map plaque above and numbered headings \"01\", \"02\", \"03\", \"04\", \"05\", \"06\". These are placeholders for future arenas, no invented live player counts or glowing portals. A large tasteful block-built \"SMASH\" header above back bays. West wall small visual plaques illustrate damage, knockback, double jump using voxel pictograms. SMASH accent colors burnt orange and dark red banners, balanced with Shacraft green. Keep the same cream stone columns, spruce, green copper and brass as vestibule. Ceiling is11 blocks above floor, visible solid ceiling and rafters, not infinitely tall. Daylight, warm readable practical space. Title in slim cream margin: \"SHACRAFT / 02 SMASH\".", - "03-cutaway": "Use case: stylized-concept.\nAsset type: architectural Minecraft building reference for SHACRAFT, design proposal, not a game screenshot.\nProject: Alpine clock station / minigame selection hub on an already built irregular foundation. All geometry must look buildable using Minecraft blocks, stairs, slabs, fences and panes. Pale sandstone and cream masonry, dark stone plinths, spruce, oxidized green copper, muted brass, warm lanterns, forest-green SHACRAFT banners with a simple gold S. Restrained excellent craftsmanship, architectural clarity, no visual clutter. No sci-fi, no neon holograms, no modern shopping mall. Tiny Minecraft players only as 2-block height scale figures.\nHard spatial brief: foundation east-west 115 blocks overall and north-south 63 blocks maximum. Broad main hall 68 blocks wide by 48 deep, west pavilion 23 wide and east pavilion24 wide, both42 deep. A south entrance projection39 wide by16 deep is offset 11 blocks east of the whole building center. Entrance faces south and has a21-block-wide external staircase rising3 blocks. First floor feetY99, next floor feetY113; a two-block deck atY111..112 gives12 blocks of first-floor clear height. Second floor has11 blocks clear height, next ceiling beginsY124. Lift housing9x9 blocks with usable cabin5x5, on entrance axis x=-6, z=-120, opens south on BOTH floors. Interior rooms have2-block-thick perimeter walls and believable aligned masonry columns. Main ridgeY140; clocktower peakY158. Only two floors are furnished. Future floors remain a design reservation, no extra themed rooms.\n\nPrimary request: Create ONE polished landscape architectural cutaway axonometric reference sheet, wide3:2 format, ivory background, thin dark green labels. A dominant three-quarter view looking from south-east shows the WHOLE clock station, broad horizontal wings, original grand green copper roofs, prominent slightly off-center front clock tower. Remove front and right portions of wall and selected roof to reveal EXACTLY TWO furnished stacked floors without concealing their connection. Ground floor is a cream stone vestibule with central arrival aisle, benches in wings, square green/brass lift core at rear of arrival aisle. Upper floor directly above has same aligned core, a small contained floating-island display in left half, six signboard alcoves along rear wall. Floor slab between them clearly shown, consistent14-block floor-to-floor spacing. Remaining roofs are translucent linework only where needed. Upper clock tower contains clockwork silhouette, not extra furnished game rooms. Foundation shape must remain broad main hall plus end pavilions, shallow rear mainhall projection and south projecting tower entrance, not a rectangular skyscraper. No extra buildings or landscape.\nAdd three neat callouts only: \"01 VESTIBULE / WALK Y99\", \"02 SMASH / WALK Y113\", \"LIFT / 5 x 5 CLEAR\". Add small title \"SHACRAFT / CLOCK STATION\" and subtitle \"Two-floor interior concept\". Add note at bottom \"Concept view - coordinate plan governs dimensions\". Strong visually legible separation of floors, genuine Minecraft voxel scale, warm copper and stone, refined presentation, no dense fake dimensions." - } -} diff --git a/examples/layout/shacraft-access.json b/examples/layout/shacraft-access.json deleted file mode 100644 index 3e2ea4a..0000000 --- a/examples/layout/shacraft-access.json +++ /dev/null @@ -1,2891 +0,0 @@ -{ - "schema": "shacraft-layout-access-supplement-v1", - "world": "shacraft_lobby_v2", - "base_plan_unchanged": true, - "routes": [ - { - "id": "boardwalk-north-stairs", - "name": "Portal court / boardwalk stair", - "waypoints": [ - [ - -167, - -82 - ], - [ - -172, - -77 - ], - [ - -177, - -69 - ] - ], - "points": [ - [ - -167, - -82 - ], - [ - -168, - -81 - ], - [ - -169, - -80 - ], - [ - -170, - -80 - ], - [ - -170, - -79 - ], - [ - -171, - -78 - ], - [ - -172, - -77 - ], - [ - -172, - -76 - ], - [ - -173, - -75 - ], - [ - -174, - -75 - ], - [ - -174, - -74 - ], - [ - -174, - -73 - ], - [ - -175, - -72 - ], - [ - -176, - -71 - ], - [ - -176, - -70 - ], - [ - -177, - -69 - ] - ], - "width": 3, - "role": "stairs", - "minimum_marker_y": 51, - "design_intent": "Reserve descending stair flight and water landing; never replace water.", - "labels": [ - { - "point": [ - -167, - -82 - ], - "text": "LAKE BOARDWALK / FUTURE STAIRS" - } - ], - "analysis": { - "ground_min": 43.6, - "ground_max": 56.7, - "water_samples": 5, - "length": 18.3 - }, - "marker_y_by_point": [ - 56, - 55, - 54, - 53, - 53, - 52, - 51, - 51, - 51, - 51, - 51, - 51, - 51, - 51, - 51, - 51 - ] - }, - { - "id": "boardwalk-south-stairs", - "name": "Waterworks / boardwalk stair", - "waypoints": [ - [ - -145, - 70 - ], - [ - -158, - 72 - ], - [ - -173, - 74 - ], - [ - -189, - 75 - ] - ], - "points": [ - [ - -145, - 70 - ], - [ - -146, - 70 - ], - [ - -147, - 70 - ], - [ - -148, - 70 - ], - [ - -149, - 71 - ], - [ - -150, - 71 - ], - [ - -151, - 71 - ], - [ - -152, - 71 - ], - [ - -153, - 71 - ], - [ - -154, - 71 - ], - [ - -155, - 72 - ], - [ - -156, - 72 - ], - [ - -157, - 72 - ], - [ - -158, - 72 - ], - [ - -159, - 72 - ], - [ - -160, - 72 - ], - [ - -161, - 72 - ], - [ - -162, - 72 - ], - [ - -163, - 73 - ], - [ - -164, - 73 - ], - [ - -165, - 73 - ], - [ - -166, - 73 - ], - [ - -167, - 73 - ], - [ - -168, - 73 - ], - [ - -169, - 74 - ], - [ - -170, - 74 - ], - [ - -171, - 74 - ], - [ - -172, - 74 - ], - [ - -173, - 74 - ], - [ - -174, - 74 - ], - [ - -175, - 74 - ], - [ - -176, - 74 - ], - [ - -177, - 74 - ], - [ - -178, - 74 - ], - [ - -179, - 74 - ], - [ - -180, - 74 - ], - [ - -181, - 74 - ], - [ - -181, - 75 - ], - [ - -182, - 75 - ], - [ - -183, - 75 - ], - [ - -184, - 75 - ], - [ - -185, - 75 - ], - [ - -186, - 75 - ], - [ - -187, - 75 - ], - [ - -188, - 75 - ], - [ - -189, - 75 - ] - ], - "width": 3, - "role": "stairs", - "minimum_marker_y": 51, - "design_intent": "Reserve an approximately 45-block descent from the viewing terrace to the Y51 boardwalk.", - "labels": [ - { - "point": [ - -145, - 70 - ], - "text": "BOARDWALK / FUTURE STAIRS" - } - ], - "analysis": { - "ground_min": 45.7, - "ground_max": 69.2, - "water_samples": 4, - "length": 46.7 - }, - "marker_y_by_point": [ - 69, - 68, - 68, - 68, - 67, - 67, - 67, - 66, - 66, - 65, - 65, - 65, - 64, - 63, - 63, - 62, - 61, - 61, - 61, - 60, - 59, - 59, - 59, - 58, - 58, - 58, - 57, - 56, - 56, - 55, - 55, - 54, - 54, - 53, - 52, - 52, - 51, - 51, - 51, - 51, - 51, - 51, - 51, - 51, - 51, - 51 - ] - }, - { - "id": "market-door-1", - "name": "Bakery entrance", - "waypoints": [ - [ - -207, - 205 - ], - [ - -211, - 202 - ], - [ - -218, - 197 - ] - ], - "points": [ - [ - -207, - 205 - ], - [ - -208, - 204 - ], - [ - -209, - 204 - ], - [ - -209, - 203 - ], - [ - -210, - 203 - ], - [ - -211, - 202 - ], - [ - -212, - 201 - ], - [ - -213, - 201 - ], - [ - -213, - 200 - ], - [ - -214, - 200 - ], - [ - -215, - 199 - ], - [ - -216, - 199 - ], - [ - -216, - 198 - ], - [ - -217, - 198 - ], - [ - -218, - 197 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "Public square boundary to the shop doorway; a reservation, not a complete street.", - "analysis": { - "ground_min": 69.1, - "ground_max": 72.4, - "water_samples": 0, - "length": 16.1 - } - }, - { - "id": "market-door-2", - "name": "Crafts hall entrance", - "waypoints": [ - [ - -182, - 204 - ], - [ - -178, - 204 - ] - ], - "points": [ - [ - -182, - 204 - ], - [ - -181, - 204 - ], - [ - -180, - 204 - ], - [ - -179, - 204 - ], - [ - -178, - 204 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "Public square boundary to the shop doorway; a reservation, not a complete street.", - "analysis": { - "ground_min": 66.1, - "ground_max": 66.8, - "water_samples": 0, - "length": 4.0 - } - }, - { - "id": "market-door-3", - "name": "Tea house entrance", - "waypoints": [ - [ - -173, - 230 - ], - [ - -170, - 230 - ] - ], - "points": [ - [ - -173, - 230 - ], - [ - -172, - 230 - ], - [ - -171, - 230 - ], - [ - -170, - 230 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "Public square boundary to the shop doorway; a reservation, not a complete street.", - "analysis": { - "ground_min": 63.8, - "ground_max": 64.0, - "water_samples": 0, - "length": 3.0 - } - }, - { - "id": "market-door-4", - "name": "Guild shop entrance", - "waypoints": [ - [ - -186, - 248 - ], - [ - -181, - 256 - ], - [ - -179, - 263 - ] - ], - "points": [ - [ - -186, - 248 - ], - [ - -186, - 249 - ], - [ - -185, - 250 - ], - [ - -184, - 250 - ], - [ - -184, - 251 - ], - [ - -184, - 252 - ], - [ - -183, - 253 - ], - [ - -182, - 254 - ], - [ - -182, - 255 - ], - [ - -181, - 256 - ], - [ - -181, - 257 - ], - [ - -180, - 258 - ], - [ - -180, - 259 - ], - [ - -180, - 260 - ], - [ - -180, - 261 - ], - [ - -179, - 262 - ], - [ - -179, - 263 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "Public square boundary to the shop doorway; a reservation, not a complete street.", - "analysis": { - "ground_min": 62.0, - "ground_max": 62.9, - "water_samples": 0, - "length": 18.5 - } - }, - { - "id": "market-door-5", - "name": "Workshop entrance", - "waypoints": [ - [ - -205, - 247 - ], - [ - -209, - 255 - ], - [ - -213, - 264 - ] - ], - "points": [ - [ - -205, - 247 - ], - [ - -205, - 248 - ], - [ - -206, - 249 - ], - [ - -206, - 250 - ], - [ - -207, - 251 - ], - [ - -208, - 252 - ], - [ - -208, - 253 - ], - [ - -209, - 254 - ], - [ - -209, - 255 - ], - [ - -209, - 256 - ], - [ - -210, - 257 - ], - [ - -210, - 258 - ], - [ - -211, - 259 - ], - [ - -211, - 260 - ], - [ - -212, - 261 - ], - [ - -212, - 262 - ], - [ - -213, - 263 - ], - [ - -213, - 264 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "Public square boundary to the shop doorway; a reservation, not a complete street.", - "analysis": { - "ground_min": 61.2, - "ground_max": 62.3, - "water_samples": 0, - "length": 20.3 - } - }, - { - "id": "market-door-6", - "name": "Guild hall entrance", - "waypoints": [ - [ - -214, - 237 - ], - [ - -218, - 238 - ], - [ - -222, - 238 - ] - ], - "points": [ - [ - -214, - 237 - ], - [ - -215, - 237 - ], - [ - -216, - 237 - ], - [ - -216, - 238 - ], - [ - -217, - 238 - ], - [ - -218, - 238 - ], - [ - -219, - 238 - ], - [ - -220, - 238 - ], - [ - -221, - 238 - ], - [ - -222, - 238 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "Public square boundary to the shop doorway; a reservation, not a complete street.", - "analysis": { - "ground_min": 63.4, - "ground_max": 64.6, - "water_samples": 0, - "length": 9.0 - } - }, - { - "id": "palm-garden-main-arc", - "name": "Palm courtyard main promenade", - "waypoints": [ - [ - 185, - -9 - ], - [ - 185, - -6 - ], - [ - 187, - -6 - ], - [ - 188, - -6 - ], - [ - 190, - -6 - ], - [ - 191, - -5 - ], - [ - 192, - -5 - ], - [ - 194, - -5 - ], - [ - 195, - -4 - ], - [ - 197, - -4 - ], - [ - 198, - -3 - ], - [ - 199, - -2 - ], - [ - 201, - -1 - ], - [ - 202, - -1 - ], - [ - 203, - 0 - ], - [ - 204, - 1 - ], - [ - 205, - 2 - ], - [ - 206, - 3 - ], - [ - 207, - 5 - ], - [ - 208, - 6 - ], - [ - 209, - 7 - ], - [ - 210, - 8 - ], - [ - 211, - 10 - ], - [ - 211, - 11 - ], - [ - 212, - 12 - ], - [ - 213, - 14 - ], - [ - 213, - 15 - ], - [ - 213, - 17 - ], - [ - 214, - 18 - ], - [ - 214, - 20 - ], - [ - 214, - 21 - ], - [ - 214, - 23 - ], - [ - 214, - 24 - ], - [ - 214, - 26 - ], - [ - 214, - 27 - ], - [ - 213, - 29 - ], - [ - 213, - 30 - ], - [ - 213, - 32 - ], - [ - 212, - 33 - ], - [ - 212, - 34 - ], - [ - 211, - 36 - ], - [ - 210, - 37 - ], - [ - 210, - 38 - ], - [ - 209, - 40 - ], - [ - 208, - 41 - ], - [ - 207, - 42 - ], - [ - 206, - 43 - ], - [ - 205, - 44 - ], - [ - 204, - 45 - ], - [ - 202, - 46 - ], - [ - 201, - 47 - ], - [ - 201, - 46 - ] - ], - "points": [ - [ - 185, - -9 - ], - [ - 185, - -8 - ], - [ - 185, - -7 - ], - [ - 185, - -6 - ], - [ - 186, - -6 - ], - [ - 187, - -6 - ], - [ - 188, - -6 - ], - [ - 189, - -6 - ], - [ - 190, - -6 - ], - [ - 191, - -5 - ], - [ - 192, - -5 - ], - [ - 193, - -5 - ], - [ - 194, - -5 - ], - [ - 194, - -4 - ], - [ - 195, - -4 - ], - [ - 196, - -4 - ], - [ - 197, - -4 - ], - [ - 198, - -4 - ], - [ - 198, - -3 - ], - [ - 198, - -2 - ], - [ - 199, - -2 - ], - [ - 200, - -2 - ], - [ - 200, - -1 - ], - [ - 201, - -1 - ], - [ - 202, - -1 - ], - [ - 202, - 0 - ], - [ - 203, - 0 - ], - [ - 204, - 0 - ], - [ - 204, - 1 - ], - [ - 204, - 2 - ], - [ - 205, - 2 - ], - [ - 206, - 2 - ], - [ - 206, - 3 - ], - [ - 206, - 4 - ], - [ - 207, - 4 - ], - [ - 207, - 5 - ], - [ - 208, - 6 - ], - [ - 209, - 7 - ], - [ - 210, - 8 - ], - [ - 210, - 9 - ], - [ - 211, - 9 - ], - [ - 211, - 10 - ], - [ - 211, - 11 - ], - [ - 212, - 12 - ], - [ - 212, - 13 - ], - [ - 213, - 13 - ], - [ - 213, - 14 - ], - [ - 213, - 15 - ], - [ - 213, - 16 - ], - [ - 213, - 17 - ], - [ - 214, - 18 - ], - [ - 214, - 19 - ], - [ - 214, - 20 - ], - [ - 214, - 21 - ], - [ - 214, - 22 - ], - [ - 214, - 23 - ], - [ - 214, - 24 - ], - [ - 214, - 25 - ], - [ - 214, - 26 - ], - [ - 214, - 27 - ], - [ - 214, - 28 - ], - [ - 213, - 28 - ], - [ - 213, - 29 - ], - [ - 213, - 30 - ], - [ - 213, - 31 - ], - [ - 213, - 32 - ], - [ - 212, - 32 - ], - [ - 212, - 33 - ], - [ - 212, - 34 - ], - [ - 212, - 35 - ], - [ - 211, - 35 - ], - [ - 211, - 36 - ], - [ - 210, - 36 - ], - [ - 210, - 37 - ], - [ - 210, - 38 - ], - [ - 210, - 39 - ], - [ - 209, - 39 - ], - [ - 209, - 40 - ], - [ - 208, - 40 - ], - [ - 208, - 41 - ], - [ - 208, - 42 - ], - [ - 207, - 42 - ], - [ - 206, - 42 - ], - [ - 206, - 43 - ], - [ - 206, - 44 - ], - [ - 205, - 44 - ], - [ - 204, - 44 - ], - [ - 204, - 45 - ], - [ - 203, - 45 - ], - [ - 203, - 46 - ], - [ - 202, - 46 - ], - [ - 201, - 47 - ], - [ - 201, - 46 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "Connect harbor and southern garden promenade around the outside of the glasshouse.", - "analysis": { - "ground_min": 78.1, - "ground_max": 85.1, - "water_samples": 0, - "length": 94.9 - } - }, - { - "id": "palm-garden-west-arc", - "name": "Palm courtyard west approach", - "waypoints": [ - [ - 160, - 23 - ], - [ - 156, - 23 - ], - [ - 156, - 22 - ], - [ - 156, - 21 - ], - [ - 156, - 20 - ], - [ - 156, - 19 - ], - [ - 156, - 18 - ], - [ - 157, - 17 - ], - [ - 157, - 17 - ], - [ - 157, - 16 - ], - [ - 157, - 15 - ], - [ - 157, - 14 - ], - [ - 158, - 13 - ], - [ - 158, - 12 - ], - [ - 158, - 11 - ], - [ - 159, - 10 - ], - [ - 159, - 10 - ], - [ - 160, - 9 - ], - [ - 160, - 8 - ], - [ - 161, - 7 - ], - [ - 161, - 6 - ], - [ - 162, - 6 - ], - [ - 162, - 5 - ], - [ - 163, - 4 - ], - [ - 164, - 4 - ], - [ - 164, - 3 - ], - [ - 165, - 2 - ], - [ - 166, - 2 - ], - [ - 166, - 1 - ], - [ - 167, - 0 - ], - [ - 168, - 0 - ], - [ - 168, - -1 - ], - [ - 169, - -1 - ], - [ - 170, - -2 - ], - [ - 171, - -2 - ], - [ - 172, - -3 - ], - [ - 172, - -3 - ], - [ - 173, - -4 - ], - [ - 174, - -4 - ], - [ - 175, - -4 - ], - [ - 176, - -5 - ], - [ - 177, - -5 - ], - [ - 178, - -5 - ], - [ - 179, - -5 - ], - [ - 179, - -5 - ], - [ - 180, - -6 - ], - [ - 181, - -6 - ], - [ - 182, - -6 - ], - [ - 183, - -6 - ], - [ - 184, - -6 - ], - [ - 185, - -6 - ], - [ - 185, - -9 - ] - ], - "points": [ - [ - 160, - 23 - ], - [ - 159, - 23 - ], - [ - 158, - 23 - ], - [ - 157, - 23 - ], - [ - 156, - 23 - ], - [ - 156, - 22 - ], - [ - 156, - 21 - ], - [ - 156, - 20 - ], - [ - 156, - 19 - ], - [ - 156, - 18 - ], - [ - 157, - 17 - ], - [ - 157, - 16 - ], - [ - 157, - 15 - ], - [ - 157, - 14 - ], - [ - 158, - 14 - ], - [ - 158, - 13 - ], - [ - 158, - 12 - ], - [ - 158, - 11 - ], - [ - 158, - 10 - ], - [ - 159, - 10 - ], - [ - 160, - 10 - ], - [ - 160, - 9 - ], - [ - 160, - 8 - ], - [ - 161, - 7 - ], - [ - 161, - 6 - ], - [ - 162, - 6 - ], - [ - 162, - 5 - ], - [ - 162, - 4 - ], - [ - 163, - 4 - ], - [ - 164, - 4 - ], - [ - 164, - 3 - ], - [ - 164, - 2 - ], - [ - 165, - 2 - ], - [ - 166, - 2 - ], - [ - 166, - 1 - ], - [ - 166, - 0 - ], - [ - 167, - 0 - ], - [ - 168, - 0 - ], - [ - 168, - -1 - ], - [ - 169, - -1 - ], - [ - 170, - -2 - ], - [ - 171, - -2 - ], - [ - 172, - -2 - ], - [ - 172, - -3 - ], - [ - 172, - -4 - ], - [ - 173, - -4 - ], - [ - 174, - -4 - ], - [ - 175, - -4 - ], - [ - 176, - -4 - ], - [ - 176, - -5 - ], - [ - 177, - -5 - ], - [ - 178, - -5 - ], - [ - 179, - -5 - ], - [ - 180, - -6 - ], - [ - 181, - -6 - ], - [ - 182, - -6 - ], - [ - 183, - -6 - ], - [ - 184, - -6 - ], - [ - 185, - -6 - ], - [ - 185, - -7 - ], - [ - 185, - -8 - ], - [ - 185, - -9 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "Connect the spawn bridge to the main garden promenade without passing through the glasshouse.", - "analysis": { - "ground_min": 79.9, - "ground_max": 85.7, - "water_samples": 0, - "length": 62.7 - } - }, - { - "id": "winter-garden-north-arc", - "name": "Winter garden north promenade", - "waypoints": [ - [ - 180, - 94 - ], - [ - 185, - 94 - ], - [ - 185, - 93 - ], - [ - 185, - 91 - ], - [ - 185, - 90 - ], - [ - 186, - 88 - ], - [ - 186, - 87 - ], - [ - 187, - 86 - ], - [ - 188, - 84 - ], - [ - 188, - 83 - ], - [ - 189, - 82 - ], - [ - 190, - 81 - ], - [ - 191, - 80 - ], - [ - 192, - 79 - ], - [ - 194, - 78 - ], - [ - 195, - 77 - ], - [ - 196, - 76 - ], - [ - 198, - 75 - ], - [ - 199, - 74 - ], - [ - 200, - 74 - ], - [ - 202, - 73 - ], - [ - 204, - 73 - ], - [ - 205, - 73 - ], - [ - 207, - 72 - ], - [ - 209, - 72 - ], - [ - 210, - 72 - ], - [ - 212, - 72 - ], - [ - 213, - 72 - ], - [ - 215, - 72 - ], - [ - 217, - 73 - ], - [ - 218, - 73 - ], - [ - 220, - 73 - ], - [ - 222, - 74 - ], - [ - 223, - 74 - ], - [ - 224, - 75 - ], - [ - 226, - 76 - ], - [ - 227, - 77 - ], - [ - 228, - 78 - ], - [ - 230, - 79 - ], - [ - 231, - 80 - ], - [ - 232, - 81 - ], - [ - 233, - 82 - ], - [ - 234, - 83 - ], - [ - 234, - 84 - ], - [ - 235, - 86 - ], - [ - 236, - 87 - ], - [ - 236, - 88 - ], - [ - 237, - 90 - ], - [ - 237, - 91 - ], - [ - 237, - 93 - ], - [ - 237, - 94 - ], - [ - 238, - 97 - ] - ], - "points": [ - [ - 180, - 94 - ], - [ - 181, - 94 - ], - [ - 182, - 94 - ], - [ - 183, - 94 - ], - [ - 184, - 94 - ], - [ - 185, - 94 - ], - [ - 185, - 93 - ], - [ - 185, - 92 - ], - [ - 185, - 91 - ], - [ - 185, - 90 - ], - [ - 185, - 89 - ], - [ - 186, - 89 - ], - [ - 186, - 88 - ], - [ - 186, - 87 - ], - [ - 186, - 86 - ], - [ - 187, - 86 - ], - [ - 187, - 85 - ], - [ - 188, - 85 - ], - [ - 188, - 84 - ], - [ - 188, - 83 - ], - [ - 188, - 82 - ], - [ - 189, - 82 - ], - [ - 190, - 82 - ], - [ - 190, - 81 - ], - [ - 190, - 80 - ], - [ - 191, - 80 - ], - [ - 192, - 80 - ], - [ - 192, - 79 - ], - [ - 193, - 79 - ], - [ - 193, - 78 - ], - [ - 194, - 78 - ], - [ - 195, - 77 - ], - [ - 196, - 76 - ], - [ - 197, - 76 - ], - [ - 197, - 75 - ], - [ - 198, - 75 - ], - [ - 198, - 74 - ], - [ - 199, - 74 - ], - [ - 200, - 74 - ], - [ - 201, - 74 - ], - [ - 201, - 73 - ], - [ - 202, - 73 - ], - [ - 203, - 73 - ], - [ - 204, - 73 - ], - [ - 205, - 73 - ], - [ - 206, - 73 - ], - [ - 206, - 72 - ], - [ - 207, - 72 - ], - [ - 208, - 72 - ], - [ - 209, - 72 - ], - [ - 210, - 72 - ], - [ - 211, - 72 - ], - [ - 212, - 72 - ], - [ - 213, - 72 - ], - [ - 214, - 72 - ], - [ - 215, - 72 - ], - [ - 216, - 72 - ], - [ - 216, - 73 - ], - [ - 217, - 73 - ], - [ - 218, - 73 - ], - [ - 219, - 73 - ], - [ - 220, - 73 - ], - [ - 221, - 73 - ], - [ - 221, - 74 - ], - [ - 222, - 74 - ], - [ - 223, - 74 - ], - [ - 224, - 74 - ], - [ - 224, - 75 - ], - [ - 225, - 75 - ], - [ - 225, - 76 - ], - [ - 226, - 76 - ], - [ - 227, - 77 - ], - [ - 228, - 78 - ], - [ - 229, - 78 - ], - [ - 229, - 79 - ], - [ - 230, - 79 - ], - [ - 230, - 80 - ], - [ - 231, - 80 - ], - [ - 232, - 80 - ], - [ - 232, - 81 - ], - [ - 232, - 82 - ], - [ - 233, - 82 - ], - [ - 234, - 82 - ], - [ - 234, - 83 - ], - [ - 234, - 84 - ], - [ - 234, - 85 - ], - [ - 235, - 85 - ], - [ - 235, - 86 - ], - [ - 236, - 86 - ], - [ - 236, - 87 - ], - [ - 236, - 88 - ], - [ - 236, - 89 - ], - [ - 237, - 89 - ], - [ - 237, - 90 - ], - [ - 237, - 91 - ], - [ - 237, - 92 - ], - [ - 237, - 93 - ], - [ - 237, - 94 - ], - [ - 237, - 95 - ], - [ - 238, - 96 - ], - [ - 238, - 97 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "A court path links the district promenade to the scenic lookout on the east side.", - "analysis": { - "ground_min": 82.3, - "ground_max": 89.0, - "water_samples": 0, - "length": 102.1 - } - }, - { - "id": "observatory-west-arc", - "name": "Observatory courtyard connection", - "waypoints": [ - [ - 218, - 137 - ], - [ - 220, - 139 - ], - [ - 219, - 139 - ], - [ - 219, - 139 - ], - [ - 219, - 139 - ], - [ - 218, - 140 - ], - [ - 218, - 140 - ], - [ - 218, - 140 - ], - [ - 218, - 141 - ], - [ - 217, - 141 - ], - [ - 217, - 142 - ], - [ - 217, - 142 - ], - [ - 217, - 142 - ], - [ - 216, - 143 - ], - [ - 216, - 143 - ], - [ - 216, - 143 - ], - [ - 216, - 144 - ], - [ - 216, - 144 - ], - [ - 216, - 145 - ], - [ - 215, - 145 - ], - [ - 215, - 145 - ], - [ - 215, - 146 - ], - [ - 215, - 146 - ], - [ - 215, - 147 - ], - [ - 215, - 147 - ], - [ - 215, - 147 - ], - [ - 214, - 148 - ], - [ - 214, - 148 - ], - [ - 214, - 149 - ], - [ - 214, - 149 - ], - [ - 214, - 149 - ], - [ - 214, - 150 - ], - [ - 214, - 150 - ], - [ - 214, - 151 - ], - [ - 214, - 151 - ], - [ - 214, - 152 - ], - [ - 214, - 152 - ], - [ - 214, - 152 - ], - [ - 214, - 153 - ], - [ - 214, - 153 - ], - [ - 214, - 154 - ], - [ - 214, - 154 - ], - [ - 214, - 155 - ], - [ - 214, - 155 - ], - [ - 214, - 155 - ], - [ - 214, - 156 - ], - [ - 214, - 156 - ], - [ - 215, - 157 - ], - [ - 215, - 157 - ], - [ - 215, - 157 - ], - [ - 215, - 158 - ], - [ - 218, - 157 - ] - ], - "points": [ - [ - 218, - 137 - ], - [ - 219, - 138 - ], - [ - 220, - 139 - ], - [ - 219, - 139 - ], - [ - 218, - 140 - ], - [ - 218, - 141 - ], - [ - 217, - 141 - ], - [ - 217, - 142 - ], - [ - 216, - 142 - ], - [ - 216, - 143 - ], - [ - 216, - 144 - ], - [ - 216, - 145 - ], - [ - 215, - 145 - ], - [ - 215, - 146 - ], - [ - 215, - 147 - ], - [ - 214, - 148 - ], - [ - 214, - 149 - ], - [ - 214, - 150 - ], - [ - 214, - 151 - ], - [ - 214, - 152 - ], - [ - 214, - 153 - ], - [ - 214, - 154 - ], - [ - 214, - 155 - ], - [ - 214, - 156 - ], - [ - 215, - 157 - ], - [ - 215, - 158 - ], - [ - 216, - 158 - ], - [ - 217, - 157 - ], - [ - 218, - 157 - ] - ], - "width": 3, - "role": "secondary", - "design_intent": "Close the small gap between northern and southern promenade approaches outside the observatory.", - "analysis": { - "ground_min": 82.6, - "ground_max": 86.1, - "water_samples": 0, - "length": 30.5 - } - } - ], - "notes": [ - "Do not apply blindly over the in-flight base marker manifest: reconcile desired blocks against live/scheduled markers.", - "For stairs, minimum_marker_y=51 keeps water cells untouched; land segments follow the actual surface.", - "White path outlines across colored courtyard boundaries indicate entrances, not holes in completed walls.", - "Garden arcs lie in the reserved walk bands outside their glasshouse footprints." - ] -} diff --git a/examples/layout/shacraft-lobby-layout.json b/examples/layout/shacraft-lobby-layout.json deleted file mode 100644 index 529e2a6..0000000 --- a/examples/layout/shacraft-lobby-layout.json +++ /dev/null @@ -1,16064 +0,0 @@ -{ - "schema": "shacraft-layout-study-v1", - "world": "shacraft_lobby_v2", - "bounds": { - "min_x": -384, - "max_x": 383, - "min_z": -384, - "max_z": 383 - }, - "water_y": 48, - "status": "offline design; not a world mutation or live snapshot", - "districts": [ - { - "id": "01", - "name": "Arrival square", - "label": [ - 0, - 8 - ], - "color": "#84d440", - "intent": "An open hexagonal plaza; preserve the raised grassy crown. Main north axis reveals the clock tower." - }, - { - "id": "02", - "name": "Clock station", - "label": [ - -18, - -119 - ], - "color": "#f4d35e", - "intent": "A long station hall with projecting clock tower and two end pavilions. Its forecourt faces spawn." - }, - { - "id": "03", - "name": "Portal concourse", - "label": [ - -201, - -156 - ], - "color": "#bc80ef", - "intent": "A chamfered hall with six separate portal-bay footprints and a sunken-feeling garden approach." - }, - { - "id": "04", - "name": "Airship harbor", - "label": [ - 179, - -137 - ], - "color": "#40c8e3", - "intent": "East-bank terminal with three west-facing piers above the water gorge. One future flagship uses the middle pier." - }, - { - "id": "05", - "name": "Sky gardens", - "label": [ - 207, - 88 - ], - "color": "#64d9a0", - "intent": "Three distinct rounded landmarks connected by winding garden paths: greenhouse, winter garden, observatory." - }, - { - "id": "06", - "name": "Market quarter", - "label": [ - -195, - 225 - ], - "color": "#ef9863", - "intent": "Six small footprints follow the slope around a compact square. Use stepped streets and individual foundations." - }, - { - "id": "07", - "name": "Arrival viaduct", - "label": [ - 8, - 284 - ], - "color": "#f5f4e9", - "intent": "A long south approach follows the valley ridge; the final viaduct crosses the merging river branches." - }, - { - "id": "08", - "name": "Lake waterworks", - "label": [ - -135, - 43 - ], - "color": "#4ca3ff", - "intent": "A compact pumping house above the east lake shore and a low over-water promenade." - }, - { - "id": "09", - "name": "Scenic overlooks", - "label": [ - 48, - -180 - ], - "color": "#f07eaf", - "intent": "Small optional lookouts frame the valley, lake and arrival route; no mountain flattening." - } - ], - "features": [ - { - "id": "arrival-hex", - "district": "01", - "name": "Arrival square", - "type": "polygon", - "points": [ - [ - 0, - -37 - ], - [ - 39, - -15 - ], - [ - 43, - 31 - ], - [ - 0, - 57 - ], - [ - -43, - 31 - ], - [ - -39, - -15 - ], - [ - 0, - -37 - ] - ], - "color": "#84d440", - "role": "plaza", - "ground": { - "min": 88.39, - "max": 96.81, - "water_samples": 0, - "samples": 5753 - } - }, - { - "id": "spawn-medallion", - "district": "01", - "name": "Shacraft medallion reserve", - "type": "polygon", - "points": [ - [ - 16, - 9 - ], - [ - 16, - 12 - ], - [ - 15, - 14 - ], - [ - 14, - 17 - ], - [ - 12, - 19 - ], - [ - 10, - 21 - ], - [ - 8, - 23 - ], - [ - 5, - 24 - ], - [ - 3, - 25 - ], - [ - 0, - 25 - ], - [ - -3, - 25 - ], - [ - -5, - 24 - ], - [ - -8, - 23 - ], - [ - -10, - 21 - ], - [ - -12, - 19 - ], - [ - -14, - 17 - ], - [ - -15, - 14 - ], - [ - -16, - 12 - ], - [ - -16, - 9 - ], - [ - -16, - 6 - ], - [ - -15, - 4 - ], - [ - -14, - 1 - ], - [ - -12, - -1 - ], - [ - -10, - -3 - ], - [ - -8, - -5 - ], - [ - -5, - -6 - ], - [ - -3, - -7 - ], - [ - 0, - -7 - ], - [ - 3, - -7 - ], - [ - 5, - -6 - ], - [ - 8, - -5 - ], - [ - 10, - -3 - ], - [ - 12, - -1 - ], - [ - 14, - 1 - ], - [ - 15, - 4 - ], - [ - 16, - 6 - ], - [ - 16, - 9 - ] - ], - "color": "#84d440", - "role": "plaza", - "ground": { - "min": 92.04, - "max": 96.25, - "water_samples": 0, - "samples": 829 - } - }, - { - "id": "clock-station", - "district": "02", - "name": "Clock station and tower", - "type": "polygon", - "points": [ - [ - -74, - -139 - ], - [ - -51, - -139 - ], - [ - -51, - -145 - ], - [ - 16, - -145 - ], - [ - 16, - -139 - ], - [ - 40, - -139 - ], - [ - 40, - -98 - ], - [ - 13, - -98 - ], - [ - 13, - -83 - ], - [ - -25, - -83 - ], - [ - -25, - -98 - ], - [ - -74, - -98 - ], - [ - -74, - -139 - ] - ], - "color": "#f4d35e", - "role": "building", - "ground": { - "min": 82.33, - "max": 99.96, - "water_samples": 0, - "samples": 5823 - } - }, - { - "id": "station-clock-base", - "district": "02", - "name": "Clock tower base", - "type": "polyline", - "points": [ - [ - -15, - -114 - ], - [ - 3, - -114 - ], - [ - 3, - -96 - ], - [ - -15, - -96 - ], - [ - -15, - -114 - ] - ], - "color": "#f4d35e", - "role": "detail", - "ground": { - "min": 93.84, - "max": 97.68, - "water_samples": 0, - "samples": 5 - } - }, - { - "id": "station-pavilion--63", - "district": "02", - "name": "End pavilion", - "type": "polyline", - "points": [ - [ - -71, - -134 - ], - [ - -55, - -134 - ], - [ - -55, - -104 - ], - [ - -71, - -104 - ], - [ - -71, - -134 - ] - ], - "color": "#f4d35e", - "role": "detail", - "ground": { - "min": 83.05, - "max": 90.77, - "water_samples": 0, - "samples": 5 - } - }, - { - "id": "station-pavilion-29", - "district": "02", - "name": "End pavilion", - "type": "polyline", - "points": [ - [ - 21, - -134 - ], - [ - 37, - -134 - ], - [ - 37, - -104 - ], - [ - 21, - -104 - ], - [ - 21, - -134 - ] - ], - "color": "#f4d35e", - "role": "detail", - "ground": { - "min": 94.37, - "max": 97.71, - "water_samples": 0, - "samples": 5 - } - }, - { - "id": "station-forecourt", - "district": "02", - "name": "Station forecourt", - "type": "polygon", - "points": [ - [ - 31, - -65 - ], - [ - 31, - -63 - ], - [ - 30, - -61 - ], - [ - 28, - -59 - ], - [ - 26, - -58 - ], - [ - 24, - -56 - ], - [ - 20, - -54 - ], - [ - 17, - -53 - ], - [ - 13, - -52 - ], - [ - 9, - -51 - ], - [ - 4, - -51 - ], - [ - 0, - -50 - ], - [ - -5, - -50 - ], - [ - -10, - -50 - ], - [ - -14, - -51 - ], - [ - -19, - -51 - ], - [ - -23, - -52 - ], - [ - -27, - -53 - ], - [ - -30, - -54 - ], - [ - -34, - -56 - ], - [ - -36, - -57 - ], - [ - -38, - -59 - ], - [ - -40, - -61 - ], - [ - -41, - -63 - ], - [ - -41, - -65 - ], - [ - -41, - -67 - ], - [ - -40, - -69 - ], - [ - -38, - -71 - ], - [ - -36, - -72 - ], - [ - -34, - -74 - ], - [ - -30, - -76 - ], - [ - -27, - -77 - ], - [ - -23, - -78 - ], - [ - -19, - -79 - ], - [ - -14, - -79 - ], - [ - -10, - -80 - ], - [ - -5, - -80 - ], - [ - 0, - -80 - ], - [ - 4, - -79 - ], - [ - 9, - -79 - ], - [ - 13, - -78 - ], - [ - 17, - -77 - ], - [ - 20, - -76 - ], - [ - 24, - -74 - ], - [ - 26, - -72 - ], - [ - 28, - -71 - ], - [ - 30, - -69 - ], - [ - 31, - -67 - ], - [ - 31, - -65 - ] - ], - "color": "#f4d35e", - "role": "plaza", - "ground": { - "min": 91.26, - "max": 96.88, - "water_samples": 0, - "samples": 1745 - } - }, - { - "id": "portal-hall", - "district": "03", - "name": "Portal concourse", - "type": "polygon", - "points": [ - [ - -234, - -176 - ], - [ - -168, - -176 - ], - [ - -159, - -169 - ], - [ - -159, - -143 - ], - [ - -168, - -136 - ], - [ - -234, - -136 - ], - [ - -243, - -143 - ], - [ - -243, - -169 - ], - [ - -234, - -176 - ] - ], - "color": "#bc80ef", - "role": "building", - "ground": { - "min": 61.59, - "max": 75.51, - "water_samples": 0, - "samples": 3329 - } - }, - { - "id": "portal-bay-1", - "district": "03", - "name": "Portal bay 1", - "type": "polyline", - "points": [ - [ - -231, - -170 - ], - [ - -219, - -170 - ], - [ - -219, - -163 - ], - [ - -231, - -163 - ], - [ - -231, - -170 - ] - ], - "color": "#bc80ef", - "role": "detail", - "number": 1, - "ground": { - "min": 66.68, - "max": 70.84, - "water_samples": 0, - "samples": 5 - } - }, - { - "id": "portal-bay-2", - "district": "03", - "name": "Portal bay 2", - "type": "polyline", - "points": [ - [ - -207, - -170 - ], - [ - -195, - -170 - ], - [ - -195, - -163 - ], - [ - -207, - -163 - ], - [ - -207, - -170 - ] - ], - "color": "#bc80ef", - "role": "detail", - "number": 2, - "ground": { - "min": 66.21, - "max": 69.09, - "water_samples": 0, - "samples": 5 - } - }, - { - "id": "portal-bay-3", - "district": "03", - "name": "Portal bay 3", - "type": "polyline", - "points": [ - [ - -183, - -170 - ], - [ - -171, - -170 - ], - [ - -171, - -163 - ], - [ - -183, - -163 - ], - [ - -183, - -170 - ] - ], - "color": "#bc80ef", - "role": "detail", - "number": 3, - "ground": { - "min": 68.63, - "max": 71.21, - "water_samples": 0, - "samples": 5 - } - }, - { - "id": "portal-bay-4", - "district": "03", - "name": "Portal bay 4", - "type": "polyline", - "points": [ - [ - -231, - -149 - ], - [ - -219, - -149 - ], - [ - -219, - -142 - ], - [ - -231, - -142 - ], - [ - -231, - -149 - ] - ], - "color": "#bc80ef", - "role": "detail", - "number": 4, - "ground": { - "min": 64.73, - "max": 66.69, - "water_samples": 0, - "samples": 5 - } - }, - { - "id": "portal-bay-5", - "district": "03", - "name": "Portal bay 5", - "type": "polyline", - "points": [ - [ - -207, - -149 - ], - [ - -195, - -149 - ], - [ - -195, - -142 - ], - [ - -207, - -142 - ], - [ - -207, - -149 - ] - ], - "color": "#bc80ef", - "role": "detail", - "number": 5, - "ground": { - "min": 63.42, - "max": 65.37, - "water_samples": 0, - "samples": 5 - } - }, - { - "id": "portal-bay-6", - "district": "03", - "name": "Portal bay 6", - "type": "polyline", - "points": [ - [ - -183, - -149 - ], - [ - -171, - -149 - ], - [ - -171, - -142 - ], - [ - -183, - -142 - ], - [ - -183, - -149 - ] - ], - "color": "#bc80ef", - "role": "detail", - "number": 6, - "ground": { - "min": 66.17, - "max": 69.12, - "water_samples": 0, - "samples": 5 - } - }, - { - "id": "portal-forecourt", - "district": "03", - "name": "Portal garden court", - "type": "polygon", - "points": [ - [ - -142, - -93 - ], - [ - -142, - -91 - ], - [ - -143, - -90 - ], - [ - -143, - -88 - ], - [ - -144, - -87 - ], - [ - -146, - -86 - ], - [ - -147, - -85 - ], - [ - -149, - -83 - ], - [ - -150, - -83 - ], - [ - -152, - -82 - ], - [ - -155, - -81 - ], - [ - -157, - -81 - ], - [ - -159, - -81 - ], - [ - -161, - -81 - ], - [ - -163, - -81 - ], - [ - -166, - -82 - ], - [ - -168, - -83 - ], - [ - -169, - -83 - ], - [ - -171, - -85 - ], - [ - -172, - -86 - ], - [ - -174, - -87 - ], - [ - -175, - -88 - ], - [ - -175, - -90 - ], - [ - -176, - -91 - ], - [ - -176, - -93 - ], - [ - -176, - -95 - ], - [ - -175, - -96 - ], - [ - -175, - -98 - ], - [ - -174, - -99 - ], - [ - -172, - -100 - ], - [ - -171, - -101 - ], - [ - -169, - -103 - ], - [ - -168, - -103 - ], - [ - -166, - -104 - ], - [ - -163, - -105 - ], - [ - -161, - -105 - ], - [ - -159, - -105 - ], - [ - -157, - -105 - ], - [ - -155, - -105 - ], - [ - -152, - -104 - ], - [ - -150, - -103 - ], - [ - -149, - -103 - ], - [ - -147, - -101 - ], - [ - -146, - -100 - ], - [ - -144, - -99 - ], - [ - -143, - -98 - ], - [ - -143, - -96 - ], - [ - -142, - -95 - ], - [ - -142, - -93 - ] - ], - "color": "#bc80ef", - "role": "plaza", - "ground": { - "min": 51.68, - "max": 69.92, - "water_samples": 0, - "samples": 675 - } - }, - { - "id": "harbor-terminal", - "district": "04", - "name": "Airship terminal", - "type": "polygon", - "points": [ - [ - 169, - -170 - ], - [ - 191, - -170 - ], - [ - 199, - -162 - ], - [ - 199, - -103 - ], - [ - 191, - -95 - ], - [ - 169, - -95 - ], - [ - 161, - -103 - ], - [ - 161, - -162 - ], - [ - 169, - -170 - ] - ], - "color": "#40c8e3", - "role": "building", - "ground": { - "min": 62.31, - "max": 71.58, - "water_samples": 0, - "samples": 2820 - } - }, - { - "id": "airship-pier-1", - "district": "04", - "name": "Airship pier 1", - "type": "polyline", - "points": [ - [ - 161, - -162 - ], - [ - 107, - -162 - ], - [ - 103, - -158 - ], - [ - 107, - -154 - ], - [ - 161, - -154 - ], - [ - 161, - -162 - ] - ], - "color": "#40c8e3", - "role": "pier", - "deck_y": 79, - "number": 1, - "ground": { - "min": 65.51, - "max": 70.2, - "water_samples": 0, - "samples": 6 - } - }, - { - "id": "airship-pier-2", - "district": "04", - "name": "Airship pier 2", - "type": "polyline", - "points": [ - [ - 161, - -136 - ], - [ - 107, - -136 - ], - [ - 103, - -132 - ], - [ - 107, - -128 - ], - [ - 161, - -128 - ], - [ - 161, - -136 - ] - ], - "color": "#40c8e3", - "role": "pier", - "deck_y": 79, - "number": 2, - "ground": { - "min": 61.88, - "max": 71.95, - "water_samples": 0, - "samples": 6 - } - }, - { - "id": "airship-pier-3", - "district": "04", - "name": "Airship pier 3", - "type": "polyline", - "points": [ - [ - 161, - -110 - ], - [ - 107, - -110 - ], - [ - 103, - -106 - ], - [ - 107, - -102 - ], - [ - 161, - -102 - ], - [ - 161, - -110 - ] - ], - "color": "#40c8e3", - "role": "pier", - "deck_y": 79, - "number": 3, - "ground": { - "min": 52.09, - "max": 66.79, - "water_samples": 0, - "samples": 6 - } - }, - { - "id": "flagship-reserve", - "district": "04", - "name": "Flagship mooring reserve", - "type": "polyline", - "points": [ - [ - 107, - -132 - ], - [ - 107, - -131 - ], - [ - 106, - -129 - ], - [ - 105, - -128 - ], - [ - 103, - -127 - ], - [ - 101, - -126 - ], - [ - 99, - -125 - ], - [ - 96, - -124 - ], - [ - 92, - -123 - ], - [ - 89, - -123 - ], - [ - 86, - -122 - ], - [ - 82, - -122 - ], - [ - 78, - -122 - ], - [ - 74, - -122 - ], - [ - 70, - -122 - ], - [ - 67, - -123 - ], - [ - 64, - -123 - ], - [ - 60, - -124 - ], - [ - 57, - -125 - ], - [ - 55, - -126 - ], - [ - 53, - -127 - ], - [ - 51, - -128 - ], - [ - 50, - -129 - ], - [ - 49, - -131 - ], - [ - 49, - -132 - ], - [ - 49, - -133 - ], - [ - 50, - -135 - ], - [ - 51, - -136 - ], - [ - 53, - -137 - ], - [ - 55, - -138 - ], - [ - 57, - -139 - ], - [ - 60, - -140 - ], - [ - 63, - -141 - ], - [ - 67, - -141 - ], - [ - 70, - -142 - ], - [ - 74, - -142 - ], - [ - 78, - -142 - ], - [ - 82, - -142 - ], - [ - 86, - -142 - ], - [ - 89, - -141 - ], - [ - 92, - -141 - ], - [ - 96, - -140 - ], - [ - 99, - -139 - ], - [ - 101, - -138 - ], - [ - 103, - -137 - ], - [ - 105, - -136 - ], - [ - 106, - -135 - ], - [ - 107, - -133 - ], - [ - 107, - -132 - ] - ], - "color": "#40c8e3", - "role": "detail", - "deck_y": 92, - "ground": { - "min": 64.94, - "max": 89.94, - "water_samples": 0, - "samples": 49 - } - }, - { - "id": "garden-1", - "district": "05", - "name": "Palm glasshouse", - "type": "polygon", - "points": [ - [ - 210, - 23 - ], - [ - 210, - 25 - ], - [ - 210, - 27 - ], - [ - 209, - 29 - ], - [ - 208, - 32 - ], - [ - 208, - 34 - ], - [ - 207, - 36 - ], - [ - 205, - 37 - ], - [ - 204, - 39 - ], - [ - 203, - 41 - ], - [ - 201, - 42 - ], - [ - 199, - 43 - ], - [ - 198, - 45 - ], - [ - 196, - 46 - ], - [ - 194, - 46 - ], - [ - 191, - 47 - ], - [ - 189, - 48 - ], - [ - 187, - 48 - ], - [ - 185, - 48 - ], - [ - 183, - 48 - ], - [ - 181, - 48 - ], - [ - 179, - 47 - ], - [ - 176, - 46 - ], - [ - 174, - 46 - ], - [ - 172, - 45 - ], - [ - 171, - 43 - ], - [ - 169, - 42 - ], - [ - 167, - 41 - ], - [ - 166, - 39 - ], - [ - 165, - 37 - ], - [ - 163, - 36 - ], - [ - 162, - 34 - ], - [ - 162, - 32 - ], - [ - 161, - 29 - ], - [ - 160, - 27 - ], - [ - 160, - 25 - ], - [ - 160, - 23 - ], - [ - 160, - 21 - ], - [ - 160, - 19 - ], - [ - 161, - 17 - ], - [ - 162, - 14 - ], - [ - 162, - 12 - ], - [ - 163, - 11 - ], - [ - 165, - 9 - ], - [ - 166, - 7 - ], - [ - 167, - 5 - ], - [ - 169, - 4 - ], - [ - 171, - 3 - ], - [ - 172, - 1 - ], - [ - 174, - 0 - ], - [ - 176, - 0 - ], - [ - 179, - -1 - ], - [ - 181, - -2 - ], - [ - 183, - -2 - ], - [ - 185, - -2 - ], - [ - 187, - -2 - ], - [ - 189, - -2 - ], - [ - 191, - -1 - ], - [ - 194, - 0 - ], - [ - 196, - 0 - ], - [ - 198, - 1 - ], - [ - 199, - 3 - ], - [ - 201, - 4 - ], - [ - 203, - 5 - ], - [ - 204, - 7 - ], - [ - 205, - 9 - ], - [ - 207, - 10 - ], - [ - 208, - 12 - ], - [ - 208, - 14 - ], - [ - 209, - 17 - ], - [ - 210, - 19 - ], - [ - 210, - 21 - ], - [ - 210, - 23 - ] - ], - "color": "#64d9a0", - "role": "building", - "ground": { - "min": 79.61, - "max": 88.0, - "water_samples": 0, - "samples": 2012 - } - }, - { - "id": "garden-court-1", - "district": "05", - "name": "Palm glasshouse surrounding walk", - "type": "polygon", - "points": [ - [ - 217, - 23 - ], - [ - 217, - 26 - ], - [ - 217, - 29 - ], - [ - 216, - 31 - ], - [ - 215, - 34 - ], - [ - 214, - 37 - ], - [ - 213, - 39 - ], - [ - 211, - 41 - ], - [ - 210, - 44 - ], - [ - 208, - 46 - ], - [ - 206, - 48 - ], - [ - 203, - 49 - ], - [ - 201, - 51 - ], - [ - 199, - 52 - ], - [ - 196, - 53 - ], - [ - 193, - 54 - ], - [ - 191, - 55 - ], - [ - 188, - 55 - ], - [ - 185, - 55 - ], - [ - 182, - 55 - ], - [ - 179, - 55 - ], - [ - 177, - 54 - ], - [ - 174, - 53 - ], - [ - 171, - 52 - ], - [ - 169, - 51 - ], - [ - 167, - 49 - ], - [ - 164, - 48 - ], - [ - 162, - 46 - ], - [ - 160, - 44 - ], - [ - 159, - 41 - ], - [ - 157, - 39 - ], - [ - 156, - 37 - ], - [ - 155, - 34 - ], - [ - 154, - 31 - ], - [ - 153, - 29 - ], - [ - 153, - 26 - ], - [ - 153, - 23 - ], - [ - 153, - 20 - ], - [ - 153, - 17 - ], - [ - 154, - 15 - ], - [ - 155, - 12 - ], - [ - 156, - 9 - ], - [ - 157, - 7 - ], - [ - 159, - 5 - ], - [ - 160, - 2 - ], - [ - 162, - 0 - ], - [ - 164, - -2 - ], - [ - 167, - -3 - ], - [ - 169, - -5 - ], - [ - 171, - -6 - ], - [ - 174, - -7 - ], - [ - 177, - -8 - ], - [ - 179, - -9 - ], - [ - 182, - -9 - ], - [ - 185, - -9 - ], - [ - 188, - -9 - ], - [ - 191, - -9 - ], - [ - 193, - -8 - ], - [ - 196, - -7 - ], - [ - 199, - -6 - ], - [ - 201, - -5 - ], - [ - 203, - -3 - ], - [ - 206, - -2 - ], - [ - 208, - 0 - ], - [ - 210, - 2 - ], - [ - 211, - 5 - ], - [ - 213, - 7 - ], - [ - 214, - 9 - ], - [ - 215, - 12 - ], - [ - 216, - 15 - ], - [ - 217, - 17 - ], - [ - 217, - 20 - ], - [ - 217, - 23 - ] - ], - "color": "#64d9a0", - "role": "plaza", - "ground": { - "min": 77.7, - "max": 88.93, - "water_samples": 0, - "samples": 3309 - } - }, - { - "id": "garden-2", - "district": "05", - "name": "Winter garden", - "type": "polygon", - "points": [ - [ - 233, - 94 - ], - [ - 233, - 96 - ], - [ - 233, - 97 - ], - [ - 232, - 99 - ], - [ - 232, - 100 - ], - [ - 231, - 102 - ], - [ - 230, - 103 - ], - [ - 229, - 104 - ], - [ - 228, - 106 - ], - [ - 227, - 107 - ], - [ - 225, - 108 - ], - [ - 224, - 109 - ], - [ - 222, - 110 - ], - [ - 220, - 110 - ], - [ - 219, - 111 - ], - [ - 217, - 111 - ], - [ - 215, - 112 - ], - [ - 213, - 112 - ], - [ - 211, - 112 - ], - [ - 209, - 112 - ], - [ - 207, - 112 - ], - [ - 205, - 111 - ], - [ - 203, - 111 - ], - [ - 202, - 110 - ], - [ - 200, - 110 - ], - [ - 198, - 109 - ], - [ - 197, - 108 - ], - [ - 195, - 107 - ], - [ - 194, - 106 - ], - [ - 193, - 104 - ], - [ - 192, - 103 - ], - [ - 191, - 102 - ], - [ - 190, - 100 - ], - [ - 190, - 99 - ], - [ - 189, - 97 - ], - [ - 189, - 96 - ], - [ - 189, - 94 - ], - [ - 189, - 92 - ], - [ - 189, - 91 - ], - [ - 190, - 89 - ], - [ - 190, - 88 - ], - [ - 191, - 86 - ], - [ - 192, - 85 - ], - [ - 193, - 84 - ], - [ - 194, - 82 - ], - [ - 195, - 81 - ], - [ - 197, - 80 - ], - [ - 198, - 79 - ], - [ - 200, - 78 - ], - [ - 202, - 78 - ], - [ - 203, - 77 - ], - [ - 205, - 77 - ], - [ - 207, - 76 - ], - [ - 209, - 76 - ], - [ - 211, - 76 - ], - [ - 213, - 76 - ], - [ - 215, - 76 - ], - [ - 217, - 77 - ], - [ - 219, - 77 - ], - [ - 220, - 78 - ], - [ - 222, - 78 - ], - [ - 224, - 79 - ], - [ - 225, - 80 - ], - [ - 227, - 81 - ], - [ - 228, - 82 - ], - [ - 229, - 84 - ], - [ - 230, - 85 - ], - [ - 231, - 86 - ], - [ - 232, - 88 - ], - [ - 232, - 89 - ], - [ - 233, - 91 - ], - [ - 233, - 92 - ], - [ - 233, - 94 - ] - ], - "color": "#64d9a0", - "role": "building", - "ground": { - "min": 81.94, - "max": 90.22, - "water_samples": 0, - "samples": 1305 - } - }, - { - "id": "garden-court-2", - "district": "05", - "name": "Winter garden surrounding walk", - "type": "polygon", - "points": [ - [ - 240, - 94 - ], - [ - 240, - 96 - ], - [ - 240, - 98 - ], - [ - 239, - 100 - ], - [ - 238, - 103 - ], - [ - 237, - 105 - ], - [ - 236, - 106 - ], - [ - 235, - 108 - ], - [ - 233, - 110 - ], - [ - 232, - 112 - ], - [ - 230, - 113 - ], - [ - 228, - 114 - ], - [ - 226, - 116 - ], - [ - 223, - 117 - ], - [ - 221, - 117 - ], - [ - 219, - 118 - ], - [ - 216, - 119 - ], - [ - 214, - 119 - ], - [ - 211, - 119 - ], - [ - 208, - 119 - ], - [ - 206, - 119 - ], - [ - 203, - 118 - ], - [ - 201, - 117 - ], - [ - 199, - 117 - ], - [ - 196, - 116 - ], - [ - 194, - 114 - ], - [ - 192, - 113 - ], - [ - 190, - 112 - ], - [ - 189, - 110 - ], - [ - 187, - 108 - ], - [ - 186, - 106 - ], - [ - 185, - 105 - ], - [ - 184, - 103 - ], - [ - 183, - 100 - ], - [ - 182, - 98 - ], - [ - 182, - 96 - ], - [ - 182, - 94 - ], - [ - 182, - 92 - ], - [ - 182, - 90 - ], - [ - 183, - 88 - ], - [ - 184, - 85 - ], - [ - 185, - 83 - ], - [ - 186, - 82 - ], - [ - 187, - 80 - ], - [ - 189, - 78 - ], - [ - 190, - 76 - ], - [ - 192, - 75 - ], - [ - 194, - 74 - ], - [ - 196, - 72 - ], - [ - 199, - 71 - ], - [ - 201, - 71 - ], - [ - 203, - 70 - ], - [ - 206, - 69 - ], - [ - 208, - 69 - ], - [ - 211, - 69 - ], - [ - 214, - 69 - ], - [ - 216, - 69 - ], - [ - 219, - 70 - ], - [ - 221, - 71 - ], - [ - 223, - 71 - ], - [ - 226, - 72 - ], - [ - 228, - 74 - ], - [ - 230, - 75 - ], - [ - 232, - 76 - ], - [ - 233, - 78 - ], - [ - 235, - 80 - ], - [ - 236, - 81 - ], - [ - 237, - 83 - ], - [ - 238, - 85 - ], - [ - 239, - 88 - ], - [ - 240, - 90 - ], - [ - 240, - 92 - ], - [ - 240, - 94 - ] - ], - "color": "#64d9a0", - "role": "plaza", - "ground": { - "min": 81.94, - "max": 90.22, - "water_samples": 0, - "samples": 2338 - } - }, - { - "id": "garden-3", - "district": "05", - "name": "Observatory", - "type": "polygon", - "points": [ - [ - 248, - 152 - ], - [ - 248, - 153 - ], - [ - 248, - 155 - ], - [ - 247, - 156 - ], - [ - 247, - 157 - ], - [ - 247, - 158 - ], - [ - 246, - 160 - ], - [ - 245, - 161 - ], - [ - 244, - 162 - ], - [ - 244, - 163 - ], - [ - 243, - 163 - ], - [ - 242, - 164 - ], - [ - 240, - 165 - ], - [ - 239, - 166 - ], - [ - 238, - 166 - ], - [ - 237, - 166 - ], - [ - 236, - 167 - ], - [ - 234, - 167 - ], - [ - 233, - 167 - ], - [ - 232, - 167 - ], - [ - 230, - 167 - ], - [ - 229, - 166 - ], - [ - 228, - 166 - ], - [ - 227, - 166 - ], - [ - 226, - 165 - ], - [ - 224, - 164 - ], - [ - 223, - 163 - ], - [ - 222, - 163 - ], - [ - 222, - 162 - ], - [ - 221, - 161 - ], - [ - 220, - 160 - ], - [ - 219, - 158 - ], - [ - 219, - 157 - ], - [ - 219, - 156 - ], - [ - 218, - 155 - ], - [ - 218, - 153 - ], - [ - 218, - 152 - ], - [ - 218, - 151 - ], - [ - 218, - 149 - ], - [ - 219, - 148 - ], - [ - 219, - 147 - ], - [ - 219, - 146 - ], - [ - 220, - 144 - ], - [ - 221, - 143 - ], - [ - 222, - 142 - ], - [ - 222, - 141 - ], - [ - 223, - 141 - ], - [ - 224, - 140 - ], - [ - 226, - 139 - ], - [ - 227, - 138 - ], - [ - 228, - 138 - ], - [ - 229, - 138 - ], - [ - 230, - 137 - ], - [ - 232, - 137 - ], - [ - 233, - 137 - ], - [ - 234, - 137 - ], - [ - 236, - 137 - ], - [ - 237, - 138 - ], - [ - 238, - 138 - ], - [ - 239, - 138 - ], - [ - 240, - 139 - ], - [ - 242, - 140 - ], - [ - 243, - 141 - ], - [ - 244, - 141 - ], - [ - 244, - 142 - ], - [ - 245, - 143 - ], - [ - 246, - 144 - ], - [ - 247, - 146 - ], - [ - 247, - 147 - ], - [ - 247, - 148 - ], - [ - 248, - 149 - ], - [ - 248, - 151 - ], - [ - 248, - 152 - ] - ], - "color": "#64d9a0", - "role": "building", - "ground": { - "min": 81.19, - "max": 88.81, - "water_samples": 0, - "samples": 749 - } - }, - { - "id": "garden-court-3", - "district": "05", - "name": "Observatory surrounding walk", - "type": "polygon", - "points": [ - [ - 255, - 152 - ], - [ - 255, - 154 - ], - [ - 255, - 156 - ], - [ - 254, - 158 - ], - [ - 254, - 160 - ], - [ - 253, - 161 - ], - [ - 252, - 163 - ], - [ - 251, - 165 - ], - [ - 250, - 166 - ], - [ - 249, - 168 - ], - [ - 247, - 169 - ], - [ - 246, - 170 - ], - [ - 244, - 171 - ], - [ - 242, - 172 - ], - [ - 241, - 173 - ], - [ - 239, - 173 - ], - [ - 237, - 174 - ], - [ - 235, - 174 - ], - [ - 233, - 174 - ], - [ - 231, - 174 - ], - [ - 229, - 174 - ], - [ - 227, - 173 - ], - [ - 225, - 173 - ], - [ - 224, - 172 - ], - [ - 222, - 171 - ], - [ - 220, - 170 - ], - [ - 219, - 169 - ], - [ - 217, - 168 - ], - [ - 216, - 166 - ], - [ - 215, - 165 - ], - [ - 214, - 163 - ], - [ - 213, - 161 - ], - [ - 212, - 160 - ], - [ - 212, - 158 - ], - [ - 211, - 156 - ], - [ - 211, - 154 - ], - [ - 211, - 152 - ], - [ - 211, - 150 - ], - [ - 211, - 148 - ], - [ - 212, - 146 - ], - [ - 212, - 144 - ], - [ - 213, - 143 - ], - [ - 214, - 141 - ], - [ - 215, - 139 - ], - [ - 216, - 138 - ], - [ - 217, - 136 - ], - [ - 219, - 135 - ], - [ - 220, - 134 - ], - [ - 222, - 133 - ], - [ - 224, - 132 - ], - [ - 225, - 131 - ], - [ - 227, - 131 - ], - [ - 229, - 130 - ], - [ - 231, - 130 - ], - [ - 233, - 130 - ], - [ - 235, - 130 - ], - [ - 237, - 130 - ], - [ - 239, - 131 - ], - [ - 241, - 131 - ], - [ - 242, - 132 - ], - [ - 244, - 133 - ], - [ - 246, - 134 - ], - [ - 247, - 135 - ], - [ - 249, - 136 - ], - [ - 250, - 138 - ], - [ - 251, - 139 - ], - [ - 252, - 141 - ], - [ - 253, - 143 - ], - [ - 254, - 144 - ], - [ - 254, - 146 - ], - [ - 255, - 148 - ], - [ - 255, - 150 - ], - [ - 255, - 152 - ] - ], - "color": "#64d9a0", - "role": "plaza", - "ground": { - "min": 80.35, - "max": 97.79, - "water_samples": 0, - "samples": 1585 - } - }, - { - "id": "market-square", - "district": "06", - "name": "Market square", - "type": "polygon", - "points": [ - [ - -173, - 225 - ], - [ - -173, - 228 - ], - [ - -174, - 231 - ], - [ - -175, - 235 - ], - [ - -176, - 238 - ], - [ - -178, - 240 - ], - [ - -179, - 243 - ], - [ - -182, - 245 - ], - [ - -184, - 247 - ], - [ - -187, - 248 - ], - [ - -189, - 249 - ], - [ - -192, - 250 - ], - [ - -195, - 250 - ], - [ - -198, - 250 - ], - [ - -201, - 249 - ], - [ - -203, - 248 - ], - [ - -206, - 247 - ], - [ - -208, - 245 - ], - [ - -211, - 243 - ], - [ - -212, - 240 - ], - [ - -214, - 238 - ], - [ - -215, - 235 - ], - [ - -216, - 231 - ], - [ - -217, - 228 - ], - [ - -217, - 225 - ], - [ - -217, - 222 - ], - [ - -216, - 219 - ], - [ - -215, - 215 - ], - [ - -214, - 212 - ], - [ - -212, - 210 - ], - [ - -211, - 207 - ], - [ - -208, - 205 - ], - [ - -206, - 203 - ], - [ - -203, - 202 - ], - [ - -201, - 201 - ], - [ - -198, - 200 - ], - [ - -195, - 200 - ], - [ - -192, - 200 - ], - [ - -189, - 201 - ], - [ - -187, - 202 - ], - [ - -184, - 203 - ], - [ - -182, - 205 - ], - [ - -179, - 207 - ], - [ - -178, - 210 - ], - [ - -176, - 212 - ], - [ - -175, - 215 - ], - [ - -174, - 219 - ], - [ - -173, - 222 - ], - [ - -173, - 225 - ] - ], - "color": "#ef9863", - "role": "plaza", - "ground": { - "min": 61.13, - "max": 69.96, - "water_samples": 0, - "samples": 1759 - } - }, - { - "id": "market-1", - "district": "06", - "name": "Bakery", - "type": "polygon", - "points": [ - [ - -232, - 181 - ], - [ - -208, - 177 - ], - [ - -206, - 195 - ], - [ - -230, - 199 - ], - [ - -232, - 181 - ] - ], - "color": "#ef9863", - "role": "building", - "ground": { - "min": 70.86, - "max": 81.22, - "water_samples": 0, - "samples": 447 - } - }, - { - "id": "market-2", - "district": "06", - "name": "Crafts hall", - "type": "polygon", - "points": [ - [ - -188, - 185 - ], - [ - -164, - 188 - ], - [ - -166, - 205 - ], - [ - -190, - 202 - ], - [ - -188, - 185 - ] - ], - "color": "#ef9863", - "role": "building", - "ground": { - "min": 63.71, - "max": 70.4, - "water_samples": 0, - "samples": 419 - } - }, - { - "id": "market-3", - "district": "06", - "name": "Tea house", - "type": "polygon", - "points": [ - [ - -169, - 217 - ], - [ - -152, - 219 - ], - [ - -155, - 245 - ], - [ - -172, - 243 - ], - [ - -169, - 217 - ] - ], - "color": "#ef9863", - "role": "building", - "ground": { - "min": 63.49, - "max": 66.03, - "water_samples": 0, - "samples": 451 - } - }, - { - "id": "market-4", - "district": "06", - "name": "Guild shop", - "type": "polygon", - "points": [ - [ - -190, - 265 - ], - [ - -168, - 262 - ], - [ - -166, - 279 - ], - [ - -188, - 282 - ], - [ - -190, - 265 - ] - ], - "color": "#ef9863", - "role": "building", - "ground": { - "min": 62.21, - "max": 64.75, - "water_samples": 0, - "samples": 383 - } - }, - { - "id": "market-5", - "district": "06", - "name": "Workshop", - "type": "polygon", - "points": [ - [ - -224, - 262 - ], - [ - -201, - 265 - ], - [ - -204, - 282 - ], - [ - -227, - 279 - ], - [ - -224, - 262 - ] - ], - "color": "#ef9863", - "role": "building", - "ground": { - "min": 62.22, - "max": 65.37, - "water_samples": 0, - "samples": 403 - } - }, - { - "id": "market-6", - "district": "06", - "name": "Guild hall", - "type": "polygon", - "points": [ - [ - -245, - 227 - ], - [ - -223, - 225 - ], - [ - -221, - 251 - ], - [ - -243, - 253 - ], - [ - -245, - 227 - ] - ], - "color": "#ef9863", - "role": "building", - "ground": { - "min": 62.16, - "max": 72.66, - "water_samples": 0, - "samples": 581 - } - }, - { - "id": "waterworks-pump", - "district": "08", - "name": "Pumping house", - "type": "polygon", - "points": [ - [ - -144, - 36 - ], - [ - -131, - 32 - ], - [ - -126, - 50 - ], - [ - -139, - 54 - ], - [ - -144, - 36 - ] - ], - "color": "#4ca3ff", - "role": "building", - "ground": { - "min": 66.75, - "max": 71.94, - "water_samples": 0, - "samples": 257 - } - }, - { - "id": "waterworks-lookout", - "district": "08", - "name": "Waterworks lookout", - "type": "polygon", - "points": [ - [ - -125, - 69 - ], - [ - -125, - 71 - ], - [ - -126, - 73 - ], - [ - -127, - 75 - ], - [ - -128, - 76 - ], - [ - -129, - 77 - ], - [ - -131, - 78 - ], - [ - -133, - 79 - ], - [ - -135, - 79 - ], - [ - -137, - 79 - ], - [ - -139, - 78 - ], - [ - -141, - 77 - ], - [ - -142, - 76 - ], - [ - -143, - 75 - ], - [ - -144, - 73 - ], - [ - -145, - 71 - ], - [ - -145, - 69 - ], - [ - -145, - 67 - ], - [ - -144, - 65 - ], - [ - -143, - 63 - ], - [ - -142, - 62 - ], - [ - -141, - 61 - ], - [ - -139, - 60 - ], - [ - -137, - 59 - ], - [ - -135, - 59 - ], - [ - -133, - 59 - ], - [ - -131, - 60 - ], - [ - -129, - 61 - ], - [ - -128, - 62 - ], - [ - -127, - 63 - ], - [ - -126, - 65 - ], - [ - -125, - 67 - ], - [ - -125, - 69 - ] - ], - "color": "#4ca3ff", - "role": "plaza", - "ground": { - "min": 68.63, - "max": 72.92, - "water_samples": 0, - "samples": 333 - } - }, - { - "id": "lake-boardwalk", - "district": "08", - "name": "Lake boardwalk alignment", - "type": "polyline", - "points": [ - [ - -177, - -69 - ], - [ - -177, - -68 - ], - [ - -176, - -67 - ], - [ - -176, - -66 - ], - [ - -176, - -65 - ], - [ - -175, - -64 - ], - [ - -175, - -63 - ], - [ - -175, - -62 - ], - [ - -174, - -61 - ], - [ - -174, - -60 - ], - [ - -174, - -59 - ], - [ - -173, - -58 - ], - [ - -173, - -57 - ], - [ - -172, - -56 - ], - [ - -172, - -55 - ], - [ - -171, - -54 - ], - [ - -170, - -53 - ], - [ - -170, - -52 - ], - [ - -169, - -51 - ], - [ - -168, - -50 - ], - [ - -168, - -49 - ], - [ - -167, - -48 - ], - [ - -167, - -47 - ], - [ - -166, - -46 - ], - [ - -166, - -45 - ], - [ - -166, - -44 - ], - [ - -165, - -43 - ], - [ - -165, - -42 - ], - [ - -165, - -41 - ], - [ - -164, - -40 - ], - [ - -164, - -39 - ], - [ - -164, - -38 - ], - [ - -163, - -37 - ], - [ - -163, - -36 - ], - [ - -163, - -35 - ], - [ - -162, - -34 - ], - [ - -162, - -33 - ], - [ - -162, - -32 - ], - [ - -161, - -31 - ], - [ - -161, - -30 - ], - [ - -161, - -29 - ], - [ - -161, - -28 - ], - [ - -161, - -27 - ], - [ - -161, - -26 - ], - [ - -160, - -25 - ], - [ - -160, - -24 - ], - [ - -160, - -23 - ], - [ - -160, - -22 - ], - [ - -160, - -21 - ], - [ - -160, - -20 - ], - [ - -160, - -19 - ], - [ - -160, - -18 - ], - [ - -160, - -17 - ], - [ - -161, - -16 - ], - [ - -161, - -15 - ], - [ - -161, - -14 - ], - [ - -161, - -13 - ], - [ - -161, - -12 - ], - [ - -161, - -11 - ], - [ - -162, - -10 - ], - [ - -162, - -9 - ], - [ - -162, - -8 - ], - [ - -163, - -7 - ], - [ - -163, - -6 - ], - [ - -163, - -5 - ], - [ - -163, - -4 - ], - [ - -163, - -3 - ], - [ - -163, - -2 - ], - [ - -164, - -1 - ], - [ - -164, - 0 - ], - [ - -164, - 1 - ], - [ - -164, - 2 - ], - [ - -164, - 3 - ], - [ - -164, - 4 - ], - [ - -164, - 5 - ], - [ - -164, - 6 - ], - [ - -164, - 7 - ], - [ - -165, - 8 - ], - [ - -165, - 9 - ], - [ - -165, - 10 - ], - [ - -165, - 11 - ], - [ - -165, - 12 - ], - [ - -165, - 13 - ], - [ - -165, - 14 - ], - [ - -165, - 15 - ], - [ - -165, - 16 - ], - [ - -166, - 17 - ], - [ - -166, - 18 - ], - [ - -166, - 19 - ], - [ - -167, - 20 - ], - [ - -167, - 21 - ], - [ - -167, - 22 - ], - [ - -168, - 23 - ], - [ - -168, - 24 - ], - [ - -168, - 25 - ], - [ - -168, - 26 - ], - [ - -168, - 27 - ], - [ - -168, - 28 - ], - [ - -168, - 29 - ], - [ - -168, - 30 - ], - [ - -168, - 31 - ], - [ - -169, - 32 - ], - [ - -169, - 33 - ], - [ - -169, - 34 - ], - [ - -169, - 35 - ], - [ - -169, - 36 - ], - [ - -169, - 37 - ], - [ - -169, - 38 - ], - [ - -169, - 39 - ], - [ - -170, - 40 - ], - [ - -170, - 41 - ], - [ - -171, - 42 - ], - [ - -171, - 43 - ], - [ - -172, - 44 - ], - [ - -172, - 45 - ], - [ - -173, - 46 - ], - [ - -173, - 47 - ], - [ - -174, - 48 - ], - [ - -175, - 49 - ], - [ - -175, - 50 - ], - [ - -176, - 50 - ], - [ - -177, - 51 - ], - [ - -178, - 52 - ], - [ - -179, - 52 - ], - [ - -180, - 53 - ], - [ - -181, - 53 - ], - [ - -182, - 54 - ], - [ - -183, - 55 - ], - [ - -184, - 56 - ], - [ - -185, - 57 - ], - [ - -186, - 58 - ], - [ - -186, - 59 - ], - [ - -187, - 60 - ], - [ - -187, - 61 - ], - [ - -188, - 62 - ], - [ - -188, - 63 - ], - [ - -188, - 64 - ], - [ - -189, - 65 - ], - [ - -189, - 66 - ], - [ - -189, - 67 - ], - [ - -190, - 68 - ], - [ - -190, - 69 - ], - [ - -190, - 70 - ], - [ - -190, - 71 - ], - [ - -190, - 72 - ], - [ - -190, - 73 - ], - [ - -189, - 74 - ], - [ - -189, - 75 - ] - ], - "color": "#4ca3ff", - "role": "promenade", - "deck_y": 51, - "ground": { - "min": 43.31, - "max": 47.12, - "water_samples": 148, - "samples": 148 - } - }, - { - "id": "overlook-1", - "district": "09", - "name": "North clock-view belvedere", - "type": "polygon", - "points": [ - [ - 51, - -173 - ], - [ - 51, - -171 - ], - [ - 50, - -170 - ], - [ - 50, - -169 - ], - [ - 49, - -167 - ], - [ - 47, - -166 - ], - [ - 46, - -166 - ], - [ - 45, - -165 - ], - [ - 43, - -165 - ], - [ - 41, - -165 - ], - [ - 40, - -166 - ], - [ - 39, - -166 - ], - [ - 37, - -167 - ], - [ - 36, - -169 - ], - [ - 36, - -170 - ], - [ - 35, - -171 - ], - [ - 35, - -173 - ], - [ - 35, - -175 - ], - [ - 36, - -176 - ], - [ - 36, - -177 - ], - [ - 37, - -179 - ], - [ - 39, - -180 - ], - [ - 40, - -180 - ], - [ - 41, - -181 - ], - [ - 43, - -181 - ], - [ - 45, - -181 - ], - [ - 46, - -180 - ], - [ - 47, - -180 - ], - [ - 49, - -179 - ], - [ - 50, - -177 - ], - [ - 50, - -176 - ], - [ - 51, - -175 - ], - [ - 51, - -173 - ] - ], - "color": "#f07eaf", - "role": "plaza", - "ground": { - "min": 87.92, - "max": 96.21, - "water_samples": 0, - "samples": 225 - } - }, - { - "id": "overlook-2", - "district": "09", - "name": "Portal ridge overlook", - "type": "polygon", - "points": [ - [ - -123, - -188 - ], - [ - -123, - -187 - ], - [ - -124, - -185 - ], - [ - -124, - -184 - ], - [ - -125, - -183 - ], - [ - -126, - -182 - ], - [ - -127, - -182 - ], - [ - -129, - -181 - ], - [ - -130, - -181 - ], - [ - -131, - -181 - ], - [ - -133, - -182 - ], - [ - -134, - -182 - ], - [ - -135, - -183 - ], - [ - -136, - -184 - ], - [ - -136, - -185 - ], - [ - -137, - -187 - ], - [ - -137, - -188 - ], - [ - -137, - -189 - ], - [ - -136, - -191 - ], - [ - -136, - -192 - ], - [ - -135, - -193 - ], - [ - -134, - -194 - ], - [ - -133, - -194 - ], - [ - -131, - -195 - ], - [ - -130, - -195 - ], - [ - -129, - -195 - ], - [ - -127, - -194 - ], - [ - -126, - -194 - ], - [ - -125, - -193 - ], - [ - -124, - -192 - ], - [ - -124, - -191 - ], - [ - -123, - -189 - ], - [ - -123, - -188 - ] - ], - "color": "#f07eaf", - "role": "plaza", - "ground": { - "min": 82.7, - "max": 93.53, - "water_samples": 0, - "samples": 169 - } - }, - { - "id": "overlook-3", - "district": "09", - "name": "Harbor overlook", - "type": "polygon", - "points": [ - [ - 237, - -76 - ], - [ - 237, - -74 - ], - [ - 236, - -73 - ], - [ - 236, - -72 - ], - [ - 235, - -70 - ], - [ - 233, - -69 - ], - [ - 232, - -69 - ], - [ - 231, - -68 - ], - [ - 229, - -68 - ], - [ - 227, - -68 - ], - [ - 226, - -69 - ], - [ - 225, - -69 - ], - [ - 223, - -70 - ], - [ - 222, - -72 - ], - [ - 222, - -73 - ], - [ - 221, - -74 - ], - [ - 221, - -76 - ], - [ - 221, - -78 - ], - [ - 222, - -79 - ], - [ - 222, - -80 - ], - [ - 223, - -82 - ], - [ - 225, - -83 - ], - [ - 226, - -83 - ], - [ - 227, - -84 - ], - [ - 229, - -84 - ], - [ - 231, - -84 - ], - [ - 232, - -83 - ], - [ - 233, - -83 - ], - [ - 235, - -82 - ], - [ - 236, - -80 - ], - [ - 236, - -79 - ], - [ - 237, - -78 - ], - [ - 237, - -76 - ] - ], - "color": "#f07eaf", - "role": "plaza", - "ground": { - "min": 72.58, - "max": 83.82, - "water_samples": 0, - "samples": 225 - } - }, - { - "id": "overlook-4", - "district": "09", - "name": "Garden lookout", - "type": "polygon", - "points": [ - [ - 273, - 110 - ], - [ - 273, - 111 - ], - [ - 272, - 113 - ], - [ - 272, - 114 - ], - [ - 271, - 115 - ], - [ - 270, - 116 - ], - [ - 269, - 116 - ], - [ - 267, - 117 - ], - [ - 266, - 117 - ], - [ - 265, - 117 - ], - [ - 263, - 116 - ], - [ - 262, - 116 - ], - [ - 261, - 115 - ], - [ - 260, - 114 - ], - [ - 260, - 113 - ], - [ - 259, - 111 - ], - [ - 259, - 110 - ], - [ - 259, - 109 - ], - [ - 260, - 107 - ], - [ - 260, - 106 - ], - [ - 261, - 105 - ], - [ - 262, - 104 - ], - [ - 263, - 104 - ], - [ - 265, - 103 - ], - [ - 266, - 103 - ], - [ - 267, - 103 - ], - [ - 269, - 104 - ], - [ - 270, - 104 - ], - [ - 271, - 105 - ], - [ - 272, - 106 - ], - [ - 272, - 107 - ], - [ - 273, - 109 - ], - [ - 273, - 110 - ] - ], - "color": "#f07eaf", - "role": "plaza", - "ground": { - "min": 91.4, - "max": 101.3, - "water_samples": 0, - "samples": 169 - } - }, - { - "id": "overlook-5", - "district": "09", - "name": "Valley approach lookout", - "type": "polygon", - "points": [ - [ - -251, - 258 - ], - [ - -251, - 259 - ], - [ - -252, - 261 - ], - [ - -252, - 262 - ], - [ - -253, - 263 - ], - [ - -254, - 264 - ], - [ - -255, - 264 - ], - [ - -257, - 265 - ], - [ - -258, - 265 - ], - [ - -259, - 265 - ], - [ - -261, - 264 - ], - [ - -262, - 264 - ], - [ - -263, - 263 - ], - [ - -264, - 262 - ], - [ - -264, - 261 - ], - [ - -265, - 259 - ], - [ - -265, - 258 - ], - [ - -265, - 257 - ], - [ - -264, - 255 - ], - [ - -264, - 254 - ], - [ - -263, - 253 - ], - [ - -262, - 252 - ], - [ - -261, - 252 - ], - [ - -259, - 251 - ], - [ - -258, - 251 - ], - [ - -257, - 251 - ], - [ - -255, - 252 - ], - [ - -254, - 252 - ], - [ - -253, - 253 - ], - [ - -252, - 254 - ], - [ - -252, - 255 - ], - [ - -251, - 257 - ], - [ - -251, - 258 - ] - ], - "color": "#f07eaf", - "role": "plaza", - "ground": { - "min": 65.75, - "max": 83.85, - "water_samples": 0, - "samples": 169 - } - } - ], - "routes": [ - { - "id": "station-axis", - "name": "Clock avenue", - "points": [ - [ - 0, - -37 - ], - [ - 0, - -38 - ], - [ - -1, - -38 - ], - [ - -1, - -39 - ], - [ - -1, - -40 - ], - [ - -2, - -41 - ], - [ - -2, - -42 - ], - [ - -3, - -43 - ], - [ - -3, - -44 - ], - [ - -3, - -45 - ], - [ - -4, - -47 - ], - [ - -4, - -48 - ], - [ - -4, - -49 - ], - [ - -4, - -50 - ], - [ - -4, - -51 - ], - [ - -4, - -52 - ], - [ - -5, - -53 - ], - [ - -5, - -54 - ], - [ - -5, - -55 - ], - [ - -5, - -56 - ], - [ - -5, - -57 - ], - [ - -5, - -58 - ], - [ - -5, - -59 - ], - [ - -5, - -60 - ], - [ - -5, - -61 - ], - [ - -5, - -62 - ], - [ - -5, - -63 - ], - [ - -5, - -64 - ], - [ - -5, - -65 - ], - [ - -5, - -66 - ], - [ - -5, - -67 - ], - [ - -5, - -68 - ], - [ - -5, - -69 - ], - [ - -5, - -70 - ], - [ - -5, - -72 - ], - [ - -5, - -73 - ], - [ - -5, - -74 - ], - [ - -6, - -75 - ], - [ - -6, - -76 - ], - [ - -6, - -77 - ], - [ - -6, - -78 - ], - [ - -6, - -79 - ], - [ - -6, - -80 - ], - [ - -6, - -81 - ], - [ - -6, - -82 - ], - [ - -6, - -83 - ] - ], - "waypoints": [ - [ - 0, - -37 - ], - [ - -4, - -48 - ], - [ - -5, - -65 - ], - [ - -6, - -83 - ] - ], - "width": 9, - "role": "main", - "analysis": { - "length": 48.9, - "ground_min": 94.5, - "ground_max": 96.1, - "p95_raw_grade": 0.19, - "water_samples": 0 - } - }, - { - "id": "portal-radial", - "name": "Portal approach", - "points": [ - [ - -35, - -14 - ], - [ - -36, - -15 - ], - [ - -37, - -16 - ], - [ - -38, - -17 - ], - [ - -39, - -18 - ], - [ - -40, - -19 - ], - [ - -41, - -20 - ], - [ - -42, - -21 - ], - [ - -43, - -22 - ], - [ - -44, - -23 - ], - [ - -45, - -24 - ], - [ - -46, - -24 - ], - [ - -47, - -25 - ], - [ - -47, - -26 - ], - [ - -48, - -27 - ], - [ - -49, - -28 - ], - [ - -50, - -28 - ], - [ - -51, - -29 - ], - [ - -51, - -30 - ], - [ - -52, - -31 - ], - [ - -53, - -31 - ], - [ - -54, - -32 - ], - [ - -55, - -33 - ], - [ - -56, - -34 - ], - [ - -57, - -35 - ], - [ - -58, - -36 - ], - [ - -59, - -37 - ], - [ - -60, - -38 - ], - [ - -61, - -38 - ], - [ - -62, - -39 - ], - [ - -63, - -40 - ], - [ - -64, - -40 - ], - [ - -64, - -41 - ], - [ - -65, - -42 - ], - [ - -66, - -42 - ], - [ - -67, - -43 - ], - [ - -68, - -44 - ], - [ - -69, - -44 - ], - [ - -69, - -45 - ], - [ - -70, - -45 - ], - [ - -71, - -46 - ], - [ - -72, - -46 - ], - [ - -73, - -47 - ], - [ - -74, - -47 - ], - [ - -75, - -48 - ], - [ - -76, - -49 - ], - [ - -77, - -50 - ], - [ - -78, - -50 - ], - [ - -79, - -51 - ], - [ - -80, - -51 - ], - [ - -81, - -52 - ], - [ - -82, - -52 - ], - [ - -83, - -53 - ], - [ - -84, - -53 - ], - [ - -85, - -54 - ], - [ - -86, - -54 - ], - [ - -87, - -55 - ], - [ - -88, - -55 - ], - [ - -89, - -56 - ], - [ - -90, - -56 - ], - [ - -91, - -57 - ], - [ - -92, - -58 - ], - [ - -93, - -58 - ], - [ - -94, - -59 - ], - [ - -95, - -59 - ], - [ - -96, - -60 - ], - [ - -97, - -60 - ], - [ - -98, - -61 - ], - [ - -99, - -61 - ], - [ - -100, - -61 - ], - [ - -101, - -62 - ], - [ - -102, - -63 - ], - [ - -103, - -63 - ], - [ - -104, - -64 - ], - [ - -105, - -64 - ], - [ - -106, - -65 - ], - [ - -107, - -65 - ], - [ - -108, - -66 - ], - [ - -109, - -66 - ], - [ - -110, - -67 - ], - [ - -112, - -67 - ], - [ - -113, - -67 - ], - [ - -114, - -68 - ], - [ - -115, - -68 - ], - [ - -116, - -69 - ], - [ - -117, - -69 - ], - [ - -118, - -69 - ], - [ - -119, - -70 - ], - [ - -120, - -70 - ], - [ - -121, - -71 - ], - [ - -122, - -71 - ], - [ - -123, - -71 - ], - [ - -124, - -72 - ], - [ - -125, - -72 - ], - [ - -126, - -72 - ], - [ - -127, - -73 - ], - [ - -128, - -73 - ], - [ - -129, - -73 - ], - [ - -130, - -73 - ], - [ - -131, - -74 - ], - [ - -132, - -74 - ], - [ - -133, - -74 - ], - [ - -134, - -75 - ], - [ - -135, - -75 - ], - [ - -136, - -75 - ], - [ - -137, - -76 - ], - [ - -138, - -76 - ], - [ - -140, - -76 - ], - [ - -141, - -77 - ], - [ - -142, - -77 - ], - [ - -143, - -77 - ], - [ - -144, - -78 - ], - [ - -145, - -78 - ], - [ - -146, - -78 - ], - [ - -148, - -78 - ], - [ - -149, - -79 - ], - [ - -150, - -79 - ], - [ - -151, - -79 - ], - [ - -152, - -79 - ], - [ - -153, - -80 - ], - [ - -154, - -80 - ], - [ - -155, - -80 - ], - [ - -156, - -80 - ], - [ - -157, - -81 - ], - [ - -158, - -81 - ], - [ - -159, - -81 - ] - ], - "waypoints": [ - [ - -35, - -14 - ], - [ - -67, - -43 - ], - [ - -107, - -65 - ], - [ - -135, - -75 - ], - [ - -159, - -81 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 154.1, - "ground_min": 60.2, - "ground_max": 95.0, - "p95_raw_grade": 0.53, - "water_samples": 0 - } - }, - { - "id": "portal-forecourt-link", - "name": "Portal court entrance", - "points": [ - [ - -159, - -105 - ], - [ - -159, - -106 - ], - [ - -158, - -106 - ], - [ - -158, - -107 - ], - [ - -158, - -108 - ], - [ - -157, - -109 - ], - [ - -157, - -110 - ], - [ - -156, - -111 - ], - [ - -156, - -112 - ], - [ - -155, - -113 - ], - [ - -155, - -114 - ], - [ - -154, - -115 - ], - [ - -154, - -117 - ], - [ - -154, - -118 - ], - [ - -154, - -119 - ], - [ - -154, - -120 - ], - [ - -154, - -121 - ], - [ - -154, - -122 - ], - [ - -155, - -123 - ], - [ - -155, - -124 - ], - [ - -156, - -125 - ], - [ - -156, - -126 - ], - [ - -157, - -127 - ], - [ - -157, - -128 - ], - [ - -158, - -129 - ], - [ - -158, - -130 - ], - [ - -159, - -131 - ], - [ - -160, - -132 - ], - [ - -160, - -133 - ], - [ - -161, - -134 - ], - [ - -161, - -135 - ], - [ - -162, - -136 - ], - [ - -162, - -137 - ], - [ - -163, - -137 - ], - [ - -163, - -138 - ], - [ - -164, - -139 - ], - [ - -164, - -139 - ] - ], - "waypoints": [ - [ - -159, - -105 - ], - [ - -154, - -120 - ], - [ - -164, - -139 - ] - ], - "width": 5, - "role": "secondary", - "analysis": { - "length": 41.4, - "ground_min": 66.4, - "ground_max": 72.0, - "p95_raw_grade": 0.38, - "water_samples": 0 - } - }, - { - "id": "lake-radial", - "name": "Lake walk", - "points": [ - [ - -42, - 18 - ], - [ - -43, - 18 - ], - [ - -44, - 18 - ], - [ - -44, - 19 - ], - [ - -45, - 19 - ], - [ - -46, - 19 - ], - [ - -47, - 19 - ], - [ - -48, - 19 - ], - [ - -48, - 20 - ], - [ - -49, - 20 - ], - [ - -50, - 20 - ], - [ - -51, - 20 - ], - [ - -52, - 21 - ], - [ - -53, - 21 - ], - [ - -55, - 21 - ], - [ - -56, - 21 - ], - [ - -57, - 22 - ], - [ - -58, - 22 - ], - [ - -59, - 22 - ], - [ - -60, - 22 - ], - [ - -61, - 23 - ], - [ - -62, - 23 - ], - [ - -63, - 23 - ], - [ - -65, - 24 - ], - [ - -66, - 24 - ], - [ - -67, - 24 - ], - [ - -68, - 24 - ], - [ - -69, - 25 - ], - [ - -70, - 25 - ], - [ - -71, - 25 - ], - [ - -72, - 26 - ], - [ - -73, - 26 - ], - [ - -74, - 26 - ], - [ - -75, - 26 - ], - [ - -76, - 27 - ], - [ - -77, - 27 - ], - [ - -78, - 27 - ], - [ - -79, - 28 - ], - [ - -80, - 28 - ], - [ - -81, - 28 - ], - [ - -82, - 29 - ], - [ - -83, - 29 - ], - [ - -84, - 30 - ], - [ - -85, - 30 - ], - [ - -86, - 31 - ], - [ - -87, - 31 - ], - [ - -88, - 31 - ], - [ - -89, - 32 - ], - [ - -90, - 32 - ], - [ - -91, - 33 - ], - [ - -92, - 33 - ], - [ - -93, - 34 - ], - [ - -94, - 34 - ], - [ - -95, - 35 - ], - [ - -96, - 35 - ], - [ - -97, - 36 - ], - [ - -98, - 36 - ], - [ - -99, - 36 - ], - [ - -100, - 37 - ], - [ - -101, - 37 - ], - [ - -102, - 38 - ], - [ - -103, - 38 - ], - [ - -104, - 38 - ], - [ - -105, - 38 - ], - [ - -106, - 39 - ], - [ - -107, - 39 - ], - [ - -109, - 39 - ], - [ - -110, - 39 - ], - [ - -111, - 39 - ], - [ - -112, - 39 - ], - [ - -113, - 39 - ], - [ - -114, - 40 - ], - [ - -116, - 40 - ], - [ - -117, - 40 - ], - [ - -118, - 40 - ], - [ - -119, - 40 - ], - [ - -120, - 40 - ], - [ - -121, - 40 - ], - [ - -122, - 40 - ], - [ - -123, - 40 - ], - [ - -124, - 40 - ], - [ - -125, - 40 - ], - [ - -126, - 40 - ] - ], - "waypoints": [ - [ - -42, - 18 - ], - [ - -77, - 27 - ], - [ - -104, - 38 - ], - [ - -126, - 40 - ] - ], - "width": 5, - "role": "secondary", - "analysis": { - "length": 94.1, - "ground_min": 72.6, - "ground_max": 92.8, - "p95_raw_grade": 0.49, - "water_samples": 0 - } - }, - { - "id": "east-radial", - "name": "Garden approach", - "points": [ - [ - 42, - 16 - ], - [ - 43, - 16 - ], - [ - 44, - 15 - ], - [ - 45, - 15 - ], - [ - 46, - 15 - ], - [ - 47, - 15 - ], - [ - 47, - 14 - ], - [ - 49, - 14 - ], - [ - 50, - 14 - ], - [ - 51, - 14 - ], - [ - 52, - 13 - ], - [ - 53, - 13 - ], - [ - 54, - 13 - ], - [ - 55, - 12 - ], - [ - 57, - 12 - ], - [ - 58, - 12 - ], - [ - 59, - 11 - ], - [ - 60, - 11 - ], - [ - 61, - 11 - ], - [ - 62, - 11 - ], - [ - 63, - 10 - ], - [ - 64, - 10 - ], - [ - 65, - 10 - ], - [ - 66, - 10 - ], - [ - 67, - 10 - ], - [ - 69, - 9 - ], - [ - 70, - 9 - ], - [ - 71, - 9 - ], - [ - 72, - 9 - ], - [ - 73, - 9 - ], - [ - 74, - 9 - ], - [ - 75, - 9 - ], - [ - 77, - 8 - ], - [ - 78, - 8 - ], - [ - 79, - 8 - ], - [ - 80, - 8 - ], - [ - 81, - 8 - ], - [ - 82, - 8 - ] - ], - "waypoints": [ - [ - 42, - 16 - ], - [ - 65, - 10 - ], - [ - 82, - 8 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 43.5, - "ground_min": 84.4, - "ground_max": 90.2, - "p95_raw_grade": 0.24, - "water_samples": 0 - } - }, - { - "id": "south-axis", - "name": "Arrival avenue", - "points": [ - [ - 0, - 57 - ], - [ - 0, - 58 - ], - [ - 0, - 59 - ], - [ - 0, - 60 - ], - [ - 0, - 61 - ], - [ - 0, - 62 - ], - [ - 1, - 62 - ], - [ - 1, - 63 - ], - [ - 1, - 64 - ], - [ - 1, - 65 - ], - [ - 1, - 66 - ], - [ - 1, - 67 - ], - [ - 1, - 68 - ], - [ - 1, - 69 - ], - [ - 2, - 70 - ], - [ - 2, - 71 - ], - [ - 2, - 72 - ], - [ - 2, - 73 - ], - [ - 2, - 74 - ], - [ - 2, - 75 - ], - [ - 2, - 76 - ], - [ - 2, - 78 - ], - [ - 2, - 79 - ], - [ - 3, - 80 - ], - [ - 3, - 81 - ], - [ - 3, - 82 - ], - [ - 3, - 83 - ], - [ - 3, - 84 - ], - [ - 3, - 86 - ], - [ - 3, - 87 - ], - [ - 3, - 88 - ], - [ - 3, - 89 - ], - [ - 3, - 90 - ], - [ - 3, - 91 - ], - [ - 3, - 93 - ], - [ - 3, - 94 - ], - [ - 3, - 95 - ], - [ - 3, - 96 - ], - [ - 3, - 97 - ], - [ - 3, - 98 - ], - [ - 3, - 99 - ], - [ - 3, - 100 - ], - [ - 3, - 101 - ], - [ - 3, - 102 - ], - [ - 2, - 103 - ], - [ - 2, - 104 - ], - [ - 2, - 105 - ], - [ - 2, - 106 - ], - [ - 2, - 107 - ], - [ - 1, - 108 - ], - [ - 1, - 109 - ], - [ - 1, - 110 - ], - [ - 1, - 111 - ], - [ - 1, - 112 - ], - [ - 0, - 113 - ], - [ - 0, - 114 - ], - [ - 0, - 115 - ], - [ - 0, - 116 - ], - [ - -1, - 117 - ], - [ - -1, - 118 - ], - [ - -1, - 119 - ], - [ - -1, - 120 - ], - [ - -2, - 121 - ], - [ - -2, - 122 - ], - [ - -2, - 123 - ], - [ - -2, - 124 - ], - [ - -3, - 125 - ], - [ - -3, - 126 - ], - [ - -3, - 127 - ], - [ - -3, - 128 - ], - [ - -3, - 129 - ], - [ - -4, - 130 - ], - [ - -4, - 131 - ], - [ - -4, - 132 - ], - [ - -4, - 133 - ], - [ - -4, - 134 - ], - [ - -4, - 135 - ], - [ - -5, - 136 - ], - [ - -5, - 137 - ], - [ - -5, - 138 - ], - [ - -5, - 139 - ], - [ - -5, - 140 - ], - [ - -5, - 141 - ], - [ - -5, - 142 - ], - [ - -5, - 143 - ], - [ - -5, - 144 - ], - [ - -5, - 145 - ], - [ - -5, - 146 - ], - [ - -5, - 147 - ], - [ - -5, - 148 - ], - [ - -5, - 149 - ], - [ - -5, - 150 - ], - [ - -4, - 151 - ], - [ - -4, - 152 - ], - [ - -4, - 153 - ], - [ - -4, - 154 - ], - [ - -4, - 155 - ], - [ - -4, - 156 - ], - [ - -3, - 157 - ], - [ - -3, - 158 - ], - [ - -3, - 159 - ], - [ - -3, - 160 - ], - [ - -2, - 161 - ], - [ - -2, - 162 - ], - [ - -2, - 163 - ], - [ - -2, - 164 - ], - [ - -2, - 165 - ], - [ - -1, - 166 - ], - [ - -1, - 167 - ], - [ - -1, - 168 - ], - [ - 0, - 169 - ], - [ - 0, - 170 - ], - [ - 0, - 171 - ], - [ - 0, - 172 - ], - [ - 1, - 173 - ], - [ - 1, - 174 - ], - [ - 1, - 175 - ], - [ - 1, - 176 - ], - [ - 2, - 177 - ], - [ - 2, - 178 - ], - [ - 2, - 179 - ], - [ - 2, - 180 - ], - [ - 3, - 181 - ], - [ - 3, - 182 - ], - [ - 3, - 183 - ], - [ - 3, - 184 - ], - [ - 3, - 185 - ], - [ - 4, - 186 - ], - [ - 4, - 187 - ], - [ - 4, - 188 - ], - [ - 4, - 189 - ], - [ - 4, - 190 - ], - [ - 4, - 191 - ], - [ - 4, - 192 - ], - [ - 5, - 193 - ], - [ - 5, - 194 - ], - [ - 5, - 195 - ], - [ - 5, - 196 - ], - [ - 5, - 197 - ], - [ - 5, - 198 - ], - [ - 5, - 199 - ], - [ - 5, - 200 - ], - [ - 5, - 201 - ], - [ - 6, - 202 - ], - [ - 6, - 203 - ], - [ - 6, - 204 - ], - [ - 6, - 205 - ], - [ - 6, - 206 - ], - [ - 6, - 207 - ], - [ - 6, - 208 - ], - [ - 6, - 209 - ], - [ - 6, - 210 - ], - [ - 6, - 211 - ], - [ - 7, - 212 - ], - [ - 7, - 213 - ], - [ - 7, - 214 - ], - [ - 7, - 215 - ], - [ - 7, - 216 - ], - [ - 7, - 217 - ], - [ - 7, - 218 - ], - [ - 7, - 219 - ], - [ - 7, - 220 - ], - [ - 7, - 221 - ], - [ - 7, - 222 - ], - [ - 7, - 223 - ], - [ - 7, - 224 - ], - [ - 7, - 225 - ], - [ - 8, - 226 - ], - [ - 8, - 227 - ], - [ - 8, - 228 - ], - [ - 8, - 229 - ], - [ - 8, - 230 - ], - [ - 8, - 231 - ], - [ - 8, - 232 - ], - [ - 8, - 233 - ], - [ - 8, - 234 - ], - [ - 8, - 235 - ], - [ - 8, - 236 - ], - [ - 8, - 237 - ], - [ - 8, - 238 - ], - [ - 8, - 239 - ], - [ - 8, - 240 - ], - [ - 8, - 241 - ], - [ - 8, - 242 - ], - [ - 8, - 243 - ], - [ - 8, - 244 - ], - [ - 8, - 245 - ], - [ - 8, - 246 - ], - [ - 8, - 247 - ], - [ - 8, - 248 - ], - [ - 8, - 249 - ], - [ - 8, - 250 - ], - [ - 8, - 251 - ], - [ - 8, - 252 - ], - [ - 8, - 253 - ], - [ - 8, - 254 - ], - [ - 8, - 255 - ], - [ - 8, - 256 - ], - [ - 8, - 257 - ], - [ - 8, - 258 - ], - [ - 8, - 260 - ], - [ - 8, - 261 - ], - [ - 8, - 262 - ], - [ - 8, - 263 - ], - [ - 8, - 264 - ], - [ - 8, - 265 - ], - [ - 8, - 266 - ], - [ - 8, - 267 - ], - [ - 8, - 268 - ], - [ - 7, - 269 - ], - [ - 7, - 270 - ], - [ - 7, - 271 - ], - [ - 7, - 272 - ], - [ - 7, - 273 - ], - [ - 7, - 274 - ], - [ - 7, - 275 - ], - [ - 7, - 276 - ], - [ - 7, - 277 - ], - [ - 7, - 278 - ], - [ - 7, - 279 - ], - [ - 7, - 280 - ], - [ - 7, - 281 - ], - [ - 7, - 282 - ], - [ - 7, - 283 - ], - [ - 7, - 284 - ], - [ - 7, - 285 - ], - [ - 7, - 286 - ], - [ - 7, - 288 - ], - [ - 7, - 289 - ], - [ - 7, - 290 - ], - [ - 7, - 291 - ], - [ - 7, - 292 - ], - [ - 6, - 293 - ], - [ - 6, - 295 - ], - [ - 6, - 296 - ], - [ - 6, - 297 - ], - [ - 6, - 298 - ], - [ - 6, - 299 - ], - [ - 6, - 300 - ], - [ - 6, - 301 - ], - [ - 6, - 303 - ], - [ - 6, - 304 - ], - [ - 6, - 305 - ], - [ - 6, - 306 - ], - [ - 6, - 307 - ], - [ - 6, - 308 - ], - [ - 6, - 309 - ], - [ - 6, - 310 - ], - [ - 6, - 311 - ], - [ - 5, - 312 - ], - [ - 5, - 313 - ], - [ - 5, - 314 - ], - [ - 5, - 315 - ], - [ - 5, - 316 - ], - [ - 5, - 317 - ], - [ - 5, - 318 - ], - [ - 5, - 319 - ], - [ - 5, - 320 - ], - [ - 5, - 321 - ] - ], - "waypoints": [ - [ - 0, - 57 - ], - [ - 3, - 97 - ], - [ - -5, - 143 - ], - [ - 4, - 189 - ], - [ - 8, - 237 - ], - [ - 7, - 281 - ], - [ - 5, - 321 - ] - ], - "width": 9, - "role": "main", - "analysis": { - "length": 275.8, - "ground_min": 59.3, - "ground_max": 91.8, - "p95_raw_grade": 0.38, - "water_samples": 0 - } - }, - { - "id": "ring-northwest", - "name": "Station to portals", - "points": [ - [ - -40, - -65 - ], - [ - -41, - -65 - ], - [ - -42, - -65 - ], - [ - -42, - -66 - ], - [ - -43, - -66 - ], - [ - -44, - -66 - ], - [ - -45, - -66 - ], - [ - -45, - -67 - ], - [ - -46, - -67 - ], - [ - -47, - -67 - ], - [ - -48, - -67 - ], - [ - -49, - -68 - ], - [ - -50, - -68 - ], - [ - -51, - -68 - ], - [ - -52, - -68 - ], - [ - -53, - -69 - ], - [ - -54, - -69 - ], - [ - -55, - -69 - ], - [ - -56, - -70 - ], - [ - -57, - -70 - ], - [ - -58, - -70 - ], - [ - -59, - -71 - ], - [ - -61, - -71 - ], - [ - -62, - -71 - ], - [ - -63, - -72 - ], - [ - -64, - -72 - ], - [ - -65, - -72 - ], - [ - -66, - -73 - ], - [ - -67, - -73 - ], - [ - -68, - -73 - ], - [ - -70, - -74 - ], - [ - -71, - -74 - ], - [ - -72, - -74 - ], - [ - -73, - -75 - ], - [ - -74, - -75 - ], - [ - -75, - -75 - ], - [ - -76, - -76 - ], - [ - -77, - -76 - ], - [ - -78, - -76 - ], - [ - -79, - -77 - ], - [ - -80, - -77 - ], - [ - -81, - -77 - ], - [ - -82, - -77 - ], - [ - -83, - -78 - ], - [ - -84, - -78 - ], - [ - -85, - -78 - ], - [ - -86, - -79 - ], - [ - -87, - -79 - ], - [ - -88, - -79 - ], - [ - -89, - -80 - ], - [ - -90, - -80 - ], - [ - -91, - -80 - ], - [ - -92, - -80 - ], - [ - -93, - -81 - ], - [ - -94, - -81 - ], - [ - -95, - -81 - ], - [ - -96, - -82 - ], - [ - -97, - -82 - ], - [ - -98, - -83 - ], - [ - -99, - -83 - ], - [ - -100, - -83 - ], - [ - -101, - -84 - ], - [ - -102, - -84 - ], - [ - -103, - -84 - ], - [ - -104, - -85 - ], - [ - -105, - -85 - ], - [ - -106, - -85 - ], - [ - -107, - -86 - ], - [ - -108, - -86 - ], - [ - -109, - -86 - ], - [ - -110, - -87 - ], - [ - -111, - -87 - ], - [ - -112, - -87 - ], - [ - -113, - -88 - ], - [ - -114, - -88 - ], - [ - -115, - -89 - ], - [ - -116, - -89 - ], - [ - -117, - -89 - ], - [ - -118, - -90 - ], - [ - -119, - -90 - ], - [ - -120, - -91 - ], - [ - -121, - -91 - ], - [ - -122, - -92 - ], - [ - -123, - -92 - ], - [ - -124, - -93 - ], - [ - -125, - -93 - ], - [ - -126, - -94 - ], - [ - -127, - -94 - ], - [ - -128, - -95 - ], - [ - -129, - -95 - ], - [ - -130, - -96 - ], - [ - -131, - -96 - ], - [ - -132, - -97 - ], - [ - -133, - -97 - ], - [ - -134, - -97 - ], - [ - -135, - -98 - ], - [ - -136, - -98 - ], - [ - -136, - -99 - ], - [ - -137, - -99 - ], - [ - -138, - -100 - ], - [ - -139, - -100 - ], - [ - -140, - -101 - ], - [ - -141, - -102 - ], - [ - -142, - -102 - ], - [ - -143, - -103 - ], - [ - -144, - -104 - ], - [ - -145, - -105 - ], - [ - -146, - -106 - ], - [ - -147, - -107 - ], - [ - -147, - -108 - ], - [ - -148, - -109 - ], - [ - -148, - -110 - ], - [ - -149, - -111 - ], - [ - -149, - -112 - ], - [ - -150, - -113 - ], - [ - -150, - -114 - ], - [ - -150, - -115 - ], - [ - -150, - -116 - ], - [ - -150, - -117 - ], - [ - -151, - -118 - ], - [ - -151, - -119 - ], - [ - -151, - -120 - ], - [ - -152, - -121 - ], - [ - -152, - -122 - ], - [ - -153, - -123 - ], - [ - -153, - -124 - ], - [ - -154, - -125 - ], - [ - -154, - -126 - ], - [ - -155, - -127 - ], - [ - -156, - -128 - ], - [ - -156, - -129 - ], - [ - -157, - -130 - ], - [ - -158, - -131 - ], - [ - -158, - -132 - ], - [ - -159, - -133 - ], - [ - -160, - -133 - ], - [ - -160, - -134 - ], - [ - -161, - -135 - ], - [ - -162, - -136 - ], - [ - -162, - -137 - ], - [ - -163, - -137 - ], - [ - -163, - -138 - ], - [ - -164, - -139 - ], - [ - -164, - -139 - ] - ], - "waypoints": [ - [ - -40, - -65 - ], - [ - -77, - -76 - ], - [ - -116, - -89 - ], - [ - -145, - -105 - ], - [ - -152, - -122 - ], - [ - -164, - -139 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 166.2, - "ground_min": 69.4, - "ground_max": 92.2, - "p95_raw_grade": 0.48, - "water_samples": 0 - } - }, - { - "id": "ring-west", - "name": "West inner promenade", - "points": [ - [ - -147, - -111 - ], - [ - -146, - -110 - ], - [ - -146, - -109 - ], - [ - -145, - -109 - ], - [ - -145, - -108 - ], - [ - -144, - -107 - ], - [ - -143, - -106 - ], - [ - -142, - -105 - ], - [ - -141, - -104 - ], - [ - -141, - -103 - ], - [ - -140, - -102 - ], - [ - -139, - -101 - ], - [ - -138, - -100 - ], - [ - -137, - -99 - ], - [ - -136, - -98 - ], - [ - -136, - -97 - ], - [ - -135, - -96 - ], - [ - -134, - -95 - ], - [ - -134, - -94 - ], - [ - -133, - -93 - ], - [ - -132, - -92 - ], - [ - -132, - -91 - ], - [ - -131, - -90 - ], - [ - -130, - -90 - ], - [ - -130, - -89 - ], - [ - -129, - -88 - ], - [ - -128, - -87 - ], - [ - -128, - -86 - ], - [ - -127, - -85 - ], - [ - -127, - -84 - ], - [ - -126, - -83 - ], - [ - -126, - -82 - ], - [ - -125, - -81 - ], - [ - -125, - -80 - ], - [ - -125, - -79 - ], - [ - -124, - -78 - ], - [ - -124, - -77 - ], - [ - -124, - -76 - ], - [ - -123, - -75 - ], - [ - -123, - -74 - ], - [ - -123, - -73 - ], - [ - -122, - -72 - ], - [ - -122, - -71 - ], - [ - -122, - -70 - ], - [ - -122, - -69 - ], - [ - -122, - -68 - ], - [ - -121, - -67 - ], - [ - -121, - -66 - ], - [ - -121, - -65 - ], - [ - -121, - -64 - ], - [ - -121, - -63 - ], - [ - -121, - -62 - ], - [ - -121, - -61 - ], - [ - -120, - -60 - ], - [ - -120, - -59 - ], - [ - -120, - -58 - ], - [ - -120, - -57 - ], - [ - -120, - -56 - ], - [ - -120, - -55 - ], - [ - -120, - -54 - ], - [ - -120, - -53 - ], - [ - -119, - -52 - ], - [ - -119, - -51 - ], - [ - -119, - -50 - ], - [ - -119, - -48 - ], - [ - -119, - -47 - ], - [ - -119, - -46 - ], - [ - -119, - -45 - ], - [ - -119, - -44 - ], - [ - -118, - -43 - ], - [ - -118, - -42 - ], - [ - -118, - -41 - ], - [ - -118, - -40 - ], - [ - -118, - -39 - ], - [ - -117, - -38 - ], - [ - -117, - -37 - ], - [ - -117, - -36 - ], - [ - -117, - -35 - ], - [ - -117, - -34 - ], - [ - -116, - -33 - ], - [ - -116, - -32 - ], - [ - -116, - -31 - ], - [ - -116, - -30 - ], - [ - -116, - -29 - ], - [ - -115, - -28 - ], - [ - -115, - -27 - ], - [ - -115, - -26 - ], - [ - -115, - -25 - ], - [ - -114, - -24 - ], - [ - -114, - -23 - ], - [ - -114, - -22 - ], - [ - -114, - -21 - ], - [ - -114, - -20 - ], - [ - -113, - -19 - ], - [ - -113, - -18 - ], - [ - -113, - -17 - ], - [ - -113, - -16 - ], - [ - -113, - -15 - ], - [ - -112, - -14 - ], - [ - -112, - -13 - ], - [ - -112, - -12 - ], - [ - -112, - -11 - ], - [ - -112, - -10 - ], - [ - -111, - -9 - ], - [ - -111, - -8 - ], - [ - -111, - -7 - ], - [ - -111, - -6 - ], - [ - -111, - -5 - ], - [ - -111, - -4 - ], - [ - -111, - -3 - ], - [ - -110, - -2 - ], - [ - -110, - -1 - ], - [ - -110, - 0 - ], - [ - -110, - 1 - ], - [ - -110, - 2 - ], - [ - -110, - 3 - ], - [ - -110, - 4 - ], - [ - -110, - 5 - ], - [ - -110, - 6 - ], - [ - -110, - 7 - ], - [ - -110, - 8 - ], - [ - -110, - 9 - ], - [ - -110, - 10 - ], - [ - -110, - 11 - ], - [ - -110, - 12 - ], - [ - -110, - 13 - ], - [ - -110, - 14 - ], - [ - -111, - 15 - ], - [ - -111, - 16 - ], - [ - -111, - 17 - ], - [ - -111, - 18 - ], - [ - -111, - 19 - ], - [ - -111, - 20 - ], - [ - -111, - 21 - ], - [ - -112, - 22 - ], - [ - -112, - 23 - ], - [ - -112, - 24 - ], - [ - -112, - 25 - ], - [ - -112, - 26 - ], - [ - -112, - 27 - ], - [ - -113, - 28 - ], - [ - -113, - 29 - ], - [ - -113, - 30 - ], - [ - -113, - 31 - ], - [ - -113, - 32 - ], - [ - -113, - 33 - ], - [ - -114, - 34 - ], - [ - -114, - 35 - ], - [ - -114, - 36 - ], - [ - -114, - 37 - ], - [ - -114, - 38 - ], - [ - -114, - 39 - ], - [ - -114, - 40 - ], - [ - -115, - 41 - ], - [ - -115, - 42 - ], - [ - -115, - 43 - ], - [ - -115, - 44 - ], - [ - -115, - 45 - ], - [ - -115, - 46 - ], - [ - -115, - 47 - ], - [ - -115, - 48 - ], - [ - -115, - 49 - ], - [ - -115, - 50 - ], - [ - -115, - 51 - ], - [ - -115, - 52 - ], - [ - -115, - 53 - ], - [ - -115, - 54 - ], - [ - -115, - 55 - ], - [ - -115, - 56 - ], - [ - -115, - 57 - ], - [ - -115, - 58 - ], - [ - -114, - 59 - ], - [ - -114, - 60 - ], - [ - -114, - 61 - ], - [ - -114, - 62 - ], - [ - -114, - 63 - ], - [ - -114, - 64 - ], - [ - -114, - 65 - ], - [ - -114, - 66 - ], - [ - -113, - 67 - ], - [ - -113, - 68 - ], - [ - -113, - 69 - ], - [ - -113, - 70 - ], - [ - -113, - 71 - ], - [ - -113, - 72 - ], - [ - -113, - 73 - ], - [ - -113, - 74 - ], - [ - -113, - 75 - ], - [ - -113, - 76 - ], - [ - -113, - 77 - ], - [ - -113, - 78 - ], - [ - -113, - 79 - ], - [ - -112, - 80 - ], - [ - -112, - 81 - ], - [ - -113, - 82 - ], - [ - -113, - 83 - ], - [ - -113, - 84 - ], - [ - -113, - 85 - ], - [ - -113, - 86 - ], - [ - -113, - 87 - ], - [ - -113, - 88 - ], - [ - -113, - 89 - ], - [ - -113, - 90 - ], - [ - -114, - 91 - ], - [ - -114, - 92 - ], - [ - -114, - 93 - ], - [ - -114, - 94 - ], - [ - -115, - 95 - ], - [ - -115, - 96 - ], - [ - -115, - 97 - ], - [ - -116, - 99 - ], - [ - -116, - 100 - ], - [ - -116, - 101 - ], - [ - -117, - 102 - ], - [ - -117, - 103 - ], - [ - -118, - 104 - ], - [ - -118, - 105 - ], - [ - -119, - 106 - ], - [ - -119, - 107 - ], - [ - -119, - 108 - ], - [ - -120, - 110 - ], - [ - -120, - 111 - ], - [ - -121, - 112 - ], - [ - -121, - 113 - ], - [ - -122, - 114 - ], - [ - -122, - 115 - ], - [ - -122, - 116 - ], - [ - -123, - 117 - ], - [ - -124, - 118 - ], - [ - -124, - 119 - ], - [ - -124, - 120 - ], - [ - -125, - 121 - ], - [ - -125, - 122 - ], - [ - -126, - 122 - ], - [ - -126, - 123 - ], - [ - -126, - 124 - ], - [ - -127, - 125 - ], - [ - -127, - 126 - ], - [ - -127, - 126 - ] - ], - "waypoints": [ - [ - -147, - -111 - ], - [ - -125, - -80 - ], - [ - -118, - -41 - ], - [ - -110, - 4 - ], - [ - -115, - 47 - ], - [ - -113, - 88 - ], - [ - -127, - 126 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 263.3, - "ground_min": 67.5, - "ground_max": 80.5, - "p95_raw_grade": 0.37, - "water_samples": 0 - } - }, - { - "id": "ring-market-north", - "name": "Market north street", - "points": [ - [ - -213, - 128 - ], - [ - -213, - 129 - ], - [ - -214, - 130 - ], - [ - -214, - 131 - ], - [ - -214, - 132 - ], - [ - -215, - 132 - ], - [ - -215, - 133 - ], - [ - -216, - 134 - ], - [ - -216, - 135 - ], - [ - -216, - 137 - ], - [ - -217, - 138 - ], - [ - -217, - 139 - ], - [ - -217, - 140 - ], - [ - -218, - 141 - ], - [ - -218, - 142 - ], - [ - -218, - 143 - ], - [ - -218, - 145 - ], - [ - -218, - 146 - ], - [ - -218, - 147 - ], - [ - -218, - 148 - ], - [ - -218, - 149 - ], - [ - -218, - 150 - ], - [ - -217, - 151 - ], - [ - -217, - 152 - ], - [ - -216, - 153 - ], - [ - -216, - 154 - ], - [ - -215, - 155 - ], - [ - -215, - 156 - ], - [ - -214, - 157 - ], - [ - -214, - 158 - ], - [ - -213, - 159 - ], - [ - -213, - 160 - ], - [ - -212, - 161 - ], - [ - -211, - 162 - ], - [ - -211, - 163 - ], - [ - -210, - 164 - ], - [ - -210, - 165 - ], - [ - -209, - 166 - ], - [ - -208, - 166 - ], - [ - -208, - 167 - ], - [ - -207, - 168 - ], - [ - -207, - 169 - ], - [ - -206, - 170 - ], - [ - -205, - 170 - ], - [ - -205, - 171 - ], - [ - -204, - 172 - ], - [ - -203, - 173 - ], - [ - -202, - 174 - ], - [ - -201, - 175 - ], - [ - -200, - 176 - ], - [ - -199, - 176 - ], - [ - -198, - 177 - ], - [ - -197, - 177 - ], - [ - -196, - 178 - ], - [ - -195, - 179 - ], - [ - -194, - 180 - ], - [ - -193, - 181 - ], - [ - -193, - 182 - ], - [ - -193, - 183 - ], - [ - -193, - 184 - ], - [ - -193, - 185 - ], - [ - -193, - 186 - ], - [ - -193, - 187 - ], - [ - -193, - 188 - ], - [ - -193, - 189 - ], - [ - -193, - 191 - ], - [ - -193, - 192 - ], - [ - -193, - 193 - ], - [ - -194, - 194 - ], - [ - -194, - 195 - ], - [ - -194, - 196 - ], - [ - -194, - 197 - ], - [ - -195, - 198 - ], - [ - -195, - 199 - ], - [ - -195, - 200 - ] - ], - "waypoints": [ - [ - -213, - 128 - ], - [ - -218, - 148 - ], - [ - -205, - 171 - ], - [ - -193, - 182 - ], - [ - -195, - 200 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 88.2, - "ground_min": 69.1, - "ground_max": 77.6, - "p95_raw_grade": 0.63, - "water_samples": 0 - } - }, - { - "id": "ring-market-south", - "name": "Market south street", - "points": [ - [ - -195, - 250 - ], - [ - -194, - 250 - ], - [ - -194, - 251 - ], - [ - -193, - 251 - ], - [ - -192, - 252 - ], - [ - -191, - 252 - ], - [ - -190, - 252 - ], - [ - -188, - 253 - ], - [ - -187, - 253 - ], - [ - -186, - 253 - ], - [ - -185, - 253 - ], - [ - -184, - 253 - ], - [ - -183, - 253 - ], - [ - -182, - 253 - ], - [ - -181, - 253 - ], - [ - -180, - 253 - ], - [ - -179, - 253 - ], - [ - -178, - 253 - ], - [ - -177, - 253 - ], - [ - -176, - 253 - ], - [ - -175, - 253 - ], - [ - -174, - 253 - ], - [ - -172, - 253 - ], - [ - -171, - 252 - ], - [ - -170, - 252 - ], - [ - -169, - 252 - ], - [ - -168, - 251 - ], - [ - -167, - 251 - ], - [ - -166, - 251 - ], - [ - -165, - 250 - ], - [ - -164, - 250 - ], - [ - -163, - 250 - ], - [ - -162, - 249 - ], - [ - -161, - 249 - ], - [ - -160, - 248 - ], - [ - -159, - 248 - ], - [ - -158, - 247 - ], - [ - -156, - 247 - ], - [ - -155, - 246 - ], - [ - -154, - 246 - ], - [ - -153, - 246 - ], - [ - -152, - 245 - ], - [ - -151, - 245 - ], - [ - -150, - 244 - ], - [ - -149, - 244 - ], - [ - -148, - 243 - ], - [ - -147, - 243 - ], - [ - -146, - 242 - ], - [ - -145, - 242 - ] - ], - "waypoints": [ - [ - -195, - 250 - ], - [ - -187, - 253 - ], - [ - -170, - 252 - ], - [ - -145, - 242 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 56.2, - "ground_min": 61.8, - "ground_max": 65.9, - "p95_raw_grade": 0.3, - "water_samples": 0 - } - }, - { - "id": "ring-south", - "name": "South inner promenade", - "points": [ - [ - -72, - 242 - ], - [ - -71, - 242 - ], - [ - -70, - 242 - ], - [ - -69, - 242 - ], - [ - -68, - 242 - ], - [ - -67, - 241 - ], - [ - -66, - 241 - ], - [ - -65, - 241 - ], - [ - -64, - 241 - ], - [ - -63, - 241 - ], - [ - -62, - 241 - ], - [ - -61, - 241 - ], - [ - -60, - 240 - ], - [ - -59, - 240 - ], - [ - -58, - 240 - ], - [ - -57, - 240 - ], - [ - -56, - 240 - ], - [ - -55, - 240 - ], - [ - -53, - 240 - ], - [ - -52, - 240 - ], - [ - -51, - 239 - ], - [ - -50, - 239 - ], - [ - -49, - 239 - ], - [ - -48, - 239 - ], - [ - -46, - 239 - ], - [ - -45, - 239 - ], - [ - -44, - 239 - ], - [ - -43, - 239 - ], - [ - -42, - 238 - ], - [ - -41, - 238 - ], - [ - -39, - 238 - ], - [ - -38, - 238 - ], - [ - -37, - 238 - ], - [ - -36, - 238 - ], - [ - -35, - 238 - ], - [ - -34, - 238 - ], - [ - -33, - 238 - ], - [ - -32, - 238 - ], - [ - -31, - 238 - ], - [ - -30, - 238 - ], - [ - -29, - 238 - ], - [ - -28, - 238 - ], - [ - -27, - 238 - ], - [ - -26, - 238 - ], - [ - -25, - 238 - ], - [ - -24, - 238 - ], - [ - -23, - 238 - ], - [ - -22, - 238 - ], - [ - -21, - 238 - ], - [ - -20, - 238 - ], - [ - -19, - 238 - ], - [ - -18, - 238 - ], - [ - -16, - 238 - ], - [ - -15, - 238 - ], - [ - -14, - 238 - ], - [ - -13, - 238 - ], - [ - -12, - 238 - ], - [ - -11, - 239 - ], - [ - -10, - 239 - ], - [ - -9, - 239 - ], - [ - -8, - 239 - ], - [ - -7, - 239 - ], - [ - -6, - 239 - ], - [ - -5, - 239 - ], - [ - -4, - 239 - ], - [ - -3, - 238 - ], - [ - -2, - 238 - ], - [ - -1, - 238 - ], - [ - 0, - 238 - ], - [ - 1, - 238 - ], - [ - 2, - 238 - ], - [ - 3, - 238 - ], - [ - 4, - 238 - ], - [ - 5, - 238 - ], - [ - 6, - 238 - ], - [ - 6, - 237 - ], - [ - 7, - 237 - ], - [ - 8, - 237 - ], - [ - 9, - 237 - ], - [ - 10, - 236 - ], - [ - 11, - 236 - ], - [ - 12, - 235 - ], - [ - 13, - 235 - ], - [ - 14, - 234 - ], - [ - 15, - 234 - ], - [ - 16, - 233 - ], - [ - 17, - 233 - ], - [ - 18, - 232 - ], - [ - 19, - 231 - ], - [ - 20, - 230 - ], - [ - 21, - 229 - ], - [ - 22, - 229 - ], - [ - 22, - 228 - ], - [ - 23, - 227 - ], - [ - 24, - 226 - ], - [ - 25, - 225 - ], - [ - 26, - 224 - ], - [ - 26, - 223 - ], - [ - 27, - 223 - ], - [ - 28, - 222 - ], - [ - 28, - 221 - ], - [ - 29, - 220 - ], - [ - 30, - 219 - ], - [ - 31, - 218 - ], - [ - 32, - 217 - ], - [ - 32, - 216 - ], - [ - 33, - 216 - ], - [ - 34, - 215 - ], - [ - 34, - 214 - ], - [ - 35, - 213 - ], - [ - 35, - 212 - ], - [ - 36, - 212 - ], - [ - 36, - 211 - ], - [ - 37, - 210 - ], - [ - 38, - 209 - ], - [ - 38, - 208 - ], - [ - 39, - 207 - ], - [ - 40, - 206 - ], - [ - 40, - 205 - ], - [ - 41, - 204 - ], - [ - 41, - 203 - ], - [ - 42, - 202 - ], - [ - 42, - 201 - ], - [ - 43, - 200 - ], - [ - 43, - 199 - ], - [ - 44, - 199 - ], - [ - 44, - 198 - ], - [ - 45, - 197 - ], - [ - 45, - 196 - ], - [ - 46, - 195 - ], - [ - 46, - 194 - ], - [ - 46, - 193 - ], - [ - 47, - 192 - ], - [ - 47, - 191 - ], - [ - 48, - 190 - ], - [ - 48, - 189 - ], - [ - 49, - 188 - ], - [ - 49, - 187 - ], - [ - 50, - 186 - ], - [ - 50, - 185 - ], - [ - 51, - 184 - ], - [ - 51, - 183 - ], - [ - 52, - 182 - ], - [ - 52, - 181 - ], - [ - 53, - 180 - ], - [ - 53, - 178 - ], - [ - 54, - 177 - ], - [ - 54, - 176 - ], - [ - 55, - 175 - ], - [ - 55, - 174 - ], - [ - 56, - 173 - ], - [ - 56, - 172 - ], - [ - 57, - 172 - ], - [ - 57, - 171 - ], - [ - 57, - 170 - ], - [ - 58, - 169 - ], - [ - 58, - 168 - ], - [ - 59, - 167 - ], - [ - 59, - 166 - ], - [ - 59, - 166 - ] - ], - "waypoints": [ - [ - -72, - 242 - ], - [ - -36, - 238 - ], - [ - 8, - 237 - ], - [ - 31, - 218 - ], - [ - 46, - 194 - ], - [ - 59, - 166 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 182.1, - "ground_min": 62.8, - "ground_max": 67.8, - "p95_raw_grade": 0.24, - "water_samples": 0 - } - }, - { - "id": "ring-garden-south", - "name": "Garden south promenade", - "points": [ - [ - 143, - 166 - ], - [ - 144, - 167 - ], - [ - 145, - 167 - ], - [ - 146, - 168 - ], - [ - 147, - 168 - ], - [ - 148, - 168 - ], - [ - 148, - 169 - ], - [ - 149, - 169 - ], - [ - 150, - 170 - ], - [ - 151, - 170 - ], - [ - 152, - 171 - ], - [ - 153, - 172 - ], - [ - 154, - 172 - ], - [ - 155, - 173 - ], - [ - 156, - 173 - ], - [ - 157, - 174 - ], - [ - 158, - 174 - ], - [ - 159, - 175 - ], - [ - 160, - 175 - ], - [ - 161, - 176 - ], - [ - 163, - 176 - ], - [ - 164, - 177 - ], - [ - 165, - 177 - ], - [ - 166, - 178 - ], - [ - 167, - 178 - ], - [ - 168, - 178 - ], - [ - 169, - 178 - ], - [ - 170, - 179 - ], - [ - 171, - 179 - ], - [ - 172, - 179 - ], - [ - 173, - 179 - ], - [ - 174, - 179 - ], - [ - 175, - 179 - ], - [ - 176, - 179 - ], - [ - 177, - 179 - ], - [ - 178, - 179 - ], - [ - 179, - 179 - ], - [ - 180, - 179 - ], - [ - 181, - 179 - ], - [ - 182, - 179 - ], - [ - 184, - 178 - ], - [ - 185, - 178 - ], - [ - 186, - 178 - ], - [ - 187, - 178 - ], - [ - 188, - 177 - ], - [ - 189, - 177 - ], - [ - 190, - 177 - ], - [ - 191, - 177 - ], - [ - 192, - 176 - ], - [ - 193, - 176 - ], - [ - 194, - 176 - ], - [ - 195, - 175 - ], - [ - 196, - 175 - ], - [ - 197, - 175 - ], - [ - 198, - 174 - ], - [ - 199, - 174 - ], - [ - 200, - 173 - ], - [ - 201, - 173 - ], - [ - 202, - 172 - ], - [ - 203, - 172 - ], - [ - 204, - 171 - ], - [ - 205, - 171 - ], - [ - 206, - 170 - ], - [ - 207, - 169 - ], - [ - 208, - 169 - ], - [ - 209, - 168 - ], - [ - 210, - 167 - ], - [ - 211, - 166 - ], - [ - 211, - 165 - ], - [ - 212, - 164 - ], - [ - 213, - 164 - ], - [ - 213, - 163 - ], - [ - 214, - 162 - ], - [ - 215, - 161 - ], - [ - 215, - 160 - ], - [ - 216, - 160 - ], - [ - 216, - 159 - ], - [ - 217, - 158 - ], - [ - 218, - 157 - ], - [ - 218, - 157 - ] - ], - "waypoints": [ - [ - 143, - 166 - ], - [ - 172, - 179 - ], - [ - 203, - 172 - ], - [ - 218, - 157 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 92.2, - "ground_min": 71.9, - "ground_max": 83.8, - "p95_raw_grade": 0.36, - "water_samples": 0 - } - }, - { - "id": "ring-gardens", - "name": "Garden promenade", - "points": [ - [ - 218, - 137 - ], - [ - 217, - 136 - ], - [ - 217, - 135 - ], - [ - 216, - 134 - ], - [ - 216, - 133 - ], - [ - 215, - 132 - ], - [ - 215, - 131 - ], - [ - 214, - 130 - ], - [ - 213, - 129 - ], - [ - 213, - 128 - ], - [ - 212, - 128 - ], - [ - 211, - 126 - ], - [ - 211, - 125 - ], - [ - 210, - 124 - ], - [ - 209, - 124 - ], - [ - 209, - 123 - ], - [ - 208, - 122 - ], - [ - 207, - 121 - ], - [ - 207, - 120 - ], - [ - 206, - 119 - ], - [ - 205, - 118 - ], - [ - 204, - 117 - ], - [ - 203, - 116 - ], - [ - 202, - 116 - ], - [ - 201, - 115 - ], - [ - 200, - 115 - ], - [ - 199, - 114 - ], - [ - 198, - 114 - ], - [ - 197, - 114 - ], - [ - 196, - 113 - ], - [ - 195, - 113 - ], - [ - 194, - 113 - ], - [ - 193, - 113 - ], - [ - 192, - 112 - ], - [ - 191, - 112 - ], - [ - 190, - 112 - ], - [ - 189, - 111 - ], - [ - 188, - 111 - ], - [ - 188, - 110 - ], - [ - 187, - 110 - ], - [ - 186, - 109 - ], - [ - 185, - 108 - ], - [ - 184, - 107 - ], - [ - 184, - 106 - ], - [ - 183, - 105 - ], - [ - 182, - 104 - ], - [ - 181, - 103 - ], - [ - 181, - 102 - ], - [ - 180, - 101 - ], - [ - 180, - 100 - ], - [ - 179, - 99 - ], - [ - 179, - 98 - ], - [ - 178, - 97 - ], - [ - 178, - 96 - ], - [ - 177, - 95 - ], - [ - 177, - 94 - ], - [ - 177, - 93 - ], - [ - 177, - 92 - ], - [ - 176, - 91 - ], - [ - 176, - 90 - ], - [ - 176, - 89 - ], - [ - 176, - 88 - ], - [ - 176, - 87 - ], - [ - 176, - 86 - ], - [ - 176, - 85 - ], - [ - 176, - 84 - ], - [ - 176, - 83 - ], - [ - 177, - 82 - ], - [ - 177, - 81 - ], - [ - 177, - 80 - ], - [ - 177, - 79 - ], - [ - 178, - 78 - ], - [ - 178, - 77 - ], - [ - 178, - 76 - ], - [ - 179, - 75 - ], - [ - 179, - 74 - ], - [ - 180, - 73 - ], - [ - 180, - 72 - ], - [ - 180, - 71 - ], - [ - 181, - 71 - ], - [ - 181, - 70 - ], - [ - 182, - 69 - ], - [ - 182, - 68 - ], - [ - 183, - 67 - ], - [ - 183, - 66 - ], - [ - 184, - 65 - ], - [ - 185, - 64 - ], - [ - 185, - 63 - ], - [ - 186, - 62 - ], - [ - 187, - 61 - ], - [ - 188, - 60 - ], - [ - 189, - 59 - ], - [ - 189, - 58 - ], - [ - 190, - 57 - ], - [ - 191, - 56 - ], - [ - 192, - 55 - ], - [ - 193, - 54 - ], - [ - 194, - 53 - ], - [ - 195, - 52 - ], - [ - 196, - 51 - ], - [ - 197, - 51 - ], - [ - 197, - 50 - ], - [ - 198, - 49 - ], - [ - 199, - 49 - ], - [ - 199, - 48 - ], - [ - 200, - 47 - ], - [ - 201, - 46 - ], - [ - 201, - 46 - ] - ], - "waypoints": [ - [ - 218, - 137 - ], - [ - 204, - 117 - ], - [ - 186, - 109 - ], - [ - 176, - 88 - ], - [ - 184, - 65 - ], - [ - 201, - 46 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 127.9, - "ground_min": 82.0, - "ground_max": 87.2, - "p95_raw_grade": 0.47, - "water_samples": 0 - } - }, - { - "id": "ring-harbor", - "name": "Harbor garden promenade", - "points": [ - [ - 185, - -9 - ], - [ - 185, - -10 - ], - [ - 186, - -11 - ], - [ - 186, - -12 - ], - [ - 187, - -13 - ], - [ - 187, - -14 - ], - [ - 188, - -15 - ], - [ - 188, - -16 - ], - [ - 188, - -17 - ], - [ - 189, - -18 - ], - [ - 189, - -19 - ], - [ - 190, - -20 - ], - [ - 190, - -21 - ], - [ - 190, - -22 - ], - [ - 191, - -23 - ], - [ - 191, - -24 - ], - [ - 192, - -25 - ], - [ - 192, - -26 - ], - [ - 193, - -27 - ], - [ - 193, - -29 - ], - [ - 193, - -30 - ], - [ - 194, - -31 - ], - [ - 194, - -32 - ], - [ - 195, - -33 - ], - [ - 195, - -34 - ], - [ - 195, - -35 - ], - [ - 196, - -36 - ], - [ - 196, - -37 - ], - [ - 196, - -38 - ], - [ - 196, - -39 - ], - [ - 197, - -40 - ], - [ - 197, - -41 - ], - [ - 197, - -42 - ], - [ - 197, - -43 - ], - [ - 197, - -44 - ], - [ - 197, - -45 - ], - [ - 197, - -46 - ], - [ - 197, - -47 - ], - [ - 197, - -48 - ], - [ - 197, - -49 - ], - [ - 197, - -50 - ], - [ - 197, - -52 - ], - [ - 197, - -53 - ], - [ - 197, - -54 - ], - [ - 197, - -55 - ], - [ - 196, - -56 - ], - [ - 196, - -57 - ], - [ - 196, - -58 - ], - [ - 196, - -59 - ], - [ - 196, - -60 - ], - [ - 195, - -61 - ], - [ - 195, - -62 - ], - [ - 195, - -63 - ], - [ - 195, - -64 - ], - [ - 194, - -65 - ], - [ - 194, - -66 - ], - [ - 193, - -67 - ], - [ - 193, - -68 - ], - [ - 193, - -69 - ], - [ - 193, - -70 - ], - [ - 192, - -71 - ], - [ - 192, - -72 - ], - [ - 192, - -73 - ], - [ - 191, - -74 - ], - [ - 191, - -75 - ], - [ - 190, - -76 - ], - [ - 190, - -77 - ], - [ - 190, - -78 - ], - [ - 189, - -80 - ], - [ - 189, - -81 - ], - [ - 188, - -82 - ], - [ - 188, - -83 - ], - [ - 187, - -84 - ], - [ - 186, - -85 - ], - [ - 186, - -86 - ], - [ - 185, - -87 - ], - [ - 185, - -88 - ], - [ - 184, - -89 - ], - [ - 184, - -90 - ], - [ - 183, - -90 - ], - [ - 183, - -91 - ], - [ - 183, - -92 - ], - [ - 182, - -93 - ], - [ - 182, - -94 - ], - [ - 181, - -95 - ], - [ - 181, - -95 - ] - ], - "waypoints": [ - [ - 185, - -9 - ], - [ - 197, - -43 - ], - [ - 192, - -72 - ], - [ - 181, - -95 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 98.0, - "ground_min": 66.0, - "ground_max": 79.9, - "p95_raw_grade": 0.43, - "water_samples": 0 - } - }, - { - "id": "ring-northeast", - "name": "Station to east bridge", - "points": [ - [ - 40, - -95 - ], - [ - 41, - -95 - ], - [ - 42, - -95 - ], - [ - 43, - -95 - ], - [ - 44, - -94 - ], - [ - 45, - -94 - ], - [ - 46, - -94 - ], - [ - 48, - -94 - ], - [ - 49, - -94 - ], - [ - 50, - -94 - ], - [ - 51, - -93 - ], - [ - 52, - -93 - ], - [ - 53, - -93 - ], - [ - 54, - -93 - ], - [ - 56, - -92 - ], - [ - 57, - -92 - ], - [ - 58, - -92 - ], - [ - 59, - -91 - ], - [ - 60, - -91 - ], - [ - 61, - -91 - ], - [ - 62, - -90 - ], - [ - 63, - -90 - ], - [ - 64, - -89 - ], - [ - 65, - -89 - ], - [ - 66, - -88 - ], - [ - 67, - -88 - ], - [ - 68, - -87 - ], - [ - 69, - -86 - ], - [ - 70, - -86 - ], - [ - 71, - -85 - ], - [ - 72, - -85 - ], - [ - 73, - -84 - ], - [ - 74, - -83 - ], - [ - 75, - -83 - ], - [ - 76, - -82 - ], - [ - 77, - -82 - ], - [ - 77, - -81 - ], - [ - 78, - -80 - ], - [ - 79, - -80 - ], - [ - 80, - -79 - ], - [ - 81, - -79 - ], - [ - 82, - -78 - ], - [ - 82, - -78 - ] - ], - "waypoints": [ - [ - 40, - -95 - ], - [ - 60, - -91 - ], - [ - 82, - -78 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 49.4, - "ground_min": 83.4, - "ground_max": 92.1, - "p95_raw_grade": 0.38, - "water_samples": 0 - } - }, - { - "id": "harbor-bridge-landing", - "name": "Harbor bridge landing", - "points": [ - [ - 155, - -81 - ], - [ - 156, - -81 - ], - [ - 157, - -81 - ], - [ - 158, - -81 - ], - [ - 159, - -81 - ], - [ - 160, - -81 - ], - [ - 161, - -81 - ], - [ - 162, - -81 - ], - [ - 164, - -81 - ], - [ - 165, - -81 - ], - [ - 166, - -82 - ], - [ - 167, - -82 - ], - [ - 168, - -82 - ], - [ - 169, - -82 - ], - [ - 170, - -83 - ], - [ - 171, - -83 - ], - [ - 172, - -84 - ], - [ - 173, - -84 - ], - [ - 173, - -85 - ], - [ - 174, - -86 - ], - [ - 175, - -87 - ], - [ - 176, - -88 - ], - [ - 176, - -89 - ], - [ - 177, - -90 - ], - [ - 177, - -91 - ], - [ - 178, - -92 - ], - [ - 179, - -93 - ], - [ - 179, - -94 - ], - [ - 180, - -95 - ], - [ - 180, - -95 - ] - ], - "waypoints": [ - [ - 155, - -81 - ], - [ - 171, - -83 - ], - [ - 180, - -95 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 33.1, - "ground_min": 65.7, - "ground_max": 69.5, - "p95_raw_grade": 0.32, - "water_samples": 0 - } - }, - { - "id": "garden-bridge-landing", - "name": "Garden bridge landing", - "points": [ - [ - 151, - 8 - ], - [ - 152, - 9 - ], - [ - 152, - 10 - ], - [ - 153, - 11 - ], - [ - 154, - 12 - ], - [ - 155, - 12 - ], - [ - 155, - 13 - ], - [ - 156, - 14 - ], - [ - 156, - 15 - ], - [ - 157, - 16 - ], - [ - 158, - 17 - ], - [ - 158, - 18 - ], - [ - 159, - 19 - ], - [ - 159, - 21 - ], - [ - 159, - 22 - ], - [ - 160, - 22 - ], - [ - 160, - 23 - ] - ], - "waypoints": [ - [ - 151, - 8 - ], - [ - 157, - 16 - ], - [ - 160, - 23 - ] - ], - "width": 7, - "role": "main", - "analysis": { - "length": 19.9, - "ground_min": 83.7, - "ground_max": 85.1, - "p95_raw_grade": 0.22, - "water_samples": 0 - } - }, - { - "id": "market-shop-street", - "name": "Market north street", - "points": [ - [ - -207, - 192 - ], - [ - -207, - 193 - ], - [ - -206, - 194 - ], - [ - -206, - 195 - ], - [ - -206, - 196 - ], - [ - -205, - 197 - ], - [ - -205, - 198 - ], - [ - -204, - 199 - ], - [ - -204, - 200 - ], - [ - -203, - 201 - ], - [ - -203, - 202 - ] - ], - "waypoints": [ - [ - -207, - 192 - ], - [ - -205, - 198 - ], - [ - -203, - 202 - ] - ], - "width": 5, - "role": "secondary", - "analysis": { - "length": 11.7, - "ground_min": 69.9, - "ground_max": 71.8, - "p95_raw_grade": 0.31, - "water_samples": 0 - } - }, - { - "id": "market-south-shop-street", - "name": "Market south shops", - "points": [ - [ - -187, - 253 - ], - [ - -186, - 253 - ], - [ - -186, - 254 - ], - [ - -185, - 254 - ], - [ - -184, - 255 - ], - [ - -182, - 256 - ], - [ - -181, - 257 - ], - [ - -180, - 257 - ], - [ - -179, - 258 - ], - [ - -178, - 259 - ], - [ - -177, - 260 - ], - [ - -178, - 261 - ], - [ - -178, - 262 - ] - ], - "waypoints": [ - [ - -187, - 253 - ], - [ - -178, - 259 - ], - [ - -178, - 262 - ] - ], - "width": 5, - "role": "secondary", - "analysis": { - "length": 15.7, - "ground_min": 62.0, - "ground_max": 62.7, - "p95_raw_grade": 0.26, - "water_samples": 0 - } - }, - { - "id": "market-workshop-street", - "name": "Workshop approach", - "points": [ - [ - -198, - 250 - ], - [ - -199, - 250 - ], - [ - -199, - 251 - ], - [ - -200, - 251 - ], - [ - -201, - 251 - ], - [ - -203, - 252 - ], - [ - -204, - 252 - ], - [ - -205, - 253 - ], - [ - -206, - 253 - ], - [ - -207, - 254 - ], - [ - -208, - 255 - ], - [ - -209, - 255 - ], - [ - -209, - 256 - ], - [ - -210, - 257 - ], - [ - -211, - 258 - ], - [ - -212, - 259 - ], - [ - -212, - 260 - ], - [ - -213, - 261 - ], - [ - -214, - 262 - ], - [ - -214, - 262 - ] - ], - "waypoints": [ - [ - -198, - 250 - ], - [ - -207, - 254 - ], - [ - -214, - 262 - ] - ], - "width": 5, - "role": "secondary", - "analysis": { - "length": 22.5, - "ground_min": 61.2, - "ground_max": 62.2, - "p95_raw_grade": 0.15, - "water_samples": 0 - } - }, - { - "id": "market-guild-street", - "name": "Guild approach", - "points": [ - [ - -216, - 231 - ], - [ - -217, - 231 - ], - [ - -217, - 232 - ], - [ - -218, - 233 - ], - [ - -219, - 233 - ], - [ - -220, - 234 - ], - [ - -221, - 235 - ], - [ - -222, - 236 - ] - ], - "waypoints": [ - [ - -216, - 231 - ], - [ - -220, - 234 - ], - [ - -222, - 236 - ] - ], - "width": 5, - "role": "secondary", - "analysis": { - "length": 8.7, - "ground_min": 63.1, - "ground_max": 64.1, - "p95_raw_grade": 0.26, - "water_samples": 0 - } - }, - { - "id": "market-teahouse-street", - "name": "Tea house approach", - "points": [ - [ - -173, - 229 - ], - [ - -172, - 229 - ], - [ - -171, - 229 - ] - ], - "waypoints": [ - [ - -173, - 229 - ], - [ - -171, - 229 - ] - ], - "width": 5, - "role": "secondary", - "analysis": { - "length": 2.0, - "ground_min": 63.8, - "ground_max": 64.1, - "p95_raw_grade": 0.14, - "water_samples": 0 - } - }, - { - "id": "market-crafts-street", - "name": "Crafts approach", - "points": [ - [ - -184, - 203 - ], - [ - -183, - 203 - ], - [ - -182, - 204 - ], - [ - -181, - 205 - ], - [ - -180, - 206 - ], - [ - -179, - 206 - ] - ], - "waypoints": [ - [ - -184, - 203 - ], - [ - -179, - 206 - ] - ], - "width": 5, - "role": "secondary", - "analysis": { - "length": 6.2, - "ground_min": 65.8, - "ground_max": 66.7, - "p95_raw_grade": 0.33, - "water_samples": 0 - } - }, - { - "id": "waterworks-connector", - "name": "Waterworks approach", - "points": [ - [ - -114, - 44 - ], - [ - -115, - 44 - ], - [ - -117, - 44 - ], - [ - -118, - 44 - ], - [ - -119, - 44 - ], - [ - -120, - 44 - ], - [ - -121, - 44 - ], - [ - -122, - 44 - ], - [ - -123, - 44 - ], - [ - -124, - 44 - ], - [ - -126, - 44 - ], - [ - -127, - 43 - ], - [ - -128, - 43 - ] - ], - "waypoints": [ - [ - -114, - 44 - ], - [ - -122, - 44 - ], - [ - -128, - 43 - ] - ], - "width": 5, - "role": "secondary", - "analysis": { - "length": 14.4, - "ground_min": 71.9, - "ground_max": 76.0, - "p95_raw_grade": 0.49, - "water_samples": 0 - } - }, - { - "id": "waterworks-lookout-path", - "name": "Waterworks viewing path", - "points": [ - [ - -135, - 52 - ], - [ - -135, - 53 - ], - [ - -135, - 54 - ], - [ - -135, - 55 - ], - [ - -135, - 56 - ], - [ - -135, - 57 - ], - [ - -135, - 58 - ], - [ - -135, - 59 - ] - ], - "waypoints": [ - [ - -135, - 52 - ], - [ - -135, - 59 - ] - ], - "width": 3, - "role": "secondary", - "analysis": { - "length": 7.0, - "ground_min": 71.0, - "ground_max": 71.3, - "p95_raw_grade": 0.12, - "water_samples": 0 - } - }, - { - "id": "bridge-northeast", - "name": "Clock / harbor bridge", - "points": [ - [ - 82, - -78 - ], - [ - 83, - -78 - ], - [ - 84, - -78 - ], - [ - 85, - -78 - ], - [ - 86, - -78 - ], - [ - 87, - -78 - ], - [ - 88, - -78 - ], - [ - 89, - -78 - ], - [ - 90, - -78 - ], - [ - 91, - -78 - ], - [ - 92, - -78 - ], - [ - 93, - -78 - ], - [ - 94, - -79 - ], - [ - 96, - -79 - ], - [ - 97, - -79 - ], - [ - 98, - -79 - ], - [ - 99, - -79 - ], - [ - 100, - -79 - ], - [ - 101, - -79 - ], - [ - 102, - -79 - ], - [ - 103, - -79 - ], - [ - 104, - -79 - ], - [ - 106, - -79 - ], - [ - 107, - -79 - ], - [ - 108, - -79 - ], - [ - 109, - -79 - ], - [ - 110, - -79 - ], - [ - 112, - -79 - ], - [ - 113, - -79 - ], - [ - 114, - -79 - ], - [ - 115, - -79 - ], - [ - 117, - -79 - ], - [ - 118, - -79 - ], - [ - 119, - -80 - ], - [ - 120, - -80 - ], - [ - 122, - -80 - ], - [ - 123, - -80 - ], - [ - 124, - -80 - ], - [ - 125, - -80 - ], - [ - 127, - -80 - ], - [ - 128, - -80 - ], - [ - 129, - -80 - ], - [ - 130, - -80 - ], - [ - 131, - -80 - ], - [ - 133, - -80 - ], - [ - 134, - -80 - ], - [ - 135, - -80 - ], - [ - 136, - -80 - ], - [ - 137, - -80 - ], - [ - 138, - -80 - ], - [ - 139, - -80 - ], - [ - 140, - -80 - ], - [ - 141, - -80 - ], - [ - 143, - -80 - ], - [ - 144, - -81 - ], - [ - 145, - -81 - ], - [ - 146, - -81 - ], - [ - 147, - -81 - ], - [ - 148, - -81 - ], - [ - 149, - -81 - ], - [ - 150, - -81 - ], - [ - 151, - -81 - ], - [ - 152, - -81 - ], - [ - 153, - -81 - ], - [ - 154, - -81 - ], - [ - 155, - -81 - ] - ], - "waypoints": [ - [ - 82, - -78 - ], - [ - 155, - -81 - ] - ], - "width": 7, - "role": "bridge", - "deck_y": 86, - "minimum_ground": 32.99586868286133, - "endpoint_ground": [ - 83.44853973388672, - 69.5047836303711 - ], - "intent": "Mark both parapet alignments and abutments; preserve the river and banks.", - "abutments": [ - { - "point": [ - 82, - -78 - ], - "ground_y": 83.4, - "deck_y": 86, - "future_stairs": false, - "rise": 3 - }, - { - "point": [ - 155, - -81 - ], - "ground_y": 69.5, - "deck_y": 86, - "future_stairs": true, - "rise": 17 - } - ], - "analysis": { - "length": 74.2, - "ground_min": 33.0, - "ground_max": 83.4, - "p95_raw_grade": 2.81, - "water_samples": 23 - } - }, - { - "id": "bridge-east", - "name": "Spawn / garden bridge", - "points": [ - [ - 82, - 8 - ], - [ - 83, - 8 - ], - [ - 84, - 8 - ], - [ - 85, - 8 - ], - [ - 86, - 8 - ], - [ - 87, - 8 - ], - [ - 88, - 8 - ], - [ - 89, - 8 - ], - [ - 90, - 8 - ], - [ - 91, - 8 - ], - [ - 92, - 8 - ], - [ - 93, - 8 - ], - [ - 94, - 8 - ], - [ - 95, - 8 - ], - [ - 96, - 8 - ], - [ - 97, - 8 - ], - [ - 98, - 8 - ], - [ - 99, - 8 - ], - [ - 100, - 8 - ], - [ - 101, - 8 - ], - [ - 102, - 8 - ], - [ - 104, - 8 - ], - [ - 105, - 8 - ], - [ - 106, - 8 - ], - [ - 107, - 8 - ], - [ - 108, - 8 - ], - [ - 110, - 8 - ], - [ - 111, - 8 - ], - [ - 112, - 8 - ], - [ - 113, - 8 - ], - [ - 115, - 8 - ], - [ - 116, - 8 - ], - [ - 117, - 8 - ], - [ - 118, - 8 - ], - [ - 120, - 8 - ], - [ - 121, - 8 - ], - [ - 122, - 8 - ], - [ - 123, - 8 - ], - [ - 125, - 8 - ], - [ - 126, - 8 - ], - [ - 127, - 8 - ], - [ - 128, - 8 - ], - [ - 129, - 8 - ], - [ - 131, - 8 - ], - [ - 132, - 8 - ], - [ - 133, - 8 - ], - [ - 134, - 8 - ], - [ - 135, - 8 - ], - [ - 136, - 8 - ], - [ - 137, - 8 - ], - [ - 138, - 8 - ], - [ - 139, - 8 - ], - [ - 140, - 8 - ], - [ - 141, - 8 - ], - [ - 142, - 8 - ], - [ - 143, - 8 - ], - [ - 144, - 8 - ], - [ - 145, - 8 - ], - [ - 146, - 8 - ], - [ - 147, - 8 - ], - [ - 148, - 8 - ], - [ - 149, - 8 - ], - [ - 150, - 8 - ], - [ - 151, - 8 - ] - ], - "waypoints": [ - [ - 82, - 8 - ], - [ - 151, - 8 - ] - ], - "width": 7, - "role": "bridge", - "deck_y": 87, - "minimum_ground": 33.16427230834961, - "endpoint_ground": [ - 84.41950225830078, - 84.60459899902344 - ], - "intent": "Mark both parapet alignments and abutments; preserve the river and banks.", - "abutments": [ - { - "point": [ - 82, - 8 - ], - "ground_y": 84.4, - "deck_y": 87, - "future_stairs": false, - "rise": 3 - }, - { - "point": [ - 151, - 8 - ], - "ground_y": 84.6, - "deck_y": 87, - "future_stairs": false, - "rise": 3 - } - ], - "analysis": { - "length": 69.0, - "ground_min": 33.2, - "ground_max": 85.8, - "p95_raw_grade": 3.87, - "water_samples": 15 - } - }, - { - "id": "bridge-southeast", - "name": "Garden / south bridge", - "points": [ - [ - 59, - 166 - ], - [ - 60, - 166 - ], - [ - 61, - 166 - ], - [ - 62, - 166 - ], - [ - 63, - 166 - ], - [ - 64, - 166 - ], - [ - 65, - 166 - ], - [ - 66, - 166 - ], - [ - 67, - 166 - ], - [ - 68, - 166 - ], - [ - 69, - 166 - ], - [ - 70, - 166 - ], - [ - 71, - 166 - ], - [ - 72, - 166 - ], - [ - 73, - 166 - ], - [ - 74, - 166 - ], - [ - 75, - 166 - ], - [ - 76, - 166 - ], - [ - 77, - 166 - ], - [ - 78, - 166 - ], - [ - 79, - 166 - ], - [ - 80, - 166 - ], - [ - 82, - 166 - ], - [ - 83, - 166 - ], - [ - 84, - 166 - ], - [ - 85, - 166 - ], - [ - 86, - 166 - ], - [ - 87, - 166 - ], - [ - 89, - 166 - ], - [ - 90, - 166 - ], - [ - 91, - 166 - ], - [ - 92, - 166 - ], - [ - 94, - 166 - ], - [ - 95, - 166 - ], - [ - 96, - 166 - ], - [ - 97, - 166 - ], - [ - 99, - 166 - ], - [ - 100, - 166 - ], - [ - 101, - 166 - ], - [ - 102, - 166 - ], - [ - 103, - 166 - ], - [ - 105, - 166 - ], - [ - 106, - 166 - ], - [ - 107, - 166 - ], - [ - 108, - 166 - ], - [ - 110, - 166 - ], - [ - 111, - 166 - ], - [ - 112, - 166 - ], - [ - 113, - 166 - ], - [ - 115, - 166 - ], - [ - 116, - 166 - ], - [ - 117, - 166 - ], - [ - 118, - 166 - ], - [ - 119, - 166 - ], - [ - 120, - 166 - ], - [ - 122, - 166 - ], - [ - 123, - 166 - ], - [ - 124, - 166 - ], - [ - 125, - 166 - ], - [ - 126, - 166 - ], - [ - 127, - 166 - ], - [ - 128, - 166 - ], - [ - 129, - 166 - ], - [ - 130, - 166 - ], - [ - 131, - 166 - ], - [ - 132, - 166 - ], - [ - 133, - 166 - ], - [ - 134, - 166 - ], - [ - 135, - 166 - ], - [ - 136, - 166 - ], - [ - 137, - 166 - ], - [ - 138, - 166 - ], - [ - 139, - 166 - ], - [ - 140, - 166 - ], - [ - 141, - 166 - ], - [ - 142, - 166 - ], - [ - 143, - 166 - ] - ], - "waypoints": [ - [ - 59, - 166 - ], - [ - 143, - 166 - ] - ], - "width": 7, - "role": "bridge", - "deck_y": 75, - "minimum_ground": 31.958332061767578, - "endpoint_ground": [ - 67.63906860351562, - 72.64015197753906 - ], - "intent": "Mark both parapet alignments and abutments; preserve the river and banks.", - "abutments": [ - { - "point": [ - 59, - 166 - ], - "ground_y": 67.6, - "deck_y": 75, - "future_stairs": true, - "rise": 8 - }, - { - "point": [ - 143, - 166 - ], - "ground_y": 72.6, - "deck_y": 75, - "future_stairs": false, - "rise": 3 - } - ], - "analysis": { - "length": 84.0, - "ground_min": 32.0, - "ground_max": 72.7, - "p95_raw_grade": 2.37, - "water_samples": 18 - } - }, - { - "id": "bridge-market-north", - "name": "Market / lake bridge", - "points": [ - [ - -127, - 126 - ], - [ - -128, - 126 - ], - [ - -129, - 126 - ], - [ - -130, - 126 - ], - [ - -131, - 126 - ], - [ - -132, - 126 - ], - [ - -133, - 126 - ], - [ - -134, - 126 - ], - [ - -135, - 126 - ], - [ - -136, - 126 - ], - [ - -137, - 126 - ], - [ - -138, - 126 - ], - [ - -139, - 126 - ], - [ - -140, - 126 - ], - [ - -141, - 126 - ], - [ - -142, - 126 - ], - [ - -143, - 126 - ], - [ - -144, - 126 - ], - [ - -145, - 126 - ], - [ - -146, - 126 - ], - [ - -147, - 126 - ], - [ - -148, - 126 - ], - [ - -149, - 127 - ], - [ - -151, - 127 - ], - [ - -152, - 127 - ], - [ - -153, - 127 - ], - [ - -154, - 127 - ], - [ - -155, - 127 - ], - [ - -156, - 127 - ], - [ - -158, - 127 - ], - [ - -159, - 127 - ], - [ - -160, - 127 - ], - [ - -161, - 127 - ], - [ - -163, - 127 - ], - [ - -164, - 127 - ], - [ - -165, - 127 - ], - [ - -166, - 127 - ], - [ - -168, - 127 - ], - [ - -169, - 127 - ], - [ - -170, - 127 - ], - [ - -171, - 127 - ], - [ - -172, - 127 - ], - [ - -174, - 127 - ], - [ - -175, - 127 - ], - [ - -176, - 127 - ], - [ - -177, - 127 - ], - [ - -179, - 127 - ], - [ - -180, - 127 - ], - [ - -181, - 127 - ], - [ - -182, - 127 - ], - [ - -184, - 127 - ], - [ - -185, - 127 - ], - [ - -186, - 127 - ], - [ - -187, - 127 - ], - [ - -188, - 127 - ], - [ - -189, - 127 - ], - [ - -191, - 127 - ], - [ - -192, - 128 - ], - [ - -193, - 128 - ], - [ - -194, - 128 - ], - [ - -195, - 128 - ], - [ - -196, - 128 - ], - [ - -197, - 128 - ], - [ - -198, - 128 - ], - [ - -199, - 128 - ], - [ - -200, - 128 - ], - [ - -201, - 128 - ], - [ - -202, - 128 - ], - [ - -203, - 128 - ], - [ - -204, - 128 - ], - [ - -205, - 128 - ], - [ - -206, - 128 - ], - [ - -207, - 128 - ], - [ - -208, - 128 - ], - [ - -209, - 128 - ], - [ - -210, - 128 - ], - [ - -211, - 128 - ], - [ - -212, - 128 - ], - [ - -213, - 128 - ] - ], - "waypoints": [ - [ - -127, - 126 - ], - [ - -213, - 128 - ] - ], - "width": 7, - "role": "bridge", - "deck_y": 72, - "minimum_ground": 33.51194381713867, - "endpoint_ground": [ - 67.53361511230469, - 69.1140365600586 - ], - "intent": "Mark both parapet alignments and abutments; preserve the river and banks.", - "abutments": [ - { - "point": [ - -127, - 126 - ], - "ground_y": 67.5, - "deck_y": 72, - "future_stairs": true, - "rise": 5 - }, - { - "point": [ - -213, - 128 - ], - "ground_y": 69.1, - "deck_y": 72, - "future_stairs": false, - "rise": 3 - } - ], - "analysis": { - "length": 86.8, - "ground_min": 33.5, - "ground_max": 69.1, - "p95_raw_grade": 1.58, - "water_samples": 26 - } - }, - { - "id": "bridge-market-south", - "name": "Market / arrival bridge", - "points": [ - [ - -145, - 242 - ], - [ - -144, - 242 - ], - [ - -143, - 242 - ], - [ - -142, - 242 - ], - [ - -141, - 242 - ], - [ - -140, - 242 - ], - [ - -139, - 242 - ], - [ - -138, - 242 - ], - [ - -137, - 242 - ], - [ - -136, - 242 - ], - [ - -135, - 242 - ], - [ - -134, - 242 - ], - [ - -133, - 242 - ], - [ - -131, - 242 - ], - [ - -130, - 242 - ], - [ - -129, - 242 - ], - [ - -128, - 242 - ], - [ - -127, - 242 - ], - [ - -126, - 242 - ], - [ - -125, - 242 - ], - [ - -124, - 242 - ], - [ - -123, - 242 - ], - [ - -121, - 242 - ], - [ - -120, - 242 - ], - [ - -119, - 242 - ], - [ - -118, - 242 - ], - [ - -117, - 242 - ], - [ - -115, - 242 - ], - [ - -114, - 242 - ], - [ - -113, - 242 - ], - [ - -112, - 242 - ], - [ - -110, - 242 - ], - [ - -109, - 242 - ], - [ - -108, - 242 - ], - [ - -107, - 242 - ], - [ - -105, - 242 - ], - [ - -104, - 242 - ], - [ - -103, - 242 - ], - [ - -102, - 242 - ], - [ - -100, - 242 - ], - [ - -99, - 242 - ], - [ - -98, - 242 - ], - [ - -97, - 242 - ], - [ - -96, - 242 - ], - [ - -94, - 242 - ], - [ - -93, - 242 - ], - [ - -92, - 242 - ], - [ - -91, - 242 - ], - [ - -90, - 242 - ], - [ - -89, - 242 - ], - [ - -88, - 242 - ], - [ - -87, - 242 - ], - [ - -86, - 242 - ], - [ - -84, - 242 - ], - [ - -83, - 242 - ], - [ - -82, - 242 - ], - [ - -81, - 242 - ], - [ - -80, - 242 - ], - [ - -79, - 242 - ], - [ - -78, - 242 - ], - [ - -77, - 242 - ], - [ - -76, - 242 - ], - [ - -75, - 242 - ], - [ - -74, - 242 - ], - [ - -73, - 242 - ], - [ - -72, - 242 - ] - ], - "waypoints": [ - [ - -145, - 242 - ], - [ - -72, - 242 - ] - ], - "width": 7, - "role": "bridge", - "deck_y": 70, - "minimum_ground": 34.22280502319336, - "endpoint_ground": [ - 64.9618148803711, - 67.11980438232422 - ], - "intent": "Mark both parapet alignments and abutments; preserve the river and banks.", - "abutments": [ - { - "point": [ - -145, - 242 - ], - "ground_y": 65.0, - "deck_y": 70, - "future_stairs": true, - "rise": 6 - }, - { - "point": [ - -72, - 242 - ], - "ground_y": 67.1, - "deck_y": 70, - "future_stairs": false, - "rise": 3 - } - ], - "analysis": { - "length": 73.0, - "ground_min": 34.3, - "ground_max": 67.1, - "p95_raw_grade": 1.99, - "water_samples": 21 - } - }, - { - "id": "arrival-viaduct", - "name": "Arrival viaduct", - "points": [ - [ - 5, - 321 - ], - [ - 5, - 322 - ], - [ - 5, - 323 - ], - [ - 5, - 324 - ], - [ - 5, - 325 - ], - [ - 5, - 326 - ], - [ - 5, - 327 - ], - [ - 5, - 328 - ], - [ - 5, - 329 - ], - [ - 5, - 330 - ], - [ - 5, - 331 - ], - [ - 5, - 332 - ], - [ - 5, - 333 - ], - [ - 5, - 334 - ], - [ - 5, - 335 - ], - [ - 5, - 336 - ], - [ - 5, - 337 - ], - [ - 5, - 339 - ], - [ - 5, - 340 - ], - [ - 5, - 341 - ], - [ - 5, - 342 - ], - [ - 5, - 343 - ], - [ - 5, - 345 - ], - [ - 5, - 346 - ], - [ - 5, - 347 - ], - [ - 5, - 348 - ], - [ - 5, - 350 - ], - [ - 5, - 351 - ], - [ - 5, - 352 - ], - [ - 5, - 353 - ], - [ - 5, - 354 - ], - [ - 5, - 356 - ], - [ - 5, - 357 - ], - [ - 5, - 358 - ], - [ - 5, - 359 - ], - [ - 5, - 361 - ], - [ - 5, - 362 - ], - [ - 5, - 363 - ], - [ - 5, - 364 - ], - [ - 5, - 365 - ], - [ - 5, - 367 - ], - [ - 5, - 368 - ], - [ - 5, - 369 - ], - [ - 5, - 370 - ], - [ - 5, - 371 - ], - [ - 5, - 372 - ], - [ - 5, - 373 - ], - [ - 5, - 374 - ], - [ - 5, - 375 - ], - [ - 5, - 376 - ], - [ - 5, - 377 - ], - [ - 5, - 378 - ], - [ - 5, - 379 - ], - [ - 5, - 380 - ], - [ - 5, - 381 - ], - [ - 5, - 382 - ], - [ - 5, - 383 - ] - ], - "waypoints": [ - [ - 5, - 321 - ], - [ - 5, - 383 - ] - ], - "width": 11, - "role": "bridge", - "deck_y": 65, - "minimum_ground": 32.357540130615234, - "endpoint_ground": [ - 59.652252197265625, - 55.05183029174805 - ], - "intent": "Mark both parapet alignments and abutments; preserve the river and banks.", - "abutments": [ - { - "point": [ - 5, - 321 - ], - "ground_y": 59.7, - "deck_y": 65, - "future_stairs": true, - "rise": 6 - }, - { - "point": [ - 5, - 383 - ], - "ground_y": 55.1, - "deck_y": 65, - "future_stairs": true, - "rise": 10 - } - ], - "analysis": { - "length": 62.0, - "ground_min": 32.4, - "ground_max": 61.1, - "p95_raw_grade": 2.03, - "water_samples": 19 - } - }, - { - "id": "north-overlook-path", - "name": "North overlook", - "points": [ - [ - 40, - -128 - ], - [ - 41, - -129 - ], - [ - 42, - -129 - ], - [ - 43, - -129 - ], - [ - 43, - -130 - ], - [ - 44, - -130 - ], - [ - 45, - -131 - ], - [ - 46, - -131 - ], - [ - 47, - -132 - ], - [ - 48, - -132 - ], - [ - 49, - -133 - ], - [ - 50, - -134 - ], - [ - 51, - -134 - ], - [ - 52, - -135 - ], - [ - 53, - -136 - ], - [ - 54, - -136 - ], - [ - 55, - -137 - ], - [ - 56, - -138 - ], - [ - 57, - -138 - ], - [ - 57, - -139 - ], - [ - 58, - -140 - ], - [ - 59, - -141 - ], - [ - 59, - -142 - ], - [ - 59, - -143 - ], - [ - 60, - -144 - ], - [ - 60, - -145 - ], - [ - 61, - -146 - ], - [ - 61, - -147 - ], - [ - 61, - -148 - ], - [ - 62, - -149 - ], - [ - 62, - -150 - ], - [ - 62, - -151 - ], - [ - 62, - -152 - ], - [ - 62, - -153 - ], - [ - 62, - -154 - ], - [ - 62, - -156 - ], - [ - 62, - -157 - ], - [ - 62, - -158 - ], - [ - 62, - -159 - ], - [ - 62, - -160 - ], - [ - 62, - -161 - ], - [ - 62, - -162 - ], - [ - 61, - -163 - ], - [ - 60, - -164 - ], - [ - 60, - -165 - ], - [ - 59, - -166 - ], - [ - 58, - -166 - ], - [ - 57, - -167 - ], - [ - 56, - -168 - ], - [ - 55, - -168 - ], - [ - 54, - -169 - ], - [ - 53, - -169 - ], - [ - 52, - -170 - ], - [ - 51, - -170 - ], - [ - 50, - -171 - ], - [ - 50, - -171 - ] - ], - "waypoints": [ - [ - 40, - -128 - ], - [ - 58, - -140 - ], - [ - 62, - -161 - ], - [ - 50, - -171 - ] - ], - "width": 3, - "role": "secondary", - "analysis": { - "length": 64.1, - "ground_min": 82.0, - "ground_max": 94.1, - "p95_raw_grade": 0.67, - "water_samples": 0 - } - }, - { - "id": "portal-overlook-path", - "name": "Portal ridge path", - "points": [ - [ - -159, - -151 - ], - [ - -158, - -151 - ], - [ - -157, - -151 - ], - [ - -156, - -152 - ], - [ - -155, - -152 - ], - [ - -154, - -152 - ], - [ - -153, - -153 - ], - [ - -152, - -153 - ], - [ - -151, - -153 - ], - [ - -149, - -153 - ], - [ - -148, - -154 - ], - [ - -147, - -154 - ], - [ - -146, - -154 - ], - [ - -145, - -155 - ], - [ - -144, - -155 - ], - [ - -143, - -155 - ], - [ - -142, - -156 - ], - [ - -141, - -156 - ], - [ - -140, - -156 - ], - [ - -139, - -157 - ], - [ - -138, - -157 - ], - [ - -137, - -158 - ], - [ - -136, - -158 - ], - [ - -135, - -159 - ], - [ - -134, - -159 - ], - [ - -133, - -160 - ], - [ - -132, - -161 - ], - [ - -131, - -161 - ], - [ - -131, - -162 - ], - [ - -130, - -163 - ], - [ - -129, - -164 - ], - [ - -128, - -164 - ], - [ - -127, - -165 - ], - [ - -127, - -166 - ], - [ - -126, - -167 - ], - [ - -125, - -168 - ], - [ - -124, - -169 - ], - [ - -124, - -170 - ], - [ - -124, - -171 - ], - [ - -123, - -171 - ], - [ - -123, - -172 - ], - [ - -123, - -173 - ], - [ - -123, - -174 - ], - [ - -124, - -175 - ], - [ - -124, - -176 - ], - [ - -125, - -177 - ], - [ - -126, - -178 - ], - [ - -127, - -179 - ], - [ - -128, - -180 - ], - [ - -129, - -180 - ], - [ - -129, - -181 - ] - ], - "waypoints": [ - [ - -159, - -151 - ], - [ - -136, - -158 - ], - [ - -123, - -172 - ], - [ - -129, - -181 - ] - ], - "width": 3, - "role": "secondary", - "analysis": { - "length": 59.7, - "ground_min": 72.4, - "ground_max": 90.6, - "p95_raw_grade": 1.04, - "water_samples": 0 - } - }, - { - "id": "harbor-overlook-path", - "name": "Harbor lookout trail", - "points": [ - [ - 196, - -80 - ], - [ - 197, - -80 - ], - [ - 198, - -79 - ], - [ - 199, - -79 - ], - [ - 200, - -79 - ], - [ - 201, - -78 - ], - [ - 202, - -78 - ], - [ - 203, - -78 - ], - [ - 205, - -77 - ], - [ - 206, - -77 - ], - [ - 207, - -77 - ], - [ - 208, - -76 - ], - [ - 209, - -76 - ], - [ - 210, - -76 - ], - [ - 211, - -75 - ], - [ - 212, - -75 - ], - [ - 213, - -75 - ], - [ - 214, - -75 - ], - [ - 216, - -75 - ], - [ - 217, - -75 - ], - [ - 218, - -75 - ], - [ - 219, - -75 - ], - [ - 220, - -76 - ], - [ - 221, - -76 - ] - ], - "waypoints": [ - [ - 196, - -80 - ], - [ - 213, - -75 - ], - [ - 221, - -76 - ] - ], - "width": 3, - "role": "secondary", - "analysis": { - "length": 27.3, - "ground_min": 69.3, - "ground_max": 73.7, - "p95_raw_grade": 0.64, - "water_samples": 0 - } - }, - { - "id": "garden-overlook-path", - "name": "Garden lookout trail", - "points": [ - [ - 232, - 96 - ], - [ - 233, - 96 - ], - [ - 234, - 96 - ], - [ - 235, - 96 - ], - [ - 236, - 97 - ], - [ - 237, - 97 - ], - [ - 238, - 97 - ], - [ - 239, - 97 - ], - [ - 240, - 97 - ], - [ - 242, - 97 - ], - [ - 243, - 97 - ], - [ - 244, - 98 - ], - [ - 245, - 98 - ], - [ - 246, - 98 - ], - [ - 247, - 98 - ], - [ - 248, - 99 - ], - [ - 249, - 99 - ], - [ - 250, - 100 - ], - [ - 251, - 100 - ], - [ - 252, - 101 - ], - [ - 253, - 102 - ], - [ - 254, - 102 - ], - [ - 255, - 103 - ], - [ - 256, - 104 - ], - [ - 257, - 105 - ], - [ - 258, - 106 - ], - [ - 259, - 107 - ], - [ - 259, - 107 - ] - ], - "waypoints": [ - [ - 232, - 96 - ], - [ - 249, - 99 - ], - [ - 259, - 107 - ] - ], - "width": 3, - "role": "secondary", - "analysis": { - "length": 31.6, - "ground_min": 87.4, - "ground_max": 92.2, - "p95_raw_grade": 0.81, - "water_samples": 0 - } - }, - { - "id": "south-overlook-path", - "name": "Valley lookout trail", - "points": [ - [ - -218, - 236 - ], - [ - -218, - 235 - ], - [ - -218, - 234 - ], - [ - -219, - 233 - ], - [ - -219, - 232 - ], - [ - -219, - 231 - ], - [ - -219, - 230 - ], - [ - -219, - 228 - ], - [ - -220, - 227 - ], - [ - -220, - 226 - ], - [ - -220, - 225 - ], - [ - -220, - 224 - ], - [ - -221, - 223 - ], - [ - -221, - 222 - ], - [ - -222, - 221 - ], - [ - -223, - 220 - ], - [ - -223, - 219 - ], - [ - -224, - 219 - ], - [ - -225, - 219 - ], - [ - -226, - 218 - ], - [ - -227, - 218 - ], - [ - -228, - 218 - ], - [ - -229, - 218 - ], - [ - -230, - 218 - ], - [ - -231, - 218 - ], - [ - -232, - 218 - ], - [ - -233, - 219 - ], - [ - -234, - 219 - ], - [ - -235, - 219 - ], - [ - -237, - 219 - ], - [ - -238, - 219 - ], - [ - -239, - 220 - ], - [ - -240, - 220 - ], - [ - -241, - 220 - ], - [ - -242, - 221 - ], - [ - -243, - 221 - ], - [ - -244, - 221 - ], - [ - -245, - 222 - ], - [ - -246, - 222 - ], - [ - -247, - 223 - ], - [ - -248, - 223 - ], - [ - -249, - 224 - ], - [ - -250, - 224 - ], - [ - -251, - 225 - ], - [ - -252, - 226 - ], - [ - -253, - 227 - ], - [ - -254, - 228 - ], - [ - -255, - 228 - ], - [ - -255, - 229 - ], - [ - -256, - 230 - ], - [ - -257, - 231 - ], - [ - -258, - 232 - ], - [ - -258, - 233 - ], - [ - -259, - 234 - ], - [ - -260, - 235 - ], - [ - -261, - 236 - ], - [ - -261, - 237 - ], - [ - -262, - 238 - ], - [ - -262, - 239 - ], - [ - -263, - 239 - ], - [ - -263, - 240 - ], - [ - -264, - 241 - ], - [ - -264, - 243 - ], - [ - -264, - 244 - ], - [ - -264, - 245 - ], - [ - -264, - 246 - ], - [ - -264, - 247 - ], - [ - -264, - 248 - ], - [ - -264, - 249 - ], - [ - -264, - 250 - ], - [ - -264, - 251 - ] - ], - "waypoints": [ - [ - -218, - 236 - ], - [ - -224, - 219 - ], - [ - -248, - 223 - ], - [ - -263, - 240 - ], - [ - -264, - 251 - ] - ], - "width": 3, - "role": "secondary", - "analysis": { - "length": 82.9, - "ground_min": 63.1, - "ground_max": 87.1, - "p95_raw_grade": 1.06, - "water_samples": 0 - } - } - ], - "construction_notes": [ - "Colored outlines are building reservations, not instructions to level their entire bounding boxes.", - "Walks remain aligned to terrain; steep local runs need short stairs during the later building phase.", - "Main avenues 9 blocks, district ring 7, side paths 5, scenic trails 3; marker widths can be thinner than final clear widths.", - "Keep protected water intact. Bridges have a separate planned deck Y above their endpoint terrain.", - "The natural western lake bank is too steep for a main ring street. The main route uses the calm inner/east lake shoulder; the low over-water boardwalk is secondary.", - "No minigame arenas. Six portal bays, three harbor piers, one future flagship reservation." - ] -} diff --git a/examples/terrain/shacraft-lobby-world.json b/examples/terrain/shacraft-lobby-world.json deleted file mode 100644 index 7302bf2..0000000 --- a/examples/terrain/shacraft-lobby-world.json +++ /dev/null @@ -1,273 +0,0 @@ -{ - "version": 1, - "min": { - "x": -384, - "y": 16, - "z": -384 - }, - "max": { - "x": 383, - "y": 207, - "z": 383 - }, - "base_height": 59, - "seed": 28092005, - "mode": "sculpt", - "noise": { - "amplitude": 26, - "scale": 90 - }, - "palette": { - "rock": "minecraft:stone", - "soil": "minecraft:dirt", - "surface": "minecraft:grass_block", - "soil_depth": 3 - }, - "features": [ - { - "type": "ridge", - "points": [ - { - "x": -350, - "z": -285 - }, - { - "x": -190, - "z": -335 - }, - { - "x": 0, - "z": -305 - }, - { - "x": 160, - "z": -330 - }, - { - "x": 330, - "z": -290 - } - ], - "width": 15, - "height": 100, - "falloff": 110 - }, - { - "type": "ridge", - "points": [ - { - "x": -330, - "z": -220 - }, - { - "x": -350, - "z": -40 - }, - { - "x": -330, - "z": 180 - }, - { - "x": -290, - "z": 310 - } - ], - "width": 8, - "height": 60, - "falloff": 70 - }, - { - "type": "ridge", - "points": [ - { - "x": 335, - "z": -210 - }, - { - "x": 340, - "z": 30 - }, - { - "x": 315, - "z": 210 - } - ], - "width": 10, - "height": 66, - "falloff": 80 - }, - { - "type": "hill", - "center": { - "x": 0, - "z": 0 - }, - "radius": 50, - "height": 45, - "falloff": 140 - }, - { - "type": "plateau", - "min": { - "x": -65, - "z": -55 - }, - "max": { - "x": 65, - "z": 60 - }, - "height": 106, - "falloff": 28 - }, - { - "type": "plateau", - "min": { - "x": -82, - "z": -165 - }, - "max": { - "x": 82, - "z": -80 - }, - "height": 118, - "falloff": 24 - }, - { - "type": "plateau", - "min": { - "x": -245, - "z": -185 - }, - "max": { - "x": -125, - "z": -105 - }, - "height": 110, - "falloff": 22 - }, - { - "type": "plateau", - "min": { - "x": 130, - "z": -200 - }, - "max": { - "x": 255, - "z": -100 - }, - "height": 124, - "falloff": 20 - }, - { - "type": "plateau", - "min": { - "x": 135, - "z": -60 - }, - "max": { - "x": 250, - "z": 5 - }, - "height": 110, - "falloff": 15 - }, - { - "type": "plateau", - "min": { - "x": 145, - "z": 40 - }, - "max": { - "x": 250, - "z": 95 - }, - "height": 97, - "falloff": 15 - }, - { - "type": "plateau", - "min": { - "x": 160, - "z": 130 - }, - "max": { - "x": 235, - "z": 180 - }, - "height": 84, - "falloff": 14 - }, - { - "type": "plateau", - "min": { - "x": -240, - "z": 90 - }, - "max": { - "x": -115, - "z": 215 - }, - "height": 92, - "falloff": 25 - }, - { - "type": "basin", - "center": { - "x": -220, - "z": -5 - }, - "radius": 47, - "height": 38, - "falloff": 30 - }, - { - "type": "channel", - "points": [ - { - "x": -40, - "z": 125 - }, - { - "x": -25, - "z": 200 - }, - { - "x": 45, - "z": 260 - }, - { - "x": 15, - "z": 383 - } - ], - "width": 20, - "height": 22, - "falloff": 16 - }, - { - "type": "channel", - "points": [ - { - "x": 100, - "z": -250 - }, - { - "x": 110, - "z": -100 - }, - { - "x": 100, - "z": 70 - }, - { - "x": 80, - "z": 190 - } - ], - "width": 10, - "height": 30, - "falloff": 12 - } - ], - "preserve": [] -} diff --git a/examples/terrain/shacraft-massif.json b/examples/terrain/shacraft-massif.json deleted file mode 100644 index 8ec974b..0000000 --- a/examples/terrain/shacraft-massif.json +++ /dev/null @@ -1,273 +0,0 @@ -{ - "version": 1, - "min": { - "x": -384, - "y": -48, - "z": -384 - }, - "max": { - "x": 383, - "y": 143, - "z": 383 - }, - "base_height": -5, - "seed": 28092005, - "mode": "sculpt", - "noise": { - "amplitude": 26, - "scale": 90 - }, - "palette": { - "rock": "minecraft:stone", - "soil": "minecraft:dirt", - "surface": "minecraft:grass_block", - "soil_depth": 3 - }, - "features": [ - { - "type": "ridge", - "points": [ - { - "x": -350, - "z": -285 - }, - { - "x": -190, - "z": -335 - }, - { - "x": 0, - "z": -305 - }, - { - "x": 160, - "z": -330 - }, - { - "x": 330, - "z": -290 - } - ], - "width": 15, - "height": 100, - "falloff": 110 - }, - { - "type": "ridge", - "points": [ - { - "x": -330, - "z": -220 - }, - { - "x": -350, - "z": -40 - }, - { - "x": -330, - "z": 180 - }, - { - "x": -290, - "z": 310 - } - ], - "width": 8, - "height": 60, - "falloff": 70 - }, - { - "type": "ridge", - "points": [ - { - "x": 335, - "z": -210 - }, - { - "x": 340, - "z": 30 - }, - { - "x": 315, - "z": 210 - } - ], - "width": 10, - "height": 66, - "falloff": 80 - }, - { - "type": "hill", - "center": { - "x": 0, - "z": 0 - }, - "radius": 50, - "height": 45, - "falloff": 140 - }, - { - "type": "plateau", - "min": { - "x": -65, - "z": -55 - }, - "max": { - "x": 65, - "z": 60 - }, - "height": 42, - "falloff": 28 - }, - { - "type": "plateau", - "min": { - "x": -82, - "z": -165 - }, - "max": { - "x": 82, - "z": -80 - }, - "height": 54, - "falloff": 24 - }, - { - "type": "plateau", - "min": { - "x": -245, - "z": -185 - }, - "max": { - "x": -125, - "z": -105 - }, - "height": 46, - "falloff": 22 - }, - { - "type": "plateau", - "min": { - "x": 130, - "z": -200 - }, - "max": { - "x": 255, - "z": -100 - }, - "height": 60, - "falloff": 20 - }, - { - "type": "plateau", - "min": { - "x": 135, - "z": -60 - }, - "max": { - "x": 250, - "z": 5 - }, - "height": 46, - "falloff": 15 - }, - { - "type": "plateau", - "min": { - "x": 145, - "z": 40 - }, - "max": { - "x": 250, - "z": 95 - }, - "height": 33, - "falloff": 15 - }, - { - "type": "plateau", - "min": { - "x": 160, - "z": 130 - }, - "max": { - "x": 235, - "z": 180 - }, - "height": 20, - "falloff": 14 - }, - { - "type": "plateau", - "min": { - "x": -240, - "z": 90 - }, - "max": { - "x": -115, - "z": 215 - }, - "height": 28, - "falloff": 25 - }, - { - "type": "basin", - "center": { - "x": -220, - "z": -5 - }, - "radius": 47, - "height": -26, - "falloff": 30 - }, - { - "type": "channel", - "points": [ - { - "x": -40, - "z": 125 - }, - { - "x": -25, - "z": 200 - }, - { - "x": 45, - "z": 260 - }, - { - "x": 15, - "z": 383 - } - ], - "width": 20, - "height": -42, - "falloff": 16 - }, - { - "type": "channel", - "points": [ - { - "x": 100, - "z": -250 - }, - { - "x": 110, - "z": -100 - }, - { - "x": 100, - "z": 70 - }, - { - "x": 80, - "z": 190 - } - ], - "width": 10, - "height": -34, - "falloff": 12 - } - ], - "preserve": [] -} diff --git a/examples/terrain/shacraft-natural-world.json b/examples/terrain/shacraft-natural-world.json deleted file mode 100644 index 0a2ad82..0000000 --- a/examples/terrain/shacraft-natural-world.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "min": { - "x": -384, - "y": 16, - "z": -384 - }, - "max": { - "x": 383, - "y": 239, - "z": 383 - }, - "base_height": 62, - "seed": 28092005, - "mode": "sculpt", - "noise": { - "amplitude": 0, - "scale": 220 - }, - "palette": { - "rock": "minecraft:stone", - "soil": "minecraft:dirt", - "surface": "minecraft:grass_block", - "soil_depth": 3 - }, - "features": [], - "preserve": [] -} diff --git a/pom.xml b/pom.xml index 2f5a17c..d09a5da 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,7 @@ 4.0.0 io.github.minecraftbuilderminecraft-builder-mcp0.1.0-SNAPSHOTpom - world-corepaper-pluginterrain-world-plugin + world-corepaper-plugin 25UTF-826.2.build.123-stable org.apache.maven.pluginsmaven-compiler-plugin3.14.1 diff --git a/scripts/bridge.py b/scripts/bridge.py index 3932bd6..823fc65 100644 --- a/scripts/bridge.py +++ b/scripts/bridge.py @@ -8,6 +8,7 @@ import re import subprocess ROOT = Path(__file__).resolve().parents[1] +RUNTIME = Path(os.environ.get('MCB_RUNTIME_DIR') or ROOT / '.runtime').expanduser().resolve() def scalar(text, key): @@ -37,7 +38,7 @@ def main(): parser.add_argument('mode', choices=['doctor', 'chat', 'mcp', 'login', 'status']) parser.add_argument('action', nargs='?', choices=['status'], help='Only for login: check authentication without starting a login flow') parser.add_argument('--status', action='store_true', help='With login: check authentication without starting login') - parser.add_argument('--config', type=Path, default=ROOT / '.runtime/server/plugins/MinecraftBuilderMCP/config.yml') + parser.add_argument('--config', type=Path, default=RUNTIME / 'server/plugins/MinecraftBuilderMCP/config.yml') parser.add_argument('--console', action='store_true', help='Use console scope only with allow-local-automation in the disposable fixture') args = parser.parse_args() if (args.action or args.status) and args.mode != 'login': @@ -46,7 +47,7 @@ def main(): # Login/authentication status needs no Paper config or Paper token. Always use the same # default state directory as the chat daemon, including when called from another cwd. env = dict(os.environ) - env.setdefault('MCB_STATE_DIR', str(ROOT / '.runtime/bridge-state')) + env.setdefault('MCB_STATE_DIR', str(RUNTIME / 'bridge-state')) needs_backend = args.mode in ('chat', 'mcp') if needs_backend and not args.config.is_file(): raise SystemExit('Start the Paper plugin once to generate its private config.') diff --git a/scripts/build-gothic-hall.py b/scripts/build-gothic-hall.py deleted file mode 100644 index 5c22d1b..0000000 --- a/scripts/build-gothic-hall.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -"""Plan/apply/verify the Gothic reference build through checked Paper editor operations.""" -import argparse -from collections import Counter -import hashlib -import json -import os -from pathlib import Path -import re -import time -import urllib.error -import urllib.request -import uuid - -from builds.gothic_hall.scene import build_scene, manifest - -ROOT = Path(__file__).resolve().parents[1] -OUTPUT = ROOT / '.runtime/gothic-hall' -CONFIG = ROOT / '.runtime/server/plugins/MinecraftBuilderMCP/config.yml' - -def save(path, data): - temp = path.with_suffix('.tmp') - with temp.open('w') as stream: - json.dump(data, stream, indent=2) - stream.write('\n'); stream.flush(); os.fsync(stream.fileno()) - temp.replace(path) - -def digest(data): - return hashlib.sha256(json.dumps(data, sort_keys=True, separators=(',', ':')).encode()).hexdigest() - -def point_key(value): - return value['x'], value['y'], value['z'] - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('action', choices=['plan', 'apply', 'verify']) - args = parser.parse_args() - OUTPUT.mkdir(exist_ok=True) - config = CONFIG.read_text() - def scalar(key): - return re.search(r'^' + re.escape(key) + r':[ \t]*(.*?)$', config, re.M).group(1).strip().strip("'\"") - scope = {'player_id': scalar('owner-uuid'), 'project_id': scalar('project-id')} - assert scope['player_id'], 'An online project owner is required' - token = scalar('agent-token') - opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) - def rpc(method, params): - for attempt in range(100): - data = {'method': method, 'params': {**params, **scope}, 'requestId': str(uuid.uuid4())} - req = urllib.request.Request('http://127.0.0.1:' + scalar('http-port') + '/v1/rpc', - data=json.dumps(data).encode(), headers={'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}) - try: - with opener.open(req, timeout=30) as response: body = json.load(response) - except urllib.error.HTTPError as error: - body = json.load(error) - if body.get('ok'): return body['result'] - code = body.get('error', {}).get('code', 'unknown') - if code == 'busy': time.sleep(.1); continue - raise RuntimeError(f'{method}: {code}; no blind overwrite or replay performed') - raise RuntimeError('Paper remained busy') - - context = rpc('project_context', {}) - if args.action == 'plan': - voxels = build_scene(); model = manifest(voxels) - allowed = set(context['supported_materials']) - assert all(block.split('[')[0] in allowed for block in model['palette']), 'Unsupported material in blueprint' - for batch in model['batches']: - assert batch['blocks'] <= 4096 - expanded = {} - for operation in batch['recipe']['operations']: - a, b = operation['min'], operation['max'] - for y in range(a['y'], b['y'] + 1): - for z in range(a['z'], b['z'] + 1): - for x in range(a['x'], b['x'] + 1): - assert (x, y, z) not in expanded, 'Compression overlapped a voxel' - local = tuple(n - o for n, o in zip((x, y, z), model['origin'])) - assert voxels.blocks[local] == operation['block'], 'Compression changed the blueprint' - expanded[(x, y, z)] = operation['block'] - assert len(expanded) == batch['blocks'] - for axis in ('x', 'y', 'z'): - assert context['region']['min'][axis] <= batch['min'][axis] <= batch['max'][axis] <= context['region']['max'][axis] - model['world_id'] = context['world_id']; model['world_epoch'] = context['world_epoch'] - path = OUTPUT / 'manifest.json' - if (OUTPUT / 'ledger.json').exists() and path.exists(): - assert digest(json.loads(path.read_text())) == digest(model), 'Existing live build has a different blueprint; preserve it' - save(path, model) - print(json.dumps({'blocks': model['blocks'], 'batches': len(model['batches']), 'stages': model['stages'], - 'max_recipe_operations': max(len(b['recipe']['operations']) for b in model['batches'])}), flush=True) - return - - model = json.loads((OUTPUT / 'manifest.json').read_text()) - assert model['world_id'] == context['world_id'] and model['world_epoch'] == context['world_epoch'], 'World identity changed' - ledger_path = OUTPUT / 'ledger.json' - ledger = json.loads(ledger_path.read_text()) if ledger_path.exists() else {'manifest_sha256': digest(model), 'batches': {}} - assert ledger['manifest_sha256'] == digest(model), 'Blueprint changed after writing began' - for index, batch in enumerate(model['batches'], 1): - record = ledger['batches'].get(batch['key']) - if args.action == 'apply': - if record is None: - before = rpc('region_inspect', {'min': batch['min'], 'max': batch['max'], 'detail': 'summary'}) - assert set(before['palette']) == {'minecraft:air'}, f"Occupied site in batch {batch['key']}; preserve and review" - plan = rpc('build_prepare', {'recipe': batch['recipe']}) - # The immutable persisted plan must still agree with the reviewed empty site. - envelope = json.loads((CONFIG.parent / 'journal/plans' / (plan['plan_id'] + '.json')).read_text()) - persisted = json.loads(envelope['payload']) - assert all(change['expected'] == 'minecraft:air' for change in persisted['changes']), 'Concurrent edit before prepare; stop' - record = {'plan': plan, 'idempotency_key': 'gothic-hall-' + plan['plan_id']} - ledger['batches'][batch['key']] = record; save(ledger_path, ledger) - if not record.get('operation_id'): - operation = rpc('build_apply', {**record['plan'], 'idempotency_key': record['idempotency_key']}) - record['operation_id'] = operation['operation_id']; save(ledger_path, ledger) - for _ in range(600): - status = rpc('operation_status', {'operation_id': record['operation_id']}) - if status['status'] not in ('queued', 'applying'): break - time.sleep(.1) - record['status'] = status; save(ledger_path, ledger) - assert status['status'] == 'applied', f"Batch {batch['key']} stopped: {status['status']}; inspect the operation" - print(json.dumps({'batch': index, 'of': len(model['batches']), 'key': batch['key'], 'written': status['written'], - 'stages': batch['stages'], 'operation_id': record['operation_id']}), flush=True) - else: - assert record and record.get('status', {}).get('status') == 'applied', 'Build is not fully applied' - envelope = json.loads((CONFIG.parent / 'journal/plans' / (record['plan']['plan_id'] + '.json')).read_text()) - planned = json.loads(envelope['payload']) - current = rpc('region_inspect', {'min': batch['min'], 'max': batch['max'], 'detail': 'blocks'}) - states = {point_key(block['pos']): block['state'] for block in current['blocks']} - mismatches = [change['pos'] for change in planned['changes'] if states[point_key(change['pos'])] != change['desired']] - assert not mismatches, f"Live geometry differs in batch {batch['key']} at {mismatches[:5]}; preserve edits" - if args.action == 'verify': - report = {'status': 'verified', 'blocks': model['blocks'], 'batches': len(model['batches']), 'verified_at': time.time(), - 'manifest_sha256': digest(model), 'scope': scope} - save(OUTPUT / 'verification.json', report) - print(json.dumps(report), flush=True) - else: - print('GOTHIC HALL BUILD APPLIED', flush=True) - -if __name__ == '__main__': - main() diff --git a/scripts/build-shacraft-balustrade.py b/scripts/build-shacraft-balustrade.py deleted file mode 100644 index b82b655..0000000 --- a/scripts/build-shacraft-balustrade.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python3 -"""Compile a continuous, checked arrival-square balustrade; never edit the world. - -The existing polygon is followed exactly. Cardinal elbows close diagonal gaps; -garden-side elbows move outward and receive observed, grounded stone footings. -Every road clear cell, planted block and existing light remains protected. -""" -import argparse -from collections import Counter -import importlib.util -import json -import math -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -STAGE = ROOT / '.runtime/balustrade-stage04' -AIR = 'minecraft:air' -N = {'east': (1, 0), 'north': (0, -1), 'south': (0, 1), 'west': (-1, 0)} - - -def module(name, path): - spec = importlib.util.spec_from_file_location(name, path) - value = importlib.util.module_from_spec(spec) - spec.loader.exec_module(value) - return value - - -survey = module('balustrade_survey', ROOT / 'scripts/foundation-survey.py') -geometry = module('balustrade_geometry', ROOT / 'scripts/foundation-study/geometry.py') - - -def compile_plan(before): - design = json.loads((ROOT / '.runtime/plaza-stage03/design-final.json').read_text()) - geo = json.loads((ROOT / '.runtime/foundations-stage02/foundations-final.geometry.json').read_text()) - cells = {(c['x'], c['z']): c for c in geo['cells']} - roads = {tuple(p) for r in geo['routes'] for p in r['clear_cells']} - outer = geometry.polygon_cells(design['outer_hex']) - edge = {p for p in outer if any((p[0]+dx, p[1]+dz) not in outer for dx, dz in N.values())} - gardens = set() - for bed in design['beds']: - gardens |= geometry.polygon_cells(bed['outline']) - gardens |= {tuple(p) for p in bed.get('additional_planting_columns', [])} - ordered = sorted(edge, key=lambda p: math.atan2(p[1]-9, p[0])) - chain, elbows = [], [] - for a, b in zip(ordered, ordered[1:]+ordered[:1]): - chain.append(a) - dx, dz = b[0]-a[0], b[1]-a[1] - if max(abs(dx), abs(dz)) > 1: - raise ValueError('Boundary order contains a nonadjacent jump') - if dx and dz: - candidates = [(a[0], b[1]), (b[0], a[1])] - def score(p): - state = before.state(p[0], 96, p[1]) - return (p in edge or p in chain, p in gardens or state != AIR, p not in outer, p) - # An opening remains open even when its diagonal connector lies in it. - p = min(candidates, key=score) - chain.append(p) - elbows.append(p) - if len(set(chain)) != len(chain): - raise ValueError('Cardinal perimeter is not a simple cycle') - selected = {p for p in chain if p not in roads} - for p in selected: - state = before.state(p[0], 96, p[1]) - if state != AIR and not ('stone_brick_wall[' in state or - (p in edge and state == 'minecraft:smooth_sandstone_slab[type=bottom,waterlogged=false]')): - raise ValueError(f'Protected decoration intersects fence at {p}: {state}') - if before.state(p[0], 97, p[1]) != AIR: - raise ValueError(f'Protected decoration intersects handrail at {p}') - # Rotate at an opening, then split into uninterrupted fence runs. - cut = next(i for i, p in enumerate(chain) if p not in selected) - linear = chain[cut:]+chain[:cut] - runs, run = [], [] - for p in linear: - if p in selected: - run.append(p) - elif run: - runs.append(run) - run = [] - if run: - runs.append(run) - piers = set() - for run in runs: - segments = max(1, round((len(run)-1)/8)) - piers |= {run[round(i*(len(run)-1)/segments)] for i in range(segments+1)} - piers |= {tuple(p) for p in design['outer_hex'] if tuple(p) in run} - desired, groups, footing_columns, replaced_rims = {}, {}, [], [] - def put(x, y, z, state, group): - if (x, z) in roads: - raise ValueError(f'Protected road cell at {(x, z)}') - before.state(x, y, z) - desired[x, y, z] = 'minecraft:'+state - groups[x, y, z] = group - for x, z in sorted(selected): - if (x, z) not in outer: - # Stop at an observed full support; never assume air or bury plants. - y = 95 - while before.state(x, y, z) == AIR: - y -= 1 - support = before.state(x, y, z).split('[')[0].removeprefix('minecraft:') - if support not in survey.FULL or y > 95: - raise ValueError(f'No simple observed footing at {(x, y, z)}') - if y < 95: - footing_columns.append({'x': x, 'z': z, 'support_y': y}) - if support == 'grass_block': - put(x, y, z, 'dirt', 'stable-buried-footing-soil') - for yy in range(y+1, 96): - put(x, yy, z, 'cut_sandstone' if yy == 95 else 'stone_bricks', 'grounded-elbow-footing') - if before.state(x, 96, z).startswith('minecraft:smooth_sandstone_slab'): - replaced_rims.append([x, z]) - if (x, z) in piers: - state = 'cut_sandstone' - else: - connected = {} - for name, (dx, dz) in N.items(): - neighbor = before.state(x+dx, 96, z+dz).split('[')[0].removeprefix('minecraft:') - connected[name] = (x+dx, z+dz) in selected or neighbor.endswith('_wall') or neighbor in survey.FULL - straight = (connected['east'] and connected['west'] and not connected['north'] and not connected['south']) or (connected['north'] and connected['south'] and not connected['east'] and not connected['west']) - props = {name: 'tall' if value else 'none' for name, value in connected.items()} - props |= {'up': 'false' if straight else 'true', 'waterlogged': 'false'} - state = 'stone_brick_wall['+','.join(f'{k}={v}' for k, v in sorted(props.items()))+']' - put(x, 96, z, state, 'sandstone-piers' if (x, z) in piers else 'connected-stone-balusters') - put(x, 97, z, 'smooth_sandstone_slab[type=bottom,waterlogged=false]', 'continuous-cream-handrail') - # Old uncapped road rails need reciprocal arms where the new fence joins them. - adjacent = {(x+dx, z+dz) for x, z in selected for dx, dz in N.values()} - selected - neighboring_rails = {p for p in adjacent if before.state(p[0], 96, p[1]).startswith('minecraft:stone_brick_wall[')} - for x, z in sorted(neighboring_rails): - if before.state(x, 97, z) != AIR: - raise ValueError('Neighboring rail has an unsupported cap configuration') - links = {} - for name, (dx, dz) in N.items(): - p = x+dx, 96, z+dz - neighbor = desired.get(p, before.state(*p)).split('[')[0].removeprefix('minecraft:') - links[name] = neighbor.endswith('_wall') or neighbor in survey.FULL - straight = (links['east'] and links['west'] and not links['north'] and not links['south']) or (links['north'] and links['south'] and not links['east'] and not links['west']) - props = {name: 'low' if linked else 'none' for name, linked in links.items()} - props |= {'up': 'false' if straight else 'true', 'waterlogged': 'false'} - put(x, 96, z, 'stone_brick_wall['+','.join(f'{k}={v}' for k, v in sorted(props.items()))+']', 'reciprocal-road-rail-joins') - blocks = [dict(zip(('x', 'y', 'z'), p)) | {'block': state, 'expected': before.state(*p), 'group': groups[p]} - for p, state in sorted(desired.items()) if state != before.state(*p)] - meta = {'version': 1, 'scope': before.scope, 'floor_y': 95, 'handrail_top_y': 97.5, - 'fence_columns': sorted(selected), 'pier_columns': sorted(piers), 'path_runs': runs, - 'elbow_columns': sorted(set(elbows) & selected), 'footing_columns': footing_columns, - 'neighboring_rail_columns': sorted(neighboring_rails), - 'replaced_planter_rims': replaced_rims, 'protected_road_columns': sorted(roads), - 'by_group': dict(Counter(b['group'] for b in blocks)), 'changed_blocks': len(blocks)} - # Reuse the independent garden and inherited-route auditor with the new obstacle mask. - garden_meta = json.loads((ROOT / '.runtime/plaza-stage03/plaza-polished.metadata.json').read_text()) - garden_meta['unwalkable_columns'] = sorted({tuple(p) for p in garden_meta['unwalkable_columns']} | selected) - walk = json.loads((ROOT / '.runtime/plaza-stage03/plaza-polished.walk.json').read_text()) - walk['points'] = [p for p in walk['points'] if (p['x'], p['z']) not in selected] - garden_meta['walk_samples'] = len(walk['points']) - return {'version': 1, 'scope': before.scope, 'blocks': blocks}, meta, garden_meta, walk - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--before', type=Path, default=STAGE / 'before-full.json.gz') - parser.add_argument('--output', type=Path, default=STAGE / 'balustrade.json') - args = parser.parse_args() - before = survey.load_snapshot(args.before) - plan, meta, garden_meta, walk = compile_plan(before) - args.output.parent.mkdir(parents=True, exist_ok=True) - for suffix, document in (('.json', plan), ('.metadata.json', meta), ('.garden-metadata.json', garden_meta), ('.walk.json', walk)): - args.output.with_suffix(suffix).write_text(json.dumps(document, separators=(',', ':'))+'\n') - print(json.dumps({k: meta[k] for k in ('changed_blocks', 'by_group', 'replaced_planter_rims')} - | {'fence_columns': len(meta['fence_columns']), 'piers': len(meta['pier_columns']), - 'runs': [len(r) for r in meta['path_runs']], 'footings': len(meta['footing_columns'])})) - - -if __name__ == '__main__': - main() diff --git a/scripts/build-shacraft-foundations.py b/scripts/build-shacraft-foundations.py deleted file mode 100644 index 69462f7..0000000 --- a/scripts/build-shacraft-foundations.py +++ /dev/null @@ -1,226 +0,0 @@ -#!/usr/bin/env python3 -"""Compile the reviewed foundations and streets into a checked, reversible block plan. - -No server writes here. A full live voxel survey is required, and differences from -the known natural world + previous marker receipts are protected, not adopted. -Apply the resulting JSON with scripts/layout.py. -""" -import argparse -from collections import Counter, deque -import importlib.util -import json -import math -from pathlib import Path - -ROOT=Path(__file__).resolve().parents[1] - -def module(name,path): - spec=importlib.util.spec_from_file_location(name,path) - value=importlib.util.module_from_spec(spec);spec.loader.exec_module(value);return value - -geometry=module('foundation_geometry',ROOT/'scripts/foundation-study/geometry.py') -survey=module('foundation_survey',ROOT/'scripts/foundation-survey.py') -N=((1,0),(-1,0),(0,1),(0,-1)) - - -def distances(mask): - dist={p:0 for p in mask if any((p[0]+dx,p[1]+dz) not in mask for dx,dz in N)} - q=deque(dist) - while q: - x,z=q.popleft() - for dx,dz in N: - p=(x+dx,z+dz) - if p in mask and p not in dist:dist[p]=dist[(x,z)]+1;q.append(p) - return dist - - -def compile_plan(design,layout,before,original,marker_documents): - scope=before.document['scope'];geo=geometry.build_geometry(design,layout);cells=geo['cells'] - mask=set(cells);edge=distances(mask);desired={};groups={} - known={} - for doc in marker_documents: - for b in doc['blocks']:known[(b['x'],b['y'],b['z'])]=b['block'] - def natural(x,z): - i=(z-original['min_z'])*original['width']+x-original['min_x'] - return original['surface_y'][i],original['palette'][original['material_index'][i]] - def predicted(x,y,z): - if (x,y,z) in known:return known[(x,y,z)] - h,material=natural(x,z) - if y>h:return 'minecraft:air' - if material=='minecraft:water':raise ValueError('Water column outside foundation scope') - if y==h:return material+'[snowy=false]' if material=='minecraft:grass_block' else material - return 'minecraft:dirt' if material=='minecraft:grass_block' and y>=h-3 else 'minecraft:stone' - def put(x,y,z,block,group): - if not block.startswith('minecraft:'):block='minecraft:'+block - desired[(x,y,z)]=block;groups[(x,y,z)]=group - # Remove only old survey blocks within this stage's exact footprint + 1-column - # cleanup margin. Outside district markers remain unchanged. - cleanup=mask|{(x+dx,z+dz) for x,z in mask for dx,dz in N} - for (x,y,z),state in known.items(): - if (x,z) not in cleanup:continue - try:before.state(x,y,z) - except (KeyError,ValueError):continue # Only the optional cleanup margin may extend beyond the survey. - h,m=natural(x,z) - if y>h:new='minecraft:air' - elif y==h:new=m+'[snowy=false]' if m=='minecraft:grass_block' else m - else:continue - put(x,y,z,new,'remove-obsolete-survey') - - road_clear={tuple(p) for r in geo['routes'] for p in r['clear_cells']} - road_buffer=road_clear|{(x+dx,z+dz) for x,z in road_clear for dx,dz in N} - # Exact solid footings and two structural courses beneath every walking deck. - for (x,z),c in cells.items(): - y=c['block_y'];h,_=natural(x,z);d=edge[(x,z)];group=c['group'] - bottom=min(h-1,y-2) if d<=1 else min(h,y-2) - for yy in range(bottom,y): - block='stone' - if d==0: - block='deepslate_bricks' if yy<=h+1 else 'stone_bricks' - if yy==y-1:block='polished_andesite' - if (x+z)%12 in (0,1) and yy>h+1:block='smooth_sandstone' - elif d==1 and yy==y-1:block='stone_bricks' - put(x,yy,z,block,group+'-foundation') - # Clear all natural overburden and former stakes above the finished floor. - for yy in range(y+1,max(h+2,y+4)+1):put(x,yy,z,'air',group+'-clearance') - if c['kind']=='stairs': - paving=f"stone_brick_stairs[facing={c['facing']},half=bottom,shape=straight,waterlogged=false]" - elif not c['clear'] or d==0:paving='polished_andesite' - elif d==1:paving='stone_bricks' - elif d==2:paving='smooth_sandstone' - elif group=='clock-station': - # Quiet structural floor; regular narrow foundation setting-out bands. - paving='stone_bricks' if x%16==0 or z%16==0 else 'smooth_stone' - elif group in ('arrival-hex','station-forecourt'): - paving='smooth_sandstone' - if x%12==0 or z%12==0:paving='smooth_stone' - else: - paving='smooth_stone' - if c['kind']=='full' and ((x if 'radial' in group or 'ring-' in group else z)%8==0):paving='stone_bricks' - put(x,y,z,paving,group+'-paving') - - # Green inset medallion. All tesserae are flush with the arrival paving. - hexagon=[(round(16*math.sin(math.pi/3*i)),round(9-16*math.cos(math.pi/3*i))) for i in range(6)] - medallion=geometry.polygon_cells(hexagon);md=distances(medallion) - for x,z in medallion: - put(x,95,z,'smooth_quartz' if md[(x,z)]<=1 else 'green_concrete','arrival-medallion') - glyph=['01110','11000','11000','01110','00011','00011','01110'] - for row,bits in enumerate(glyph): - for col,on in enumerate(bits): - if on=='1': - for dx in (0,1): - for dz in (0,1):put(-5+col*2+dx,95,2+row*2+dz,'smooth_quartz','arrival-medallion') - - # Keep the future tower and pavilion footing outlines readable in the deck. - for f in layout['features']: - if f['id'] not in ('station-clock-base','station-pavilion--63','station-pavilion-29'):continue - vertices=f['points'];outline=geometry.polygon_cells(vertices) - for x,z in outline: - if any((x+dx,z+dz) not in outline for dx,dz in N):put(x,98,z,'polished_andesite','station-structural-bands') - - # A shallow blind arcade breaks up the tall western station retaining wall. - # Each opening is one block deep, with an intact solid backing and lintel. - for center in (-132,-124,-116,-108): - for offset in range(-2,3): - z=center+offset;h,_=natural(-74,z);top=94-abs(offset) - for y in range(max(h+2,85),top+1): - put(-74,y,z,'air','station-west-blind-arcade') - put(-73,y,z,'deepslate_bricks','station-west-blind-arcade') - if top>=h+2:put(-74,top+1,z,'smooth_sandstone','station-west-arch-stones') - for z0 in (-138,-128,-120,-112,-104,-99): - for z in (z0,z0+1): - h,_=natural(-75,z) - for y in range(h-1,98):put(-75,y,z,'deepslate_bricks' if y=3 or c['group']=='station-entrance-stair':rail[(x,z)]=c['block_y']+1 - for (x,z),y in rail.items(): - props={name:('low' if (x+dx,z+dz) in rail and abs(rail[(x+dx,z+dz)]-y)<=1 else 'none') - for name,(dx,dz) in {'east':(1,0),'north':(0,-1),'south':(0,1),'west':(-1,0)}.items()} - straight=(props['east']==props['west']=='low' and props['north']==props['south']=='none') or (props['north']==props['south']=='low' and props['east']==props['west']=='none') - up='false' if straight and (x+z)%8 else 'true' - state=f"stone_brick_wall[east={props['east']},north={props['north']},south={props['south']},up={up},waterlogged=false,west={props['west']}]" - put(x,y,z,state,'edge-balustrades') - - # Roads meet the reserved bridge decks exactly. A temporary end balustrade - # prevents a finished street from leading straight into an unbuilt span. - bridge_gates=[] - for ident in ('east-radial-local','ring-northeast-local'): - r=next(r for r in geo['routes'] if r['id']==ident) - end_x,end_z=r['centerline'][-1];deck=cells[(end_x,end_z)]['block_y'] - zs=sorted(z for x,z in r['corridor'] if x==end_x) - for z in zs: - x=end_x+1;h,_=natural(x,z) - for y in range(min(h,deck-1),deck+1):put(x,y,z,'stone_bricks','temporary-bridge-threshold') - north='low' if z-1 in zs else 'none';south='low' if z+1 in zs else 'none' - put(x,deck+1,z,f'stone_brick_wall[east=none,north={north},south={south},up=true,waterlogged=false,west=none]','temporary-bridge-gates') - bridge_gates.append({'x':end_x+1.5,'y':deck+4,'z':end_z+.5,'deck_y':deck}) - - # Lit piers are part of the stone edge, never obstacles in the clear road lane. - candidates=[p for p in rail if cells[p]['kind']=='full'] - chosen=[] - landmarks=[(0,-37),(38,-14),(42,30),(0,56),(-42,30),(-38,-14), - (-36,-61),(27,-61),(-25,-83),(13,-83),(-74,-139),(40,-139),(-74,-98),(40,-98)] - for target in landmarks: - options=sorted(candidates,key=lambda p:(p[0]-target[0])**2+(p[1]-target[1])**2) - if options and math.dist(options[0],target)<12 and all(math.dist(options[0],p)>8 for p in chosen):chosen.append(options[0]) - for point in sorted(candidates,key=lambda p:(p[1],p[0])): - if all(math.dist(point,p)>19 for p in chosen):chosen.append(point) - for x,z in chosen: - y=cells[(x,z)]['block_y'] - put(x,y+1,z,'chiseled_stone_bricks','lamp-piers');put(x,y+2,z,'stone_bricks','lamp-piers') - put(x,y+3,z,'smooth_stone_slab[type=double,waterlogged=false]','lamp-piers') - put(x,y+4,z,'lantern[hanging=false,waterlogged=false]','lamps') - - # Preserve any unexpected human edits, including underground blocks. We do - # not silently rebase onto arbitrary newly observed content. - blocks=[];mismatches=[] - for (x,y,z),block in sorted(desired.items()): - actual=before.state(x,y,z);expected=predicted(x,y,z) - if actual!=expected: - mismatches.append({'x':x,'y':y,'z':z,'expected':expected,'actual':actual}) - continue - if block!=actual:blocks.append({'x':x,'y':y,'z':z,'block':block,'expected':actual,'group':groups[(x,y,z)]}) - if mismatches:raise ValueError(f'Unexpected live edits preserved: {len(mismatches)}, first {mismatches[:5]}') - # Sample both treads of every stair and every usable full floor column. Rails - # and lamp piers are excluded; actual after-survey tests their surrounding lanes. - walk=[] - for (x,z),c in cells.items(): - if (x,z) in rail or not c['clear']:continue - if c['kind']=='stairs': - for sub in (.25,.75): - sx,sz=(sub,.5) if c['facing'] in ('east','west') else (.5,sub) - walk.append({'x':x,'z':z,'standing_y':geometry.tread_height(c,sx,sz),'sub_x':sx,'sub_z':sz}) - else:walk.append({'x':x,'z':z,'standing_y':c['block_y']+1}) - metadata={'scope':scope,'geometry_checks':geo['checks'],'changed_blocks':len(blocks),'walk_samples':len(walk), - 'lamps':len(chosen),'rail_columns':len(rail),'by_material':dict(Counter(b['block'] for b in blocks)), - 'temporary_bridge_gates':bridge_gates, - 'source_snapshot_finished_at':before.document['finished_at'],'source_snapshot_atomic':False, - 'note':'Only zones01/02 foundations and local access roads; other districts remain marked reservations.'} - return {'version':1,'scope':scope,'blocks':blocks},metadata,{'scope':scope,'points':walk},geo - - -def main(): - p=argparse.ArgumentParser(description=__doc__) - p.add_argument('--design',type=Path,required=True);p.add_argument('--snapshot',type=Path,required=True) - p.add_argument('--output',type=Path,required=True);a=p.parse_args() - before=survey.load_snapshot(a.snapshot) - design=json.loads(a.design.read_text());layout=json.loads((ROOT/'examples/layout/shacraft-lobby-layout.json').read_text()) - original=json.loads((ROOT/'.runtime/server/plugins/ShacraftTerrain/maps/layout-before.json').read_text()) - markers=[json.loads((ROOT/name).read_text()) for name in ('.runtime/layout-study/markers-final.json','.runtime/layout-study/access-blocks.json')] - plan,meta,walk,geo=compile_plan(design,layout,before,original,markers) - a.output.parent.mkdir(parents=True,exist_ok=True) - a.output.write_text(json.dumps(plan,separators=(',',':'))+'\n') - a.output.with_suffix('.metadata.json').write_text(json.dumps(meta,indent=2)+'\n') - a.output.with_suffix('.walk.json').write_text(json.dumps(walk,separators=(',',':'))+'\n') - a.output.with_suffix('.geometry.json').write_text(json.dumps(geometry.serializable(geo),separators=(',',':'))+'\n') - print(json.dumps(meta)) - - -if __name__=='__main__':main() diff --git a/scripts/build-shacraft-plaza.py b/scripts/build-shacraft-plaza.py deleted file mode 100644 index c086560..0000000 --- a/scripts/build-shacraft-plaza.py +++ /dev/null @@ -1,294 +0,0 @@ -#!/usr/bin/env python3 -"""Compile a reference-led arrival garden against observed, protected voxel states. - -No world writes. Apply the resulting checked recipe with scripts/layout.py. -The brand bitmap is sampled into block coordinates, not used as a rendered fake. -""" -import argparse -from collections import Counter, deque -import hashlib -import importlib.util -import json -import math -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -N2 = ((1, 0), (-1, 0), (0, 1), (0, -1)) -N3 = ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)) -AIR = 'minecraft:air' - - -def module(name, path): - spec = importlib.util.spec_from_file_location(name, path) - m = importlib.util.module_from_spec(spec) - spec.loader.exec_module(m) - return m - - -geometry = module('plaza_geometry', ROOT / 'scripts/foundation-study/geometry.py') -survey = module('plaza_survey', ROOT / 'scripts/foundation-survey.py') -assets = module('plaza_assets', ROOT / 'scripts/plaza-assets.py') - - -def distances(mask): - result = {p: 0 for p in mask if any((p[0]+dx, p[1]+dz) not in mask for dx, dz in N2)} - pending = deque(result) - while pending: - x, z = pending.popleft() - for dx, dz in N2: - p = x+dx, z+dz - if p in mask and p not in result: - result[p] = result[x, z]+1 - pending.append(p) - return result - - -def brand_cells(path, width=25, height=35): - from PIL import Image - source = Image.open(path).convert('RGBA') - # Select the green artwork, excluding transparency and the pale antialias fringe. - mask = Image.new('L', source.size) - pixels = source.get_flattened_data() if hasattr(source, 'get_flattened_data') else source.getdata() - mask.putdata([255 if a > 100 and g > r*1.15 and g > b*1.15 else 0 for r, g, b, a in pixels]) - box = mask.getbbox() - if not box: - raise ValueError('Brand image contains no green artwork') - cells = mask.crop(box).resize((width, height), Image.Resampling.BOX) - return {(x-width//2, z+9-height//2) for z in range(height) for x in range(width) - if cells.getpixel((x, z)) >= 115} - - -def compile_plan(design, before, previous, logo): - base_geometry = json.loads((ROOT / '.runtime/foundations-stage02/foundations-final.geometry.json').read_text()) - cells = {(c['x'], c['z']): c for c in base_geometry['cells']} - outer = geometry.polygon_cells(design['outer_hex']) - floor = {p for p in outer if cells[p]['kind'] == 'full' and cells[p]['block_y'] == 95} - edge = distances(outer) - protected = {tuple(p) for route in base_geometry['routes'] for p in route['clear_cells']} - desired, groups = {}, {} - - def put(x, y, z, state, group): - if not state.startswith('minecraft:'): - state = 'minecraft:'+state - before.state(x, y, z) # Require observed support and air, including canopy overhangs. - desired[x, y, z], groups[x, y, z] = state, group - - def current(x, y, z): - return desired.get((x, y, z), before.state(x, y, z)) - - # Retire only still-matching zone01 survey marks, including its exterior - # stakes and number. Other districts and changed human blocks are preserved. - marker_groups = {'arrival-hex', 'spawn-medallion', 'spawn-monogram', 'label-01', 'wayfinding-01'} - markers = json.loads((ROOT/'.runtime/layout-study/markers-final.json').read_text()) - for b in markers['blocks']: - if b.get('group') not in marker_groups: - continue - p = b['x'], b['y'], b['z'] - try: - observed = before.state(*p) - except (KeyError, ValueError): - continue - if observed == b['block']: - put(*p, b['expected'], 'retire-zone01-survey') - - # Preserve stair treads, the foundation footprint, and all neighboring districts. - for x, z in sorted(floor): - material = 'smooth_sandstone' - if edge[x, z] == 0: - material = 'cut_sandstone' - elif edge[x, z] == 1: - material = 'smooth_stone' - put(x, 95, z, material, 'cream-paving') - - for radius, material in ((22, 'polished_andesite'), (24, 'smooth_stone'), (28, 'cut_sandstone')): - poly = [(round(radius*math.sin(i*math.pi/3)), 9-round(radius*math.cos(i*math.pi/3))) for i in range(6)] - ring = geometry.polygon_cells(poly) - for x, z in ring: - if any((x+dx, z+dz) not in ring for dx, dz in N2): - put(x, 95, z, material, 'hexagonal-paving-bands') - logo_mask = brand_cells(logo) - for x, z in logo_mask: - if (x, z) not in floor: - raise ValueError('Logo exceeds the paving') - put(x, 95, z, 'green_concrete', 'original-brand-inlay') - - # Replace the former bulky survey-stage light piers. Their perimeter rails remain. - old_plan = json.loads((ROOT / '.runtime/foundations-stage02/foundations-final.json').read_text()) - old_lamps = [(b['x'], b['z']) for b in old_plan['blocks'] if b.get('group') == 'lamps' - and ((b['x'], b['z']) in outer or (b['x'], b['z']) == (-5, 56))] - for x, z in old_lamps: - for y in range(96, 100): - put(x, y, z, 'air', 'replace-old-lamp-piers') - props = {} - for name, (dx, dz) in {'east': (1, 0), 'north': (0, -1), 'south': (0, 1), 'west': (-1, 0)}.items(): - neighbor = before.state(x+dx, 96, z+dz) - props[name] = 'low' if any(n in neighbor for n in ('stone_brick_wall', 'chiseled_stone_bricks')) else 'none' - put(x, 96, z, 'stone_brick_wall['+','.join(f'{k}={v}' for k, v in sorted(props.items() | {'up': 'true', 'waterlogged': 'false'}.items()))+']', 'restored-perimeter-rail') - - beds, beds_union, bench_aprons, fixture_records = [], set(), set(), [] - flower_types = ('pink_tulip', 'white_tulip', 'oxeye_daisy', 'allium', 'azure_bluet') - for index, bed in enumerate(design['beds']): - mask = geometry.polygon_cells(bed['outline']) - mask |= {tuple(p) for p in bed.get('additional_planting_columns', [])} - if not mask <= floor or mask & protected: - raise ValueError('Garden intersects a protected road or non-flat foundation') - beds.append(mask) - beds_union |= mask - inset = distances(mask) - for x, z in sorted(mask): - put(x, 94, z, 'dirt', 'garden-soil') - put(x, 95, z, 'grass_block[snowy=false]', 'garden-soil') - if inset[x, z] == 0: - put(x, 95, z, 'smooth_sandstone', 'planter-rim') - put(x, 96, z, 'smooth_sandstone_slab[type=bottom,waterlogged=false]', 'planter-rim') - else: - # Drifts follow small clusters, rather than an alternating plant checkerboard. - patch = (x//3+2*(z//3)+index) % 7 - if inset[x, z] == 1 and patch in (0, 1, 5): - put(x, 96, z, 'oak_leaves[distance=7,persistent=true,waterlogged=false]', 'low-evergreen-hedge') - elif (x*17+z*31) % 5 != 0: - flower = flower_types[patch % len(flower_types)] - put(x, 96, z, flower, 'flower-drifts') - - tree = bed['tree'] - put(tree['x'], 95, tree['z'], 'dirt', 'stable-tree-soil') - height = max(11, min(15, tree['height_above_floor'])) - for (dx, y, dz), state in assets.conifer(height, seed=1337+index*71).items(): - if 'leaves' in state and y < 4: - continue - put(tree['x']+dx, 96+y, tree['z']+dz, state, 'custom-conifers') - fixture_records.append({'type': 'conifer', 'x': tree['x'], 'z': tree['z'], 'height': height}) - t = bed['small_topiary'] - put(t['x'], 95, t['z'], 'dirt', 'stable-tree-soil') - for y in range(4): - put(t['x'], 96+y, t['z'], 'spruce_log[axis=y]', 'small-topiary') - for y, radius in ((2, 1), (3, 1), (4, 0)): - for dx in range(-radius, radius+1): - for dz in range(-radius, radius+1): - if dx*dx+dz*dz <= 2 and (dx or dz or y == 4): - put(t['x']+dx, 96+y, t['z']+dz, 'spruce_leaves[distance=7,persistent=true,waterlogged=false]', 'small-topiary') - fixture_records.append({'type': 'topiary', 'x': t['x'], 'z': t['z'], 'height': 5}) - - facing_vectors = {'north': (0, -1), 'east': (1, 0), 'south': (0, 1), 'west': (-1, 0)} - opposite = {'north': 'south', 'south': 'north', 'east': 'west', 'west': 'east'} - for bed, mask in zip(design['beds'], beds): - b = bed['bench'] - cx, cz = b['center'] - facing = opposite[b['back_faces']] - dx, dz = facing_vectors[facing] - # Open a level, three-block seat approach through the planter rim. - for offset in (-1, 0, 1): - for step in range(1, 8): - x, z = cx+dx*step-dz*offset, cz+dz*step+dx*offset - if (x, z) not in floor: - raise ValueError('Bench approach exceeds the plaza') - for y in (96, 97): - if '_log[' in current(x, y, z): - raise ValueError('Bench approach intersects a tree') - put(x, y, z, 'air', 'bench-access') - put(x, 95, z, 'smooth_sandstone', 'bench-access') - bench_aprons.add((x, z)) - if (x, z) not in mask: - break - for (ox, y, oz), state in assets.bench(b['length'], facing).items(): - x, z = cx+ox, cz+oz - if (x, z) not in floor or (x, z) in protected: - raise ValueError('Bench exceeds its clear garden bay') - for yy in (96, 97): - if '_log[' in current(x, yy, z): - raise ValueError('Bench intersects a tree root') - put(x, yy, z, 'air', 'bench-access') - put(x, 95, z, 'smooth_sandstone', 'bench-foundation') - put(x, 96+y, z, state, 'garden-benches') - fixture_records.append({'type': 'bench', 'x': cx, 'z': cz, 'facing': facing, 'seats': 3}) - - lamps = [(v['x'], v['z']) for v in design['lighting']['lamps']] - for x, z in lamps: - c = cells.get((x, z), {}) - if (x, z) in protected or c.get('kind') != 'full' or c.get('block_y') != 95: - raise ValueError('Lamp base intrudes into an approach') - put(x, 95, z, 'chiseled_stone_bricks', 'lamp-footing') - for (dx, y, dz), state in assets.lamp(6).items(): - put(x+dx, 96+y, z+dz, state, 'copper-garden-lamps') - fixture_records.append({'type': 'lamp', 'x': x, 'z': z, 'lantern_y': 100}) - - # Leaves use their stable distance to the composed logs, including touching hedges. - leaf_positions = {p for p, state in desired.items() if '_leaves[' in state} - logs = {p for p, state in desired.items() if '_log[' in state} - leaf_dist, pending = {}, deque() - for x, y, z in logs: - for dx, dy, dz in N3: - p = x+dx, y+dy, z+dz - if p in leaf_positions: - leaf_dist[p] = 1 - pending.append(p) - while pending: - x, y, z = pending.popleft() - if leaf_dist[x, y, z] >= 6: - continue - for dx, dy, dz in N3: - p = x+dx, y+dy, z+dz - if p in leaf_positions and p not in leaf_dist: - leaf_dist[p] = leaf_dist[x, y, z]+1 - pending.append(p) - for p in leaf_positions: - material = desired[p].split('[', 1)[0] - desired[p] = f'{material}[distance={leaf_dist.get(p, 7)},persistent=true,waterlogged=false]' - - # Every floor column with a low obstacle is excluded from the walking network. - blocked = set() - for x, z in floor: - if current(x, 96, z) != AIR or current(x, 97, z) != AIR: - blocked.add((x, z)) - blocked |= beds_union - bench_aprons - if blocked & protected: - raise ValueError(f'Decoration obstructs protected road cells: {sorted(blocked & protected)[:8]}') - for x, z in blocked: - if abs(x) <= 6 and (x, z) not in beds_union: - # Existing outer balustrades are handled by the previous navigation mask. - if any(groups.get((x, y, z), '') not in ('restored-perimeter-rail', '') for y in (96, 97)): - raise ValueError('Central sightline/walking axis was obstructed') - - blocks = [] - for p, state in sorted(desired.items()): - observed = before.state(*p) - try: - prior = previous.state(*p) - except (KeyError, ValueError): - if p[1] <= 114: - raise - prior = AIR - if observed != prior: - raise ValueError(f'Unexpected manual edit preserved at {p}') - if state != observed: - blocks.append(dict(zip(('x', 'y', 'z'), p)) | {'block': state, 'expected': observed, 'group': groups[p]}) - walk = [{'x': x, 'z': z, 'standing_y': 96} for x, z in sorted(floor-blocked)] - meta = {'scope': before.scope, 'district': '01', 'changed_blocks': len(blocks), 'planters': len(beds), - 'planter_columns': len(beds_union), 'trees': 6, 'topiary': 6, 'benches': 6, 'new_lamps': len(lamps), - 'old_lamps_replaced': len(old_lamps), 'logo_blocks': len(logo_mask), 'walk_samples': len(walk), - 'unwalkable_columns': sorted(blocked), 'bench_access_columns': sorted(bench_aprons), - 'fixtures': fixture_records, 'by_material': dict(Counter(b['block'] for b in blocks)), - 'by_group': dict(Counter(b['group'] for b in blocks)), 'source': 'Observed after-foundation baseline; approved reference sheets 03 and 11'} - return {'version': 1, 'scope': before.scope, 'blocks': blocks}, meta, {'scope': before.scope, 'points': walk} - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--design', type=Path, default=ROOT/'.runtime/plaza-stage03/design-final.json') - parser.add_argument('--before', type=Path, default=ROOT/'.runtime/plaza-stage03/before.json.gz') - parser.add_argument('--output', type=Path, default=ROOT/'.runtime/plaza-stage03/plaza.json') - parser.add_argument('--logo', type=Path, default=Path('/home/emil/Desktop/Shacraft-Lobby-References/brand/logo-180.png')) - args = parser.parse_args() - before = survey.load_snapshot(args.before) - previous = survey.load_snapshot(ROOT/'.runtime/foundations-stage02/after.json.gz') - design = json.loads(args.design.read_text()) - plan, meta, walk = compile_plan(design, before, previous, args.logo) - meta['source_logo_sha256'] = hashlib.sha256(args.logo.read_bytes()).hexdigest() - args.output.write_text(json.dumps(plan, separators=(',', ':'))+'\n') - args.output.with_suffix('.metadata.json').write_text(json.dumps(meta, indent=2)+'\n') - args.output.with_suffix('.walk.json').write_text(json.dumps(walk, separators=(',', ':'))+'\n') - print(json.dumps({k: v for k, v in meta.items() if k not in ('unwalkable_columns', 'bench_access_columns', 'fixtures', 'by_material')})) - - -if __name__ == '__main__': - main() diff --git a/scripts/build-shacraft-station.py b/scripts/build-shacraft-station.py deleted file mode 100644 index 9cc197d..0000000 --- a/scripts/build-shacraft-station.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python3 -"""Compile the two-floor clock station against observed, unchanged foundation voxels. - -No live writes: apply the resulting recipe using scripts/layout.py after QA. -""" -import argparse -import importlib.util -import json -from collections import Counter -from pathlib import Path - -ROOT=Path(__file__).resolve().parents[1] -STAGE=ROOT/'.runtime/station-stage07' - -def module(name,path): - spec=importlib.util.spec_from_file_location(name,path) - value=importlib.util.module_from_spec(spec);spec.loader.exec_module(value);return value - -survey=module('station_survey',ROOT/'scripts/foundation-survey.py') - -def canonical(state): - if '[' not in state:return state - name,raw=state[:-1].split('[',1) - return name+'['+','.join(sorted(raw.split(',')))+']' - -def compile_station(before): - layout=json.loads((ROOT/'docs/references/zone02-interior-v1/layout.json').read_text()) - geometry=json.loads((ROOT/'.runtime/foundations-stage02/foundations-final.geometry.json').read_text()) - foundation=survey.load_snapshot(ROOT/'.runtime/foundations-stage02/after.json.gz') - foot={(c['x'],c['z']) for c in geometry['cells'] if c['group']=='clock-station'} - allowed=set(json.loads((STAGE/'context-public.json').read_text())['supported_materials']) - exterior=module('station_exterior',ROOT/'scripts/station-exterior.py') - interior=module('station_interior',ROOT/'scripts/station-interior.py') - states,groups,ext=exterior.compile_exterior(foot,layout) - decorations,labels,intmeta=interior.compile_interior(foot,layout) - changes=Counter() - for p,v in decorations.items(): - if p in states and states[p]!=v:changes[(groups[p],labels[p])]+=1 - states.update(decorations);groups.update(labels) - rows=[];conflicts=[] - for p,value in sorted(states.items()): - state=canonical(value) - if state.split('[')[0] not in allowed:raise ValueError(f'Material unavailable: {state}') - observed=before.state(*p) - if observed==state:continue - if observed not in survey.AIR: - try: prior=foundation.state(*p) - except KeyError:prior=None - if observed!=prior:conflicts.append({'at':p,'current':observed,'foundation_stage':prior}) - rows.append(dict(zip(('x','y','z'),p))|{'block':state,'expected':observed,'group':groups[p]}) - if conflicts:raise ValueError(f'Unexpected existing changes preserved: {conflicts[:12]} ({len(conflicts)} total)') - recipe={'version':1,'scope':before.scope,'blocks':rows} - desired={(b['x'],b['y'],b['z']):b['block'] for b in rows} - def state(x,y,z):return desired.get((x,y,z),before.state(x,y,z)) - inner=exterior._erode(foot,2) - floors=[] - for feet,name in [(99,'vestibule'),(113,'smash')]: - candidates=set(inner) - if feet==99:candidates|={(x,z) for x in range(-9,-2) for z in range(-85,-82)} - clear=sorted(p for p in candidates if all(state(p[0],y,p[1]) in survey.AIR for y in range(feet,feet+4))) - floors.append({'id':name,'standing_y':feet,'source':{'x':-6,'z':-120},'clear_columns':clear,'min_headroom':4}) - def region(name,x0,x1,y0,y1,z0,z1): - return {'id':name,'min':{'x':x0,'y':y0,'z':z0},'max':{'x':x1,'y':y1,'z':z1}} - clear_regions=[region('entrance',-9,-3,99,102,-85,-83)] - for feet in [99,113]: - clear_regions += [region(f'cabin-{feet}',-8,-4,feet,feet+3,-122,-118), - region(f'lift-door-{feet}',-8,-4,feet,feet+3,-117,-116)] - clear_regions += [region('ground-entry-aisle',-9,-3,99,102,-115,-86), - region('smash-bay-front',-46,11,113,116,-135,-129)] - metadata={'scope':before.scope,'exterior':ext,'interior':intmeta,'public_floors':floors, - 'clear_regions':clear_regions,'footprint':sorted(foot), - 'containment':{'bounds':{'min':{'x':-79,'y':94,'z':-150},'max':{'x':45,'y':163,'z':-76}}, - 'authorized_caps':[region('ground-entrance-audit-cap',-9,-3,99,110,-84,-84)], - 'forbidden_y_at_or_above':126, - 'seeds':[{'id':'vestibule-and-cabin','x':-6,'y':99,'z':-120,'min_y':99,'max_y':110}, - {'id':'smash-and-cabin','x':-6,'y':113,'z':-120,'min_y':113,'max_y':123}]}, - 'compile_summary':{'written_blocks':len(rows),'intended_states':len(states),'materials':dict(Counter(b['block'].split('[')[0] for b in rows)), - 'layer_overrides':[{'exterior':a,'interior':b,'count':n} for (a,b),n in changes.items()]}} - return recipe,metadata - -def main(): - parser=argparse.ArgumentParser(description=__doc__) - parser.add_argument('--before',type=Path,default=STAGE/'before.json.gz') - parser.add_argument('--output',type=Path,default=STAGE/'compiled') - args=parser.parse_args();recipe,metadata=compile_station(survey.load_snapshot(args.before)) - args.output.mkdir(parents=True,exist_ok=True) - for name,data in [('station.json',recipe),('station.metadata.json',metadata)]: - (args.output/name).write_text(json.dumps(data,separators=(',',':'))+'\n') - print(json.dumps(metadata['compile_summary'])) - -if __name__=='__main__':main() diff --git a/scripts/builds/gothic_hall/main_hall.py b/scripts/builds/gothic_hall/main_hall.py deleted file mode 100644 index 1959d5b..0000000 --- a/scripts/builds/gothic_hall/main_hall.py +++ /dev/null @@ -1,291 +0,0 @@ -"""Main Gothic hall geometry in local coordinates; pure voxel composition, no IO.""" - - -def build(v): - """Compose the north-facing hall, hollow interior, roof, and attached masonry.""" - stone = "minecraft:stone_bricks" - ashlar = "minecraft:polished_andesite" - plinth = "minecraft:andesite" - pale = "minecraft:polished_diorite" - dark = "minecraft:deepslate_tiles" - glass = "minecraft:tinted_glass" - wood = "minecraft:dark_oak_planks" - oak = "minecraft:spruce_planks" - - def slab(material="stone_brick", half="bottom"): - return f"minecraft:{material}_slab[type={half},waterlogged=false]" - - def stair(material, facing, half="bottom"): - return (f"minecraft:{material}_stairs[facing={facing},half={half}," - "shape=straight,waterlogged=false]") - - def log(axis="y"): - return f"minecraft:dark_oak_log[axis={axis}]" - - def roof_height(x): - return 43 - (abs(x - 19) * 16 + 13) // 14 - - def pinnacle(x, z, base, height=8): - """Square masonry shaft with a cap, taper, and slender stone finial.""" - v.box(x - 1, base, z - 1, x + 1, base, z + 1, ashlar) - v.box(x, base + 1, z, x, base + height - 3, z, stone) - v.box(x - 1, base + height - 4, z - 1, - x + 1, base + height - 4, z + 1, slab()) - v.set(x, base + height - 2, z, ashlar) - v.set(x, base + height - 1, z, slab()) - - def side_window(face, inner, center, bottom, tops, trim_face): - """Carved pointed aperture with glass one block behind the wall face.""" - half = max(tops) - lo, hi = sorted((face, inner)) - for dz in range(-half, half + 1): - top = tops[abs(dz)] - v.clear(lo, bottom, center + dz, hi, top, center + dz) - v.box(inner, bottom, center + dz, inner, top, center + dz, glass) - v.set(trim_face, top + 1, center + dz, ashlar) - for dz in (-half - 1, half + 1): - v.box(trim_face, bottom - 1, center + dz, - trim_face, tops[half] + 1, center + dz, ashlar) - v.box(trim_face, bottom - 1, center - half - 1, - trim_face, bottom - 1, center + half + 1, pale) - # Alternating vertical/horizontal pieces describe the arch, rather than - # colouring an arch outline on an otherwise flat facade. - for dz in range(-half, half + 1): - top = tops[abs(dz)] - if dz: - facing = "south" if dz < 0 else "north" - v.set(trim_face, top + 2, center + dz, stair("stone_brick", facing)) - v.set(trim_face, tops[0] + 2, center, slab()) - - def front_aperture(z_face, z_glass, center, bottom, tops): - for dx in range(-max(tops), max(tops) + 1): - top = tops[abs(dx)] - v.clear(center + dx, bottom, z_face, center + dx, top, z_glass) - v.box(center + dx, bottom, z_glass, center + dx, top, z_glass, glass) - - # Dense foundations; two genuinely hollow occupied storeys above them. - v.box(7, 0, 13, 31, 0, 56, plinth) - v.box(7, 1, 13, 31, 1, 56, ashlar) - v.box(7, 2, 13, 31, 27, 56, stone) - v.clear(9, 2, 15, 29, 5, 54) - v.clear(9, 7, 15, 29, 43, 54) - v.box(9, 6, 15, 29, 6, 54, wood) - v.box(17, 6, 15, 21, 6, 53, ashlar) - for x in (9, 29): - v.box(x, 6, 15, x, 6, 54, stone) - for z in (15, 54): - v.box(9, 6, z, 29, 6, z, stone) - - # Continuous ledges make the raised main floor legible from the courtyard. - for x in (6, 32): - v.box(x, 0, 13, x, 1, 56, ashlar) - v.box(x, 5, 13, x, 5, 56, stone) - v.box(x, 6, 13, x, 6, 56, slab("smooth_stone")) - v.box(x, 25, 12, x, 25, 57, ashlar) - v.box(x, 26, 12, x, 26, 57, pale) - v.box(x, 27, 12, x, 27, 57, slab()) - for z in (12, 57): - v.box(6, 5, z, 32, 5, z, stone) - v.box(6, 6, z, 32, 6, z, slab("smooth_stone")) - v.box(6, 25, z, 32, 25, z, ashlar) - v.box(6, 26, z, 32, 26, z, pale) - # Timber is visible in the short shadow immediately below the roof edge. - for x in (7, 31): - v.box(x, 26, 15, x, 26, 54, log("z")) - for z in range(15, 55, 2): - v.set(x, 25, z, stair("spruce", "east" if x == 7 else "west", "top")) - - bays = (18, 26, 34, 42, 50) - piers = (14, 22, 30, 38, 46, 54) - for center in bays: - for face, inner, trim in ((7, 8, 6), (31, 30, 32)): - side_window(face, inner, center, 10, {0: 22, 1: 21, 2: 19}, trim) - # The recessed central mullion and transom divide four dark lancets. - v.box(inner, 10, center, inner, 20, center, ashlar) - v.box(inner, 15, center - 2, inner, 15, center + 2, ashlar) - v.set(inner, 21, center, wood) - # A modest three-wide basement window below each high bay. - side_window(face, inner, center, 2, {0: 4, 1: 3}, trim) - v.set(inner, 2, center, wood) - # Deep, bevelled main-window sill; its ends connect to the piers. - outside = trim - 1 if trim < 19 else trim + 1 - v.box(outside, 8, center - 3, outside, 8, center + 3, slab()) - v.box(trim, 9, center - 3, trim, 9, center + 3, stone) - - # Each buttress has an outward foot and two real setbacks. - for z in piers: - for left in (True, False): - if left: - foot, middle, shaft, outside = (3, 7), (4, 7), (5, 7), 5 - else: - foot, middle, shaft, outside = (31, 35), (31, 34), (31, 33), 33 - v.box(foot[0], 0, z - 1, foot[1], 1, z + 1, plinth) - v.box(middle[0], 2, z - 1, middle[1], 5, z + 1, stone) - v.box(middle[0], 6, z - 1, middle[1], 6, z + 1, slab("smooth_stone")) - v.box(shaft[0], 7, z - 1, shaft[1], 11, z + 1, stone) - v.box(shaft[0], 12, z - 1, shaft[1], 12, z + 1, ashlar) - v.box(min(outside, 7 if left else 31), 13, z, - max(outside, 7 if left else 31), 24, z, stone) - v.set(outside, 19, z, ashlar) - v.box(shaft[0], 25, z - 1, shaft[1], 25, z + 1, ashlar) - v.set(outside, 26, z, slab("smooth_stone")) - pinnacle(outside + (1 if left else -1), z, 27, 7) - - # Recessed rear lancets, kept separate from the side gallery connection. - for center in (12, 19, 26): - front_aperture(55, 56, center, 10, {0: 22, 1: 20}) - for dx in (-2, 2): - v.box(center + dx, 9, 57, center + dx, 21, 57, ashlar) - v.set(center, 24, 57, ashlar) - v.box(center - 2, 9, 57, center + 2, 9, 57, pale) - - # Steep, two-cell-thick stepped roof. Both sides slope to the central ridge. - for x in range(5, 34): - high = roof_height(x) - if x == 19: - v.box(x, 42, 12, x, 43, 57, dark) - else: - v.box(x, high - 1, 12, x, high, 57, dark) - v.box(x, high, 12, x, high, 57, - stair("deepslate_tile", "east" if x < 19 else "west")) - # A restrained ridge crest uses slabs and piers instead of unsupported fences. - v.box(19, 43, 14, 19, 43, 55, slab("stone_brick")) - for z in range(15, 56, 4): - v.set(19, 43, z, ashlar) - - # Front and rear stepped gables: substantial masonry underneath the coping. - for z in (12, 57): - for x in range(6, 33): - high = roof_height(x) - v.box(x, 27, z, x, high, z, stone) - v.set(x, high, z, ashlar) - if x != 19: - v.set(x, high + 1, z, stair("stone_brick", "east" if x < 19 else "west")) - v.box(19, 44, z, 19, 46, z, ashlar) - v.box(18, 47, z, 20, 47, z, stone) - v.set(19, 48, z, slab()) - - # Tall north facade, projecting three-layer porch, and its open pointed door. - v.box(13, 6, 9, 25, 6, 14, ashlar) - v.box(14, 6, 8, 24, 6, 8, ashlar) - portal_top = {0: 19, 1: 18, 2: 17, 3: 15, 4: 13} - for dx, top in portal_top.items(): - for sign in (-1, 1) if dx else (1,): - x = 19 + sign * dx - v.clear(x, 7, 9, x, top, 15) - for z, material, expand in ((9, ashlar, 1), (10, stone, 0), (11, pale, 0)): - for dx in (-5, 5): - v.box(19 + dx, 7, z, 19 + dx, 14, z, material) - for dx, top in portal_top.items(): - for sign in (-1, 1) if dx else (1,): - x = 19 + sign * dx - v.box(x, top + 1, z, x, top + 1 + expand, z, material) - for x in (13, 25): - v.box(x, 7, 10, x, 14, 12, stone) - v.box(x, 15, 10, x, 15, 12, slab()) - # Open wooden leaves are flush with the inner jambs, not across the entrance. - v.box(15, 7, 14, 15, 12, 15, log()) - v.box(23, 7, 14, 23, 12, 15, log()) - v.clear(16, 7, 9, 22, 12, 15) - - # Two slim flanking lancets and a large upper traceried front window. - for center in (10, 28): - front_aperture(12, 14, center, 10, {0: 23, 1: 21}) - for x in (center - 2, center + 2): - v.box(x, 9, 11, x, 22, 11, ashlar) - v.set(center, 25, 11, slab()) - v.box(center - 2, 9, 11, center + 2, 9, 11, pale) - front_aperture(12, 14, 19, 29, {0: 39, 1: 38, 2: 36, 3: 33}) - for dx, top in {0: 39, 1: 38, 2: 36, 3: 33}.items(): - for sign in (-1, 1) if dx else (1,): - v.set(19 + dx * sign, top + 1, 11, ashlar) - for x in (15, 23): - v.box(x, 28, 11, x, 34, 11, ashlar) - for x in (18, 20): - v.box(x, 29, 14, x, 36, 14, ashlar) - v.box(16, 33, 14, 22, 33, 14, ashlar) - v.box(15, 28, 11, 23, 28, 11, pale) - # Small heraldic mosaic echoes the reference's central hanging cross. - v.box(17, 22, 11, 21, 27, 11, dark) - v.box(19, 23, 10, 19, 26, 10, pale) - v.box(18, 25, 10, 20, 25, 10, pale) - v.set(19, 21, 11, dark) - for x in (7, 31): - v.box(x - 1, 2, 11, x + 1, 9, 13, stone) - v.box(x, 10, 11, x, 28, 12, ashlar) - for y in (6, 13, 25): - v.box(x - 1, y, 10, x + 1, y, 13, slab("smooth_stone")) - pinnacle(x, 12, 29, 9) - - # Five small pointed dormers on each long roof slope. - for z in bays: - for left in (True, False): - lo, hi, face, inner = (10, 14, 10, 11) if left else (24, 28, 28, 27) - v.clear(lo, 33, z - 1, hi, 36, z + 1) - for dz in (-2, 2): - v.box(lo, 32, z + dz, hi, 37, z + dz, dark) - v.box(face, 32, z - 1, face, 37, z + 1, ashlar) - v.clear(min(face, inner), 34, z, max(face, inner), 36, z) - v.box(inner, 34, z, inner, 36, z, glass) - for dz in range(-2, 3): - top = 39 - abs(dz) - v.box(lo, top, z + dz, hi, top, z + dz, dark) - v.set(face, top, z + dz, ashlar) - v.set(face, 40, z, slab()) - - # Galleries retain a broad, open central nave and three-block walkways. - for x1, x2 in ((9, 12), (26, 29)): - v.box(x1, 16, 16, x2, 16, 52, wood) - rail = x2 if x1 < 19 else x1 - v.box(rail, 17, 16, rail, 17, 52, slab("stone_brick")) - for z in range(16, 53, 4): - v.set(rail, 17, z, ashlar) - v.set(rail, 18, z, slab()) - for z in piers[1:]: - if z > 52: - continue - v.box(rail, 7, z, rail, 15, z, stone) - v.box(rail - 1, 7, z - 1, rail + 1, 7, z + 1, ashlar) - v.box(rail - 1, 15, z, rail + 1, 15, z, ashlar) - # A rear bridge connects both galleries to the single staircase. - v.box(9, 16, 51, 29, 16, 53, wood) - v.box(12, 17, 50, 25, 17, 50, slab()) - for x in (12, 19, 25): - v.set(x, 17, 50, ashlar) - v.set(x, 18, 50, slab()) - for x in (12, 26): - v.box(x, 7, 52, x, 15, 52, stone) - # Transverse oak roof frames are visible from the nave between the dormers. - for z in (22, 30, 38, 46, 54): - v.box(9, 28, z, 29, 28, z, log("x")) - for x in range(9, 30): - y = 40 - abs(x - 19) - v.set(x, y, z, wood) - for x in (9, 29): - v.box(x, 19, z, x, 28, z, log()) - - # Rear right gallery stair: ten rises, with its complete headroom shaft. - v.clear(26, 7, 39, 28, 19, 49) - for step in range(10): - y, z = 7 + step, 40 + step - if y > 7: - v.box(26, 7, z, 28, y - 1, z, wood) - v.box(26, y, z, 28, y, z, stair("spruce", "south")) - # Separate basement access under the west gallery; the main floor is pierced. - v.clear(10, 2, 43, 11, 9, 48) - for step in range(5): - y, z = 2 + step, 44 + step - if y > 2: - v.box(10, 2, z, 11, y - 1, z, stone) - v.box(10, y, z, 11, y, z, stair("stone_brick", "south")) - - # Modest furnishings preserve a five-block processional route to the dais. - v.box(14, 7, 50, 24, 7, 53, wood) - v.box(16, 7, 49, 22, 7, 49, stair("spruce", "south")) - for z in (24, 30, 36, 42): - for x1, x2 in ((14, 16), (22, 24)): - v.box(x1, 7, z, x2, 7, z, stair("spruce", "north")) - v.set(x1, 8, z, slab("spruce")) - v.set(x2, 8, z, slab("spruce")) - # Agreed lower-level passage to the east gallery/wing, cut after decoration. - v.clear(30, 7, 36, 35, 11, 39) diff --git a/scripts/builds/gothic_hall/polish_architecture.py b/scripts/builds/gothic_hall/polish_architecture.py deleted file mode 100644 index ba772f3..0000000 --- a/scripts/builds/gothic_hall/polish_architecture.py +++ /dev/null @@ -1,120 +0,0 @@ -"""Add the architecture polish layer to the finished v1 scene, without I/O. - -The input is scene.Voxels after the eighteen finishing corrections. V1 modules -remain immutable; removed blocks disappear from this sparse voxel composition. -""" - - -def build(v): - """Restore the roof silhouette and add restrained Gothic stone/iron detail.""" - stone = "minecraft:stone_bricks" - trim = "minecraft:polished_andesite" - dark = "minecraft:deepslate_tiles" - glass = "minecraft:gray_stained_glass" - wall = ("minecraft:stone_brick_wall[east=none,north=none,south=none," - "up=true,waterlogged=false,west=none]") - - def stair(material, facing, half="bottom"): - return (f"minecraft:{material}_stairs[facing={facing},half={half}," - "shape=straight,waterlogged=false]") - - def bars(north=False, south=False, east=False, west=False): - flags = {"east": east, "north": north, "south": south, "west": west} - return "minecraft:iron_bars[" + ",".join( - f"{name}={str(value).lower()}" for name, value in flags.items() - ) + ",waterlogged=false]" - - def roof_height(x): - return 43 - (abs(x - 19) * 16 + 13) // 14 - - # Use the actual input mask so a material replacement cannot fill an opening. - # Preserve the six pale blocks of the heraldic cross on the north facade. - crest = {(19, y, 10) for y in range(23, 27)} | { - (x, 25, 10) for x in range(18, 21) - } - timber = { - p: block for p, block in v.blocks.items() - if 9 <= p[0] <= 29 and 28 <= p[1] <= 40 and 15 <= p[2] <= 54 - and (block.startswith("minecraft:dark_oak_log[") - or block == "minecraft:dark_oak_planks") - } - for p, block in tuple(v.blocks.items()): - if block == "minecraft:polished_diorite" and p not in crest: - v.set(*p, trim) - elif block == "minecraft:tinted_glass": - v.set(*p, glass) - elif (block == "minecraft:cut_sandstone" - and 39 <= p[0] <= 41 and 34 <= p[1] <= 37 and 48 <= p[2] <= 50): - v.set(*p, "minecraft:gold_block") - - # Remove only the ten old dormer envelopes, then reconstruct the same - # two-cell roof section. These envelopes exclude both gables and all trusses. - for z in (18, 26, 34, 42, 50): - for x1, x2 in ((10, 14), (24, 28)): - v.clear(x1, 32, z - 2, x2, 40, z + 2) - for x in range(x1, x2 + 1): - high = roof_height(x) - for y in (high - 1, high): - if 32 <= y <= 40: - block = dark if y < high else stair( - "deepslate_tile", "east" if x < 19 else "west") - v.box(x, y, z - 2, x, y, z + 2, block) - - # Three narrow, low dormers per slope: a one-by-two recessed light, three - # blocks of facade width, and a short roof merging into the original slope. - for z in (22, 34, 46): - for face, inner, back in ((10, 11, 13), (28, 27, 25)): - x1, x2 = sorted((face, back)) - near = back - 1 if face < back else back + 1 - v.clear(min(face, near), 33, z, max(face, near), 34, z) - for dz in (-1, 1): - v.box(x1, 32, z + dz, x2, 34, z + dz, dark) - facing = "south" if dz < 0 else "north" - v.box(x1, 35, z + dz, x2, 35, z + dz, - stair("deepslate_tile", facing)) - v.box(face, 33, z + dz, face, 34, z + dz, trim) - v.set(face, 35, z + dz, stair("stone_brick", facing)) - v.box(x1, 36, z, x2, 36, z, dark) - v.box(face, 32, z - 1, face, 32, z + 1, trim) - v.box(inner, 33, z, inner, 34, z, glass) - v.set(face, 35, z, stone) - v.set(face, 36, z, wall) - - # The old bright, chunky ridge is now a dark backing for one thin iron rail. - v.box(19, 43, 14, 19, 43, 55, dark) - for z in range(14, 56): - v.set(19, 44, z, bars(north=z > 14, south=z < 55)) - - # Replace full-block mullions with slender rods in their original glazing - # plane. Connected horizontal bars provide a clear transom without a grille. - for center in (18, 26, 34, 42, 50): - for inner, outside in ((8, 6), (30, 32)): - for y in range(10, 21): - v.set(inner, y, center, bars()) - for dz in range(-2, 3): - v.set(inner, 15, center + dz, - bars(north=dz > -2, south=dz < 2)) - # A bevel on the underside of each stepped arch reduces square caps. - for dz, top in ((-2, 19), (-1, 21), (1, 21), (2, 19)): - v.set(outside, top + 1, center + dz, - stair("stone_brick", "north" if dz < 0 else "south", "top")) - for x in (18, 20): - for y in range(29, 37): - v.set(x, y, 14, bars()) - for x in range(16, 23): - v.set(x, 33, 14, bars(east=x < 22, west=x > 16)) - - # Taper the tips, retaining the full support blocks installed by finish v1. - for x in (6, 32): - for z in (14, 22, 30, 38, 46, 54): - v.box(x, 32, z, x, 33, z, wall) - for x in (7, 31): - v.box(x, 36, 12, x, 37, 12, wall) - for x in (35, 45): - for z in (43, 55): - v.box(x, 49, z, x, 50, z, wall) - - # Two new dormer centers cross truss planes. Keep every original timber - # voxel, including the inclined members, instead of cutting a frame for glass. - for p, block in timber.items(): - v.set(*p, block) diff --git a/scripts/builds/gothic_hall/polish_scene.py b/scripts/builds/gothic_hall/polish_scene.py deleted file mode 100644 index b755471..0000000 --- a/scripts/builds/gothic_hall/polish_scene.py +++ /dev/null @@ -1,159 +0,0 @@ -"""Reference-driven finishing layer; the original scene and its ledgers stay immutable.""" -from .scene import build_scene as original_scene -from .polish_architecture import build as architecture - -STONE = 'minecraft:stone_bricks' -TRIM = 'minecraft:polished_andesite' -LEAVES = 'minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]' -CHAIN = 'minecraft:iron_chain[axis=y,waterlogged=false]' -SLAB = 'minecraft:stone_brick_slab[type=bottom,waterlogged=false]' - - -def stair(facing, half='top'): - return f'minecraft:stone_brick_stairs[facing={facing},half={half},shape=straight,waterlogged=false]' - - -def bars(north=False, east=False, south=False, west=False): - return ('minecraft:iron_bars[' + ','.join(f'{key}={str(value).lower()}' for key, value in - [('north', north), ('east', east), ('south', south), ('west', west)]) + ',waterlogged=false]') - - -def finished_scene(): - v = original_scene() - for x in (6, 32): - for z in (14, 22, 30, 38, 46, 54): - v.set(x, 30, z, STONE) - for x in (7, 31): - v.set(x, 34, 12, STONE) - for z in range(36, 40): - v.set(35, 8, z, stair('east', 'bottom')) - return v - - -def lamp(v, x, y, z, hanging=True): - v.set(x, y, z, f'minecraft:lantern[hanging={str(hanging).lower()},waterlogged=false]') - - -def lighting(v): - v.stage = 'lanterns and interior lighting' - # Portal fixtures attach to the existing masonry jambs. - for x in (13, 25): - v.set(x, 14, 9, stair('south')) - v.set(x, 13, 9, CHAIN) - lamp(v, x, 12, 9) - for x, facing, bays in ((4, 'east', (14, 22, 30, 38, 46, 54)), - (34, 'west', (14, 22, 30))): - for z in bays: - v.set(x, 16, z, stair(facing)) - v.set(x, 15, z, CHAIN) - lamp(v, x, 14, z) - for x in (43, 52): - v.set(x, 8, 14, stair('south')) - v.set(x, 7, 14, CHAIN) - lamp(v, x, 6, 14) - for z in (21, 29, 37): - v.set(59, 14, z, TRIM) - v.set(60, 14, z, stair('west')) - v.set(60, 13, z, CHAIN) - lamp(v, 60, 12, z) - # Existing low piers become courtyard lamps, with a full supporting cap. - for x in (8, 30, 35, 61): - for z in (6, 59): - v.set(x, 4, z, TRIM) - lamp(v, x, 5, z, False) - # Three chandeliers hang in the clear nave, below the preserved timber ties. - for z in (22, 38, 46): - v.box(19, 19, z, 19, 27, z, CHAIN) - for dx in range(-2, 3): - v.set(19 + dx, 18, z, bars(dx == 0, dx < 2, dx == 0, dx > -2)) - for dz in (-2, -1, 1, 2): - v.set(19, 18, z + dz, bars(dz > -2, False, dz < 2, False)) - for x, zz in ((17, z), (21, z), (19, z - 2), (19, z + 2)): - lamp(v, x, 17, zz) - for x, facing in ((13, 'west'), (25, 'east')): - for z in (22, 38, 46): - v.set(x, 12, z, stair(facing)) - v.set(x, 11, z, CHAIN) - lamp(v, x, 10, z) - # Wing ground floor: lights hang from the existing floor beams. - for x in (43, 50): - for z in (23, 31): - v.set(x, 6, z, CHAIN) - lamp(v, x, 5, z) - for z in (23, 35): - v.box(38, 17, z, 57, 17, z, 'minecraft:spruce_log[axis=x]') - v.box(47, 14, z, 47, 16, z, CHAIN) - lamp(v, 47, 13, z) - # Tower landings and the block-built bell receive a restrained warm light. - for y in (7, 16, 25): - v.box(40, y + 1, 46, 40, y + 2, 46, CHAIN) - lamp(v, 40, y, 46) - for x in (37, 43): - v.box(x, 39, 49, x, 40, 49, CHAIN) - lamp(v, x, 38, 49) - - -def landscape(v): - v.stage = 'planted stone terrace' - - def bed(x1, z1, x2, z2, high=3): - # Soil replaces only the known terrace surface, with a low dressed rim. - for x in range(x1, x2 + 1): - for z in range(z1, z2 + 1): - edge = x in (x1, x2) or z in (z1, z2) - if edge: - if (x, 1, z) not in v.blocks: - v.set(x, 1, z, SLAB) - else: - v.set(x, 0, z, 'minecraft:dirt') - height = high - int((x * 7 + z * 11) % 5 == 0) - for y in range(1, height + 1): - if (x, y, z) not in v.blocks: - v.set(x, y, z, LEAVES) - - for z in (18, 26, 34, 42, 50): - bed(1, z - 2, 5, z + 2, 2) - for z in (21, 29, 37): - bed(60, z - 2, 64, z + 2, 2) - bed(5, 9, 12, 11, 2) - bed(26, 9, 32, 11, 2) - bed(39, 11, 43, 13, 2) - bed(52, 11, 56, 13, 2) - bed(9, 59, 28, 61, 2) - bed(48, 45, 59, 47, 2) - - def narrow_tree(x, z, height): - # Persistent leaves cannot decay; every addition respects existing masonry. - if (x, 0, z) not in v.blocks or v.blocks[(x, 0, z)].split('[')[0] in ( - 'minecraft:smooth_stone', 'minecraft:polished_andesite', 'minecraft:stone_bricks'): - v.set(x, 0, z, 'minecraft:moss_block') - for y in range(1, height - 1): - if (x, y, z) not in v.blocks or v.blocks[(x, y, z)] == LEAVES: - v.set(x, y, z, 'minecraft:spruce_log[axis=y]') - for y in range(2, height + 1): - radius = 2 if y <= height - 4 else 1 if y <= height - 1 else 0 - for dx in range(-radius, radius + 1): - for dz in range(-radius, radius + 1): - if abs(dx) + abs(dz) > radius + 1: - continue - p = x + dx, y, z + dz - if p not in v.blocks or v.blocks[p] == LEAVES: - v.set(*p, LEAVES) - for x, z, h in ((10, 9, 8), (28, 9, 8), (62, 16, 7), (62, 44, 7), - (6, 60, 8), (32, 60, 7)): - narrow_tree(x, z, h) - # Slight wear follows the edges, leaving the main approach calm and readable. - for (x, y, z), block in list(v.blocks.items()): - if y == 0 and block == 'minecraft:smooth_stone' and (x < 6 or x > 59 or z > 57): - if (x * 31 + z * 17) % 29 == 0: - v.set(x, y, z, 'minecraft:andesite') - if y in (1, 2, 3) and block == STONE and (x < 8 or x > 56) and (x * 13 + y * 7 + z * 19) % 31 == 0: - v.set(x, y, z, 'minecraft:mossy_stone_bricks') - - -def build_scene(): - v = finished_scene() - architecture(v) - lighting(v) - landscape(v) - return v diff --git a/scripts/builds/gothic_hall/scene.py b/scripts/builds/gothic_hall/scene.py deleted file mode 100644 index 4324cc3..0000000 --- a/scripts/builds/gothic_hall/scene.py +++ /dev/null @@ -1,157 +0,0 @@ -"""Deterministic voxel blueprint for the generated Gothic hall reference.""" -from collections import Counter, defaultdict -import importlib -import re - -ORIGIN = (-50, -60, -40) - -class Voxels: - def __init__(self): - self.blocks = {} - self.stage = 'site' - self.owners = {} - - def set(self, x, y, z, block): - assert all(isinstance(v, int) for v in (x, y, z)), (x, y, z) - assert -2 <= x <= 68 and -1 <= y <= 64 and -2 <= z <= 68, (x, y, z) - if not block.startswith('minecraft:'): - block = 'minecraft:' + block - assert re.fullmatch(r'minecraft:[a-z0-9_]+(?:\[[a-z0-9_=,]+\])?', block), block - if block == 'minecraft:air': - self.blocks.pop((x, y, z), None) - self.owners.pop((x, y, z), None) - else: - self.blocks[(x, y, z)] = block - self.owners[(x, y, z)] = self.stage - - def box(self, x1, y1, z1, x2, y2, z2, block): - x1, x2 = sorted((x1, x2)) - y1, y2 = sorted((y1, y2)) - z1, z2 = sorted((z1, z2)) - for y in range(y1, y2 + 1): - for z in range(z1, z2 + 1): - for x in range(x1, x2 + 1): - self.set(x, y, z, block) - - def clear(self, x1, y1, z1, x2, y2, z2): - self.box(x1, y1, z1, x2, y2, z2, 'air') - -def site(v): - v.box(1, 0, 1, 63, 0, 62, 'smooth_stone') - for z in (1, 62): - v.box(1, 0, z, 63, 0, z, 'polished_andesite') - for x in (1, 63): - v.box(x, 0, 1, x, 0, 62, 'polished_andesite') - # A few continuous paving bands give the terrace scale without noisy texture. - for x in (5, 33, 60): - v.box(x, 0, 2, x, 0, 61, 'stone_bricks') - for z in (4, 59): - v.box(2, 0, z, 62, 0, z, 'stone_bricks') - for step in range(1, 7): - z = step + 1 - v.box(13, 1, z, 25, step, z, 'stone_bricks') - v.box(14, step, z, 24, step, z, - 'stone_brick_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]') - for x in (12, 26): - v.box(x, 1, z, x, step + 1, z, 'polished_andesite') - v.set(x, step + 2, z, 'stone_brick_slab[type=bottom,waterlogged=false]') - v.box(13, 1, 8, 25, 6, 9, 'stone_bricks') - for x in (8, 30, 35, 61): - for z in (6, 59): - v.box(x, 1, z, x + 1, 1, z + 1, 'polished_andesite') - v.box(x, 2, z, x + 1, 3, z + 1, 'stone_bricks') - v.box(x, 4, z, x + 1, 4, z + 1, - 'stone_brick_slab[type=bottom,waterlogged=false]') - -def connection(v): - # Two-level enclosed passage between the great hall and companion wing. - v.stage = 'connecting gallery' - v.box(31, 0, 34, 40, 6, 42, 'stone_bricks') - v.box(31, 7, 34, 40, 13, 34, 'stone_bricks') - v.box(31, 7, 42, 40, 13, 42, 'stone_bricks') - v.box(31, 13, 33, 40, 13, 43, 'polished_andesite') - v.box(31, 14, 34, 40, 14, 42, 'deepslate_tiles') - v.box(31, 15, 36, 40, 15, 40, 'deepslate_tiles') - v.box(31, 16, 38, 40, 16, 38, 'deepslate_tiles') - for z, facing, y in [(34, 'south', 14), (35, 'south', 14), - (36, 'south', 15), (37, 'south', 15), - (39, 'north', 15), (40, 'north', 15), - (41, 'north', 14), (42, 'north', 14)]: - v.box(31, y, z, 40, y, z, - f'deepslate_tile_stairs[facing={facing},half=bottom,shape=straight,waterlogged=false]') - v.clear(30, 7, 36, 32, 11, 39) - v.clear(33, 8, 36, 34, 11, 39) - v.clear(35, 9, 36, 40, 12, 39) - v.box(31, 6, 36, 32, 6, 39, 'spruce_planks') - for x, y in [(33, 6), (34, 7)]: - v.box(x, y, 36, x, y, 39, - 'spruce_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]') - v.box(35, 8, 36, 40, 8, 39, 'spruce_planks') - -def build_scene(): - v = Voxels() - site(v) - for name, stage in [('main_hall', 'great hall'), ('side_wing', 'side wing'), ('tower', 'bell tower')]: - v.stage = stage - importlib.import_module('builds.gothic_hall.' + name).build(v) - connection(v) - return v - -def boxes(blocks): - """Merge runs on X, then Z and Y, preserving the exact sparse block mask.""" - rows = defaultdict(list) - for (x, y, z), block in blocks.items(): - rows[(y, z, block)].append(x) - runs = [] - for (y, z, block), xs in sorted(rows.items()): - xs.sort(); start = end = xs[0] - for x in xs[1:]: - if x == end + 1: - end = x - else: - runs.append((start, y, z, end, y, z, block)); start = end = x - runs.append((start, y, z, end, y, z, block)) - merged = [] - groups = defaultdict(list) - for x1, y1, z1, x2, y2, z2, block in runs: - groups[(x1, x2, y1, block)].append(z1) - for (x1, x2, y, block), zs in sorted(groups.items()): - zs.sort(); start = end = zs[0] - for z in zs[1:]: - if z == end + 1: end = z - else: - merged.append((x1, y, start, x2, y, end, block)); start = end = z - merged.append((x1, y, start, x2, y, end, block)) - result = []; groups = defaultdict(list) - for x1, y1, z1, x2, y2, z2, block in merged: - groups[(x1, x2, z1, z2, block)].append(y1) - for (x1, x2, z1, z2, block), ys in sorted(groups.items()): - ys.sort(); start = end = ys[0] - for y in ys[1:]: - if y == end + 1: end = y - else: - result.append((x1, start, z1, x2, end, z2, block)); start = end = y - result.append((x1, start, z1, x2, end, z2, block)) - return result - -def manifest(v): - tiles = defaultdict(dict) - for p, block in v.blocks.items(): - tiles[(p[1] // 8, p[2] // 16, p[0] // 16)][p] = block - batches = [] - for key, data in sorted(tiles.items()): - recipe = [] - for x1, y1, z1, x2, y2, z2, block in boxes(data): - lo = dict(zip(('x', 'y', 'z'), (x1 + ORIGIN[0], y1 + ORIGIN[1], z1 + ORIGIN[2]))) - hi = dict(zip(('x', 'y', 'z'), (x2 + ORIGIN[0], y2 + ORIGIN[1], z2 + ORIGIN[2]))) - recipe.append({'type': 'box', 'min': lo, 'max': hi, 'block': block}) - positions = list(data) - minimum = [min(p[i] for p in positions) + ORIGIN[i] for i in range(3)] - maximum = [max(p[i] for p in positions) + ORIGIN[i] for i in range(3)] - batches.append({'key': '-'.join(map(str, key)), 'blocks': len(data), - 'min': dict(zip(('x', 'y', 'z'), minimum)), 'max': dict(zip(('x', 'y', 'z'), maximum)), - 'recipe': {'version': 1, 'operations': recipe}, - 'stages': dict(Counter(v.owners[p] for p in data))}) - return {'name': 'Gothic hall reference reconstruction', 'origin': ORIGIN, - 'blocks': len(v.blocks), 'stages': dict(Counter(v.owners.values())), - 'palette': dict(Counter(v.blocks.values())), 'batches': batches} diff --git a/scripts/builds/gothic_hall/side_wing.py b/scripts/builds/gothic_hall/side_wing.py deleted file mode 100644 index e69ead7..0000000 --- a/scripts/builds/gothic_hall/side_wing.py +++ /dev/null @@ -1,176 +0,0 @@ -"""The smaller, two-storey gabled companion to the main Gothic hall. - -Coordinates are inclusive and local to the complete composition. ``build`` -only writes through the supplied voxel API; it does not contact Minecraft. -""" - -STONE = "minecraft:stone_bricks" -TRIM = "minecraft:polished_andesite" -LIGHT = "minecraft:polished_diorite" -GLASS = "minecraft:tinted_glass" -WOOD = "minecraft:spruce_planks" -BEAM = "minecraft:stripped_spruce_log[axis=y]" -ROOF = "minecraft:deepslate_tiles" -AIR = "minecraft:air" - - -def _stair(material, facing, half="bottom"): - return ( - f"minecraft:{material}[facing={facing},half={half}," - "shape=straight,waterlogged=false]" - ) - - -def _wall_block(x, y, z): - """Sparse, deterministic masonry variation, without a checkerboard.""" - n = (x * 31 + y * 17 + z * 47 + x * z * 3) % 53 - if n < 3: - return "minecraft:cracked_stone_bricks" - if n < 8: - return "minecraft:andesite" - return STONE - - -def _lancet(v, left, right, bottom, top, wall, outside, north=False): - """A stone pointed surround, open reveal, and recessed dark glass.""" - center = (left + right) / 2 - - def point(u, y, depth, block): - if north: - v.set(u, y, depth, block) - else: - v.set(depth, y, u, block) - - for u in range(left - 1, right + 2): - cap = top + 1 - int(abs(u - center)) - for y in range(bottom - 1, cap + 1): - point(u, y, outside, LIGHT if y == cap else TRIM) - for u in range(left, right + 1): - cap = top - int(abs(u - center)) - for y in range(bottom, cap + 1): - point(u, y, outside, AIR) - point(u, y, wall, GLASS) - # The sill projects one block; the glass stays in the wall behind it. - for u in range(left - 1, right + 2): - point(u, bottom - 1, outside, LIGHT) - - -def _buttress(v, west, z): - """Three diminishing stages, with projecting pale weathering caps.""" - outer, inner = (36, 38) if west else (57, 59) - v.box(outer, 0, z - 1, inner, 1, z + 1, TRIM) - a, b = (37, 38) if west else (57, 58) - v.box(a, 2, z - 1, b, 6, z + 1, STONE) - v.box(outer, 7, z - 1, inner, 7, z + 1, LIGHT) - v.box(a, 8, z, b, 14, z, STONE) - v.box(a, 15, z - 1, b, 15, z + 1, TRIM) - v.box(a, 16, z, b, 18, z, STONE) - v.box(a, 19, z, b, 19, z, LIGHT) - - -def _entrance(v): - # Two nested pointed archivolts give the entrance visible depth. - for z, left, right, peak, block in ( - (14, 44, 51, 8, TRIM), - (15, 45, 50, 7, LIGHT), - ): - for x in range(left, right + 1): - cap = peak - int(abs(x - 47.5)) - v.box(x, 1, z, x, cap, z, block) - for x in range(46, 50): - cap = 6 - int(abs(x - 47.5)) - v.clear(x, 1, 14, x, cap, 17) - # Timber inner jambs suggest the tall wooden doorway, leaving it usable. - v.box(45, 1, 16, 45, 4, 16, BEAM) - v.box(50, 1, 16, 50, 4, 16, BEAM) - v.box(44, 0, 13, 51, 0, 16, TRIM) - v.box(46, 0, 14, 49, 0, 17, "minecraft:smooth_stone") - - -def build(v): - """Build within x=36..59, y=0..28, z=13..43.""" - # Hollow masonry shell, an accessible ground floor and an upper floor. - v.box(38, 0, 16, 57, 0, 41, TRIM) - v.clear(39, 1, 17, 56, 27, 40) - for y in range(1, 18): - for x in range(38, 58): - for z in (16, 41): - v.set(x, y, z, _wall_block(x, y, z)) - for z in range(17, 41): - for x in (38, 57): - v.set(x, y, z, _wall_block(x, y, z)) - v.box(39, 0, 17, 56, 0, 40, WOOD) - v.box(39, 8, 17, 56, 8, 40, WOOD) - - # Floor bands and the projecting eaves visually join the two storeys. - for y, block in ((0, TRIM), (7, TRIM), (8, LIGHT), (16, TRIM)): - v.box(37, y, 15, 58, y, 15, block) - v.box(37, y, 42, 58, y, 42, block) - v.box(58, y, 16, 58, y, 41, block) - for z in range(16, 42): - # The main composition will connect the gallery through here. - if not (7 <= y <= 11 and 36 <= z <= 39): - v.set(37, y, z, block) - - for z in (17, 25, 33, 41): - _buttress(v, True, z) - _buttress(v, False, z) - for x in (38, 57): - v.box(x - 1, 0, 14, x + 1, 1, 16, TRIM) - v.box(x, 2, 14, x, 16, 16, STONE) - v.box(x - 1, 17, 14, x + 1, 17, 16, LIGHT) - v.box(x, 18, 15, x, 20, 15, STONE) - v.set(x, 21, 15, LIGHT) - - # A paired rhythm of small lower and tall upper side windows. - for z in (21, 29, 37): - for wall, outside in ((38, 37), (57, 58)): - if wall == 38 and z == 37: - continue # Keep the gallery doorway and its approach clear. - _lancet(v, z - 1, z + 1, 2, 6, wall, outside) - _lancet(v, z - 1, z + 1, 10, 15, wall, outside) - for left, right in ((41, 42), (53, 54)): - _lancet(v, left, right, 2, 6, 16, 15, north=True) - for left, right in ((42, 45), (50, 53)): - _lancet(v, left, right, 10, 15, 16, 15, north=True) - _entrance(v) - - # Timber supports remain against the edges, leaving the rooms traversable. - for x in (40, 55): - for z in (18, 39): - v.box(x, 1, z, x, 7, z, BEAM) - for z in (23, 31, 39): - v.box(39, 7, z, 56, 7, z, "minecraft:stripped_spruce_log[axis=x]") - v.clear(53, 8, 28, 55, 11, 35) - for step in range(8): - y, z = step + 1, 28 + step - if y > 1: - v.box(53, 1, z, 55, y - 1, z, WOOD) - v.box(53, y, z, 55, y, z, _stair("spruce_stairs", "south")) - v.clear(53, y + 1, z, 55, y + 2, z) - v.box(52, 9, 28, 52, 9, 35, "minecraft:spruce_slab[type=bottom,waterlogged=false]") - - # A steep roof, dark in the middle and bounded by pale stepped gables. - for x in range(37, 59): - rise = min(x - 37, 58 - x) - y = 17 + rise - facing = "east" if x <= 47 else "west" - if 38 <= x <= 57 and y >= 18: - for z in (16, 41): - v.box(x, 18, z, x, y, z, STONE) - v.box(x, y, 15, x, y, 42, _stair("deepslate_tile_stairs", facing)) - for z in (14, 43): - v.set(x, y, z, _stair("stone_brick_stairs", facing)) - if y > 17: - v.set(x, y - 1, z, TRIM) - v.box(47, 28, 15, 48, 28, 42, ROOF) - for z in (14, 43): - v.box(47, 27, z, 48, 28, z, LIGHT) - _lancet(v, 46, 49, 19, 24, 16, 15, north=True) - for x in (47, 48): - v.box(x, 26, 15, x, 28, 15, TRIM) - - # Wooden cornice brackets are visible under the long roof slopes. - for z in (19, 23, 27, 31, 35, 39): - for x, facing in ((37, "east"), (58, "west")): - v.set(x, 16, z, _stair("spruce_stairs", facing, half="top")) diff --git a/scripts/builds/gothic_hall/tower.py b/scripts/builds/gothic_hall/tower.py deleted file mode 100644 index f5b6e7b..0000000 --- a/scripts/builds/gothic_hall/tower.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Reference bell tower, in the shared scene's inclusive local coordinates. - -The scene supplies set(x, y, z, block), box(x1, y1, z1, x2, y2, z2, -block), and clear(x1, y1, z1, x2, y2, z2). This module has no I/O. -All geometry stays inside x=34..46, y=0..60, z=42..56. -""" - - -STONE = "minecraft:stone_bricks" -TRIM = "minecraft:polished_andesite" -LIGHT = "minecraft:polished_diorite" -DARK = "minecraft:deepslate_tiles" -GLASS = "minecraft:tinted_glass" -AIR = "minecraft:air" - - -def _face(face, u, y, depth=0): - """Positive depth projects outward; -1 is the recessed glazing plane.""" - if face == "north": - return u, y, 43 - depth - if face == "south": - return u, y, 55 + depth - if face == "west": - return 35 - depth, y, u - return 45 + depth, y, u - - -def _put(v, face, u, y, block, depth=0): - v.set(*_face(face, u, y, depth), block) - - -def _ring(v, x1, z1, x2, z2, y, block): - v.box(x1, y, z1, x2, y, z1, block) - v.box(x1, y, z2, x2, y, z2, block) - v.box(x1, y, z1, x1, y, z2, block) - v.box(x2, y, z1, x2, y, z2, block) - - -def _lancet(v, face, center, bottom, top, half_width): - """A pointed opening through a two-block wall, glass one block behind it.""" - for y in range(bottom, top + 1): - radius = 0 if y >= top - 1 else half_width - for u in range(center - radius, center + radius + 1): - _put(v, face, u, y, AIR) - _put(v, face, u, y, GLASS, -1) - # The projecting dressings are stone; the glazing never projects. - for u in {center - radius - 1, center + radius + 1}: - _put(v, face, u, y, TRIM, 1) - for u in range(center - half_width - 1, center + half_width + 2): - _put(v, face, u, bottom - 1, "minecraft:stone_brick_slab[type=top]", 1) - _put(v, face, center, top + 1, LIGHT, 1) - - -def _belfry_arch(v, face, center): - # Seven blocks wide below, narrowing to a high, single-block apex. - for y in range(32, 41): - radius = 3 if y <= 37 else 40 - y - for u in range(center - radius, center + radius + 1): - for depth in (0, -1): - _put(v, face, u, y, AIR, depth) - for u in (center - radius - 1, center + radius + 1): - _put(v, face, u, y, TRIM, 1) - _put(v, face, center, 41, LIGHT, 1) - for u in range(center - 4, center + 5): - _put(v, face, u, 31, "minecraft:stone_brick_slab[type=top]", 1) - - -def _stairs(v): - # Three narrow alternating flights join the interior floors. A landing at - # each end leaves a three-block-wide central well open through the tower. - for y_floor, xs, reverse in ((1, (37, 38), False), - (10, (42, 43), True), - (19, (37, 38), False)): - facing = "north" if reverse else "south" - state = f"minecraft:stone_brick_stairs[facing={facing},half=bottom,shape=straight]" - for i in range(9): - z = 53 - i if reverse else 45 + i - y = y_floor + i + 1 - for x in xs: - # Clearance through the destination floor, including headroom. - v.clear(x, y + 1, z, x, y + 3, z) - v.set(x, y, z, state) - # The flight exits sideways onto the destination floor. - v.clear(39, 29, 53, 41, 32, 53) - v.set(41, 29, 52, "minecraft:stone_brick_stairs[facing=north]") - v.set(41, 30, 51, "minecraft:stone_brick_stairs[facing=north]") - v.clear(41, 30, 52, 41, 33, 52) - v.clear(41, 31, 51, 41, 33, 51) - - -def _spire(v): - # A hollow, steep rectangular hip roof; alternate contractions avoid a - # stack of disconnected floating rings while giving the spire a fine tip. - for y in range(45, 57): - step = y - 45 - rx = 5 - step // 2 - rz = 6 - (step + 1) // 2 - if rx < 0: - rx = 0 - if rz < 0: - rz = 0 - x1, x2, z1, z2 = 40 - rx, 40 + rx, 49 - rz, 49 + rz - v.box(x1, y, z1, x2, y, z2, DARK) - if rx > 0 and rz > 0: - v.clear(x1 + 1, y, z1 + 1, x2 - 1, y, z2 - 1) - # Stair edges soften the one-block roof steps without adding width. - if rx > 0 and rz > 0: - for x in range(x1 + 1, x2): - v.set(x, y, z1, "minecraft:deepslate_tile_stairs[facing=south]") - v.set(x, y, z2, "minecraft:deepslate_tile_stairs[facing=north]") - for z in range(z1 + 1, z2): - v.set(x1, y, z, "minecraft:deepslate_tile_stairs[facing=east]") - v.set(x2, y, z, "minecraft:deepslate_tile_stairs[facing=west]") - # The cross is plain masonry, with no unsupported entities or thin blocks. - v.box(40, 57, 49, 40, 60, 49, TRIM) - v.box(39, 59, 49, 41, 59, 49, TRIM) - - -def build(v): - """Add a hollow Gothic bell tower and its pointed roof to a scene.""" - # Foundation and shell: two-block walls give the apertures real reveals. - v.box(35, 0, 43, 45, 43, 55, STONE) - v.clear(37, 2, 45, 43, 42, 53) - _ring(v, 34, 42, 46, 56, 0, "minecraft:deepslate_bricks") - _ring(v, 34, 42, 46, 56, 1, TRIM) - _ring(v, 34, 42, 46, 56, 3, "minecraft:stone_brick_slab[type=bottom]") - - # Dressed corners and stepped buttress feet, all within the tower envelope. - corners = ((34, 42, 35, 43), (45, 42, 46, 43), - (34, 55, 35, 56), (45, 55, 46, 56)) - for x1, z1, x2, z2 in corners: - v.box(x1, 1, z1, x2, 5, z2, TRIM) - v.box(x1, 6, z1, x2, 29, z2, STONE) - for y in (6, 15, 28): - v.box(x1, y, z1, x2, y, z2, TRIM) - for x, z in ((35, 43), (45, 43), (35, 55), (45, 55)): - v.box(x, 4, z, x, 43, z, TRIM) - - # Strong horizontal courses separate the shaft from the open belfry. - for y in (15, 16, 29, 30, 42, 43): - _ring(v, 34, 42, 46, 56, y, TRIM if y % 2 else STONE) - for y in (10, 19, 28, 30): - v.box(37, y, 45, 43, y, 53, "minecraft:oak_planks") - if y != 30: - v.clear(39, y, 47, 41, y, 51) - v.box(35, 44, 43, 45, 44, 55, STONE) - _ring(v, 34, 42, 46, 56, 44, "minecraft:stone_brick_slab[type=bottom]") - - # Paired narrow lower lights and broader, pointed upper lancets. - for face, centers in (("north", (38, 42)), ("south", (38, 42)), - ("west", (47, 51)), ("east", (47, 51))): - for center in centers: - _lancet(v, face, center, 6, 12, 0) - _lancet(v, face, center, 19, 26, 1) - _belfry_arch(v, face, 40 if face in ("north", "south") else 49) - - # An actual walk-in doorway toward the wing; no decorative solid door. - v.clear(39, 2, 42, 41, 5, 44) - v.clear(40, 6, 42, 40, 6, 44) - for x in (38, 42): - v.box(x, 2, 42, x, 5, 42, TRIM) - v.set(39, 6, 42, TRIM) - v.set(41, 6, 42, TRIM) - v.set(40, 7, 42, LIGHT) - _stairs(v) - - # A block-built ochre bell echoes the reference using allowlisted sandstone. - # Its open mouth and central clapper remain visible through all four arches. - v.box(35, 41, 49, 45, 41, 49, "minecraft:spruce_log[axis=x]") - v.box(40, 38, 49, 40, 40, 49, "minecraft:dark_oak_log[axis=y]") - v.box(40, 36, 49, 40, 37, 49, "minecraft:cut_sandstone") - for x, z in ((39, 49), (40, 48), (40, 49), (40, 50), (41, 49)): - v.set(x, 35, z, "minecraft:cut_sandstone") - _ring(v, 39, 48, 41, 50, 34, "minecraft:cut_sandstone") - v.set(40, 34, 49, "minecraft:dark_oak_log[axis=y]") - v.set(40, 33, 49, TRIM) - - # A row of small corbels under the cornice is readable from a distance. - for face, us in (("north", (37, 39, 41, 43)), ("south", (37, 39, 41, 43)), - ("west", (45, 47, 49, 51, 53)), ("east", (45, 47, 49, 51, 53))): - for u in us: - _put(v, face, u, 41, f"minecraft:stone_brick_stairs[facing={face},half=top]", 1) - _spire(v) - - # Four light corner pinnacles frame the darker central spire. - for x, z in ((35, 43), (45, 43), (35, 55), (45, 55)): - v.box(x, 44, z, x, 49, z, TRIM) - for dx, dz in ((-1, 0), (1, 0), (0, -1), (0, 1)): - v.set(x + dx, 46, z + dz, "minecraft:stone_brick_slab[type=top]") - v.set(x, 50, z, "minecraft:stone_brick_slab[type=bottom]") diff --git a/scripts/camera-capture.py b/scripts/camera-capture.py new file mode 100644 index 0000000..3edb2d0 --- /dev/null +++ b/scripts/camera-capture.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Capture an already positioned spectator through the authenticated local worker. + +The server/operator must teleport the observer first. This helper cannot teleport +or edit a world and does not extend the MCP project's authorized world boundary. +Useful for operator-side checks of separate initial-generation worlds. The normal +MCP camera_capture tool continues to use Paper's project-scoped route. +""" +import argparse +import base64 +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import re +import time +import urllib.request + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME = Path(os.environ.get('MCB_RUNTIME_DIR') or ROOT / '.runtime').expanduser().resolve() +# Share bounded transport/PNG checks; importing this module does not run its test. +spec = importlib.util.spec_from_file_location('camera_checks', ROOT / 'scripts/live-camera-test.py') +checks = importlib.util.module_from_spec(spec) +spec.loader.exec_module(checks) + + +def validate_request(pose): + """Validate locally before reading credentials or sending a capture request.""" + values = [pose.get(key) for key in ('x', 'y', 'z', 'yaw', 'pitch')] + if any(type(value) not in (int, float) for value in values): + raise checks.TestFailure('Pose values must all be numbers.') + checks.validate_pose(values) + dimension = pose.get('dimension') + if not isinstance(dimension, str) or len(dimension) > 128 or not re.fullmatch(r'[a-z0-9_.-]+:[a-z0-9_./-]+', dimension): + raise checks.TestFailure('Dimension must be a namespaced Minecraft dimension key.') + for key, fallback, low, high in (('fov', 70, 30, 110), ('width', 1280, 320, 1920), ('height', 720, 180, 1080)): + value = pose.get(key, fallback) + if type(value) is not int or not low <= value <= high: + raise checks.TestFailure(f'{key} must be an integer in {low}..{high}.') + + +def validated_image(result, capture_id, pose, token, started): + if result.get('captureId') != capture_id or result.get('mimeType') != 'image/png': + raise checks.TestFailure('Completed capture identity or MIME type did not match.') + if result.get('dimension') != pose['dimension']: + raise checks.TestFailure('Completed capture came from a different dimension.') + captured_at = checks.instant(result.get('capturedAt'), 'Capture timestamp') + if captured_at < started - 1 or captured_at > time.time() + 5: + raise checks.TestFailure('The frame timestamp does not correspond to this capture request.') + encoded = result.get('imageBase64') + if not isinstance(encoded, str) or len(encoded) > 12_000_000: + raise checks.TestFailure('Completed capture did not include a bounded image payload.') + try: + raw = base64.b64decode(encoded, validate=True) + except ValueError: + raise checks.TestFailure('Completed capture contained invalid image encoding.') from None + width, height = checks.inspect_png(raw) + if result.get('width') != width or result.get('height') != height: + raise checks.TestFailure('PNG dimensions disagree with the capture metadata.') + metadata = {key: value for key, value in result.items() if key in checks.SAFE_METADATA} + if any(not isinstance(value, (str, int, float, bool, type(None))) or isinstance(value, str) and len(value) > 1024 + for value in metadata.values()): + raise checks.TestFailure('Capture metadata contains unexpected values.') + metadata.update(operator_direct_worker=True, imageSha256=hashlib.sha256(raw).hexdigest(), imageBytes=len(raw)) + try: + receipt = json.dumps(metadata, indent=2, allow_nan=False) + '\n' + except ValueError: + raise checks.TestFailure('Capture metadata contains nonfinite values.') from None + if token in receipt: + raise checks.TestFailure('Refusing to save metadata containing a component secret.') + return raw, receipt + + +def capture(config, pose, output): + validate_request(pose) + try: + source = Path(config).expanduser().read_text() + token = checks.scalar(source, 'camera-token') + port = int(checks.scalar(source, 'camera-port')) + except (OSError, UnicodeError, ValueError): + raise checks.TestFailure('Cannot read a valid private camera configuration.') from None + if not 1024 <= port <= 65535 or not re.fullmatch(r'[A-Za-z0-9._~-]{32,512}', token): + raise checks.TestFailure('Invalid private camera configuration.') + opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), checks.NoRedirect()) + endpoint = f'http://127.0.0.1:{port}' + started, deadline = time.time(), time.monotonic() + 30 + result = checks.request_json(opener, endpoint + '/v1/capture', token, pose, timeout=25) + capture_id = checks.identifier(result.get('captureId'), 'Capture ID') + while result.get('status') in ('pending', 'queued', 'capturing'): + remaining = deadline - time.monotonic() + if remaining <= 0: + raise checks.TestFailure('Camera did not complete in time.') + time.sleep(min(.3, remaining)) + result = checks.request_json(opener, endpoint + '/v1/captures/' + capture_id, token, timeout=min(10, remaining)) + if result.get('captureId') != capture_id: + raise checks.TestFailure('Camera returned a different capture identity.') + if result.get('status') != 'completed': + code = result.get('error', '') + safe_code = code if isinstance(code, str) and re.fullmatch(r'[a-z_]{1,64}', code) else 'capture_failed' + raise checks.TestFailure('Camera unavailable: ' + safe_code) + raw, receipt = validated_image(result, capture_id, pose, token, started) + output = Path(output).expanduser() + output.parent.mkdir(parents=True, exist_ok=True) + output.write_bytes(raw) + output.with_suffix('.capture.json').write_text(receipt) + print(json.dumps({'path': str(output.resolve()), 'dimension': result.get('dimension'), 'status': 'completed'})) + + +if __name__ == '__main__': + p = argparse.ArgumentParser(description=__doc__) + p.add_argument('--config', type=Path, default=Path(os.environ.get('MCB_CAMERA_PAPER_CONFIG') or RUNTIME / 'server/plugins/MinecraftBuilderMCP/config.yml')) + p.add_argument('--pose', type=float, nargs=5, required=True, metavar=('X', 'Y', 'Z', 'YAW', 'PITCH')) + p.add_argument('--dimension', required=True) + p.add_argument('--fov', type=int, default=80) + p.add_argument('--out', type=Path, required=True) + a = p.parse_args() + try: + capture(a.config, dict(zip(('x', 'y', 'z', 'yaw', 'pitch'), a.pose)) | {'dimension': a.dimension, 'fov': a.fov, 'width': 1280, 'height': 720}, a.out) + except checks.TestFailure as error: + raise SystemExit(str(error)) from None diff --git a/scripts/camera-wrapper.py b/scripts/camera-wrapper.py index 6ec5ccb..878525d 100644 --- a/scripts/camera-wrapper.py +++ b/scripts/camera-wrapper.py @@ -6,11 +6,12 @@ import re import sys ROOT = Path(__file__).resolve().parents[1] +RUNTIME = Path(os.environ.get('MCB_RUNTIME_DIR') or ROOT / '.runtime').expanduser().resolve() def main(): if len(sys.argv) < 2: raise SystemExit('This helper is a Prism WrapperCommand; it requires the Java command.') - config = Path(os.environ.get('MCB_CAMERA_PAPER_CONFIG', str(ROOT / '.runtime/server/plugins/MinecraftBuilderMCP/config.yml'))) + config = Path(os.environ.get('MCB_CAMERA_PAPER_CONFIG') or RUNTIME / 'server/plugins/MinecraftBuilderMCP/config.yml').expanduser() try: data = config.read_text() token_match = re.search(r'^camera-token:[ \t]*([^\r\n]+)$', data, re.M) diff --git a/scripts/compile-layout-access.py b/scripts/compile-layout-access.py deleted file mode 100644 index a6fa515..0000000 --- a/scripts/compile-layout-access.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -"""Compile audited access refinements against the original survey plus placed markers.""" -import argparse -import importlib.util -import json -from pathlib import Path - -ROOT=Path(__file__).resolve().parents[1] -spec=importlib.util.spec_from_file_location('mark',ROOT/'scripts/mark-shacraft-layout.py') -mark=importlib.util.module_from_spec(spec);spec.loader.exec_module(mark) - -def main(): - p=argparse.ArgumentParser();p.add_argument('--before',type=Path,required=True) - p.add_argument('--base',type=Path,required=True);p.add_argument('--study',type=Path,required=True) - p.add_argument('--access',type=Path,required=True);p.add_argument('--output',type=Path,required=True) - a=p.parse_args();before=json.loads(a.before.read_text());base=json.loads(a.base.read_text()) - study=json.loads(a.study.read_text());access=json.loads(a.access.read_text());desired={} - old={(b['x'],b['y'],b['z']):b['block'] for b in base['blocks']} - def ground(x,z): - i=(z-before['min_z'])*before['width']+x-before['min_x'] - return before['surface_y'][i],before['palette'][before['material_index'][i]] - def put(x,z,block,group,y=None): - x,z=round(x),round(z);h,material=ground(x,z);y=max(h,51) if y is None else y - if y= 96 and (old != AIR or (x, z) in roads): - raise ValueError(f'Furniture touches existing decoration or an approach at {at}') - if y == 95 and not full(old): - raise ValueError(f'Inlay would change a stair or unsupported floor at {at}') - details[at] = 'minecraft:'+state - groups[at] = group - - urns = [(-10, 35), (10, 35)] - for cx, cz in urns: - decorate(cx, 96, cz, 'cut_sandstone', 'urn-pedestals') - for dx, dz in N: - decorate(cx+dx, 96, cz+dz, 'smooth_sandstone_slab[type=bottom,waterlogged=false]', 'urn-plinths') - for dx in range(-1, 2): - for dz in range(-1, 2): - if dx == dz == 0: - bowl = 'dirt' - elif dx and dz: - bowl = 'cut_sandstone' - else: - facing = 'east' if dx == 1 else 'west' if dx == -1 else 'south' if dz == 1 else 'north' - bowl = f'smooth_sandstone_stairs[facing={facing},half=bottom,shape=straight,waterlogged=false]' - decorate(cx+dx, 97, cz+dz, bowl, 'urn-bowls') - decorate(cx, 98, cz, 'white_tulip', 'urn-flowers') - # A low freestanding enamel nameboard; its text display is a separate receipt. - decorate(-8, 96, 43, 'cut_sandstone', 'welcome-pedestal') - for x in range(-9, -6): - decorate(x, 97, 43, 'green_concrete', 'welcome-nameboard') - decorate(x, 98, 43, 'waxed_oxidized_cut_copper_slab[type=bottom,waterlogged=false]', 'welcome-coping') - thresholds = { - 'north': [(x, z) for x in range(-4, 5) for z in (-31, -30)], - 'south': [(x, z) for x in range(-4, 5) for z in (50, 51)], - 'east': [(x, z) for x in (38, 39) for z in range(13, 20)], - 'west': [(x, z) for x in (-40, -39) for z in range(16, 21)], - 'northwest': [(x, z) for x in range(-36, -31) for z in range(-16, -9) - if (x, z) in roads and x-z in (-21, -20)], - } - for name, columns in thresholds.items(): - for x, z in columns: - decorate(x, 95, z, 'smooth_stone', 'flush-threshold-'+name) - # Restrained green/brass corner accents stay at the exact paving height. - for x, z in (min(columns), max(columns)): - decorate(x, 95, z, 'waxed_oxidized_cut_copper', 'threshold-copper-insets') - - # Solid joint piers stop a partial-shaped road rail from carrying a folded - # membrane through a whole garden-side balustrade and its connected plants. - for x, z in ((-42, 22), (41, 11), (42, 20)): - at = x, 96, z - if not before.state(*at).startswith('minecraft:stone_brick_wall['): - raise ValueError(f'Joint pier baseline changed at {at}') - details[at], groups[at] = 'minecraft:cut_sandstone', 'finished-road-joint-piers' - - # Allow the complete plaza, its balustrade and short level approach aprons. - near = {(x+dx, z+dz) for x, z in outer for dx in range(-2, 3) for dz in range(-2, 3)} - interior = outer | {tuple(p) for p in rails['fence_columns']} - interior |= {(c['x'], c['z']) for c in geo['cells'] if c['kind'] == 'full' and c['block_y'] == 95 - and (c['x'], c['z']) in near} - # Fold the six-face membrane inward around partial collision shapes instead - # of replacing visible rails, stair treads, plants or fixture overhangs. - n6 = ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)) - original_volume = {(x, y, z) for x, z in interior for y in range(96, 116)} - volume = set(original_volume) - cache = {} - def observed(p): - if p not in cache: - cache[p] = details.get(p, before.state(*p)) - return cache[p] - for _ in range(128): - membrane = {(x+dx, y+dy, z+dz) for x, y, z in volume for dx, dy, dz in n6} - volume - partial = {p for p in membrane if observed(p) != AIR and not full(observed(p))} - if not partial: - break - remove = {(x+dx, y+dy, z+dz) for x, y, z in partial for dx, dy, dz in n6} & volume - if not remove: - raise ValueError('Partial-shape boundary cannot be sealed without editing decoration') - volume -= remove - else: - raise ValueError('Boundary folding did not converge') - if (0, 96, 43) not in volume: - raise ValueError('Spawn is outside the finished enclosure') - shell = {(x, z) for x, y, z in membrane if 96 <= y < 116} - floor = {p for p in membrane if p[1] == 95} - roof = {p for p in membrane if p[1] == 116} - walls = membrane - floor - roof - barriers, barrier_groups = {}, {} - for group, positions in [('invisible-side-wall', walls), ('invisible-floor-gap', floor), ('invisible-roof', roof)]: - for at in sorted(positions): - state = details.get(at, before.state(*at)) - if full(state): - continue - if state != AIR: - raise ValueError(f'Enclosure would erase a visible/partial block at {at}: {state}') - barriers[at], barrier_groups[at] = BARRIER, group - if details.keys() & barriers.keys(): - raise ValueError('Decoration and containment recipes overlap') - def recipe(states, labels): - return {'version': 1, 'scope': before.scope, 'blocks': [dict(zip(('x', 'y', 'z'), p)) | - {'block': state, 'expected': before.state(*p), 'group': labels[p]} - for p, state in sorted(states.items()) if state != before.state(*p)]} - finish, envelope = recipe(details, groups), recipe(barriers, barrier_groups) - combined = {'version': 1, 'scope': before.scope, 'blocks': finish['blocks']+envelope['blocks']} - metadata = {'version': 1, 'scope': before.scope, 'interior_columns': sorted(interior), - 'shell_columns': sorted(shell), 'floor_y': 95, 'roof_y': 116, - 'source': {'x': .5, 'y': 96, 'z': 43.5}, - 'interior_excluded_voxels': sorted(original_volume-volume), - 'decoration': {'urn_centers': urns, 'welcome_plinth': [-8, 43], 'thresholds': thresholds}, - 'barrier_groups': dict(Counter(b['group'] for b in envelope['blocks'])), - 'detail_groups': dict(Counter(b['group'] for b in finish['blocks'])), - 'scope_note': 'Static collision enclosure for normal players; spectator and operator teleport/break commands bypass it.'} - return finish, envelope, combined, metadata - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--before', type=Path, default=STAGE / 'before.json.gz') - parser.add_argument('--output-dir', type=Path, default=STAGE) - args = parser.parse_args() - result = compile_plans(survey.load_snapshot(args.before)) - args.output_dir.mkdir(parents=True, exist_ok=True) - for name, value in zip(('finishing.json', 'barriers.json', 'combined.json', 'zone-boundary.metadata.json'), result): - (args.output_dir/name).write_text(json.dumps(value, separators=(',', ':'))+'\n') - print(json.dumps({'details': len(result[0]['blocks']), 'barriers': len(result[1]['blocks']), - 'interior_columns': len(result[3]['interior_columns']), 'shell_columns': len(result[3]['shell_columns']), - 'groups': result[3]['barrier_groups']})) - - -if __name__ == '__main__': - main() diff --git a/scripts/foundation-study/design.py b/scripts/foundation-study/design.py deleted file mode 100644 index 9506973..0000000 --- a/scripts/foundation-study/design.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only elevation study for the first two Shacraft construction districts. - -Writes a reviewable design specification and sections. Does not issue world edits. -Run with .runtime/terrain-study/venv/bin/python scripts/foundation-study/design.py. -Y convention: floor_y is the block coordinate; full-block walking height is Y + 1. -""" -import json -from pathlib import Path -import numpy as np -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -from matplotlib.path import Path as Polygon -from matplotlib.colors import LightSource - -ROOT=Path(__file__).resolve().parents[2] -OUT=ROOT/'.runtime/foundation-study/design' -OUT.mkdir(parents=True,exist_ok=True) -layout=json.loads((ROOT/'examples/layout/shacraft-lobby-layout.json').read_text()) -actual=json.loads((ROOT/'.runtime/server/plugins/ShacraftTerrain/maps/layout-final.json').read_text()) -natural=np.floor(np.fromfile(ROOT/'.runtime/terrain-study/natural.f32',dtype='>f4').reshape(768,768)).astype(int) -live_heights=np.asarray(actual['surface_y']).reshape(768,768) -live_materials=np.asarray(actual['material_index']).reshape(768,768) -zz,xx=np.mgrid[-384:384,-384:384] -points=np.column_stack([xx.ravel(),zz.ravel()]) - -def feature(fid): return next(f for f in layout['features'] if f['id']==fid) -def route(fid): return next(f for f in layout['routes'] if f['id']==fid) -def polygon_mask(vertices):return Polygon(vertices).contains_points(points,radius=.1).reshape(natural.shape) -def metrics(mask,y): - h=natural[mask] - deltas,counts=np.unique(live_heights[mask]-h,return_counts=True) - actual_delta_counts={str(int(k)):int(v) for k,v in zip(deltas,counts)} - return {'columns':int(mask.sum()),'live_surface_delta_from_natural_counts':actual_delta_counts,'natural_min_y':int(h.min()),'natural_max_y':int(h.max()),'cut_above_floor_blocks':int(np.maximum(h-y,0).sum()),'fill_above_natural_blocks':int(np.maximum(y-h,0).sum()),'maximum_cut':int(max(0,(h-y).max())),'maximum_fill':int(max(0,(y-h).max()))} - -surfaces=[] -for fid,y in [('arrival-hex',95),('station-forecourt',95),('clock-station',98)]: - f=feature(fid); mask=polygon_mask(f['points']) - surfaces.append({'id':fid,'district':f['district'],'geometry':'polygon','points':f['points'],'floor_y':y,'walk_y':y+1,'support':'Solid to existing ground inside the exact footprint; two-deep structural deck in cut areas. Exterior dark stone masonry, light stone coping, no broad earth platform.','metrics':metrics(mask,y)}) - -# Each schedule is expressed in the increasing travel coordinate d, irrespective of -# whether the selected world axis increases or decreases along travel. A stair -# replaces the current high-level full block; its back points toward the higher -# previous row. The next row is one full block lower. This guarantees 0.5 steps. -def descent(fid,axis,sign,start,end,y,stairs,final_y,width,waypoints): - rows=[]; level=y - for a in range(start*sign,end*sign+1): - c=a*sign - is_stair=c in stairs - facing=('north' if axis=='z' else ('west' if sign==1 else 'east')) - row={'coordinate':c,'block_y':level,'kind':'stairs' if is_stair else 'full','walk_high_y':level+1,'walk_low_y':level+.5 if is_stair else level+1} - if is_stair:row['facing']=facing - rows.append(row) - if is_stair:level-=1 - assert level==final_y,(fid,level,final_y) - # Walking from higher to lower: flat->stair upper edge is same elevation; - # stair upper->lower edge is 0.5, lower->next flat is another 0.5. - for a,b in zip(rows,rows[1:]):assert abs(a['walk_low_y']-b['walk_high_y'])<=.5 - return {'id':fid,'geometry':'road_profile','axis':axis,'travel_sign':sign,'width':width,'width_note':'Clear paving width, with one additional coping block outside either edge; use the existing marked centerline tangent.','waypoints':waypoints,'start_floor_y':y,'end_floor_y':final_y,'rows':rows,'max_walk_step':.5,'scope':'Only this local part is built in stage 01; preserve all later district markers.'} - -roads=[ - {'id':'station-axis','geometry':'road_flat','width':9,'floor_y':95,'walk_y':96,'waypoints':route('station-axis')['waypoints'],'profile_until_z':-77,'intent':'Continuous flat link into the forecourt; ornamental edge strips outside nine clear blocks.'}, - descent('south-axis-local','z',1,57,110,95,[58,61,64,67,70,73,76,79,82,86,90,94,98,101,104,107],79,9,[[0,57],[3,97],[1,110]]), - descent('portal-radial-local','x',-1,-35,-72,95,[-42,-47,-52,-58,-64,-71],89,7,[[-35,-14],[-67,-43],[-72,-46]]), - descent('lake-radial-local','x',-1,-42,-90,95,[-44,-47,-50,-55,-64,-72,-78,-83,-85,-87,-89,-90],83,5,[[-42,18],[-77,27],[-90,32]]), - descent('east-radial-local','x',1,42,82,95,[43,47,51,55,60,66,72,78],87,7,[[42,16],[65,10],[82,8]]), - descent('ring-northwest-local','x',-1,-40,-80,95,[-42,-45,-49,-53,-57,-61,-65,-69,-73,-77],85,7,[[-40,-65],[-77,-76],[-80,-77]]), - descent('ring-northeast-local','x',1,40,82,98,[41,44,47,50,54,58,62,66,70,74,78,81],86,7,[[40,-95],[60,-91],[82,-78]]) -] -# Endpoint toes use actual captured surface elevations. At the lake the last -# in-bounds column is a stair: a full block here would leave a full-block drop to -# the next native row, and editing that row would exceed the stage's X=-90 bound. -endpoint_toes=[] -for rid,end in [('portal-radial-local',(-72,-46)),('lake-radial-local',(-90,32))]: - r=next(item for item in roads if item['id']==rid) - last=r['rows'][-1] - native=[] - for z in range(end[1]-1,end[1]+2): - x=end[0]-1 - native.append({'x':x,'z':z,'natural_block_y':int(natural[z+384,x+384]),'live_surface_y':int(live_heights[z+384,x+384]),'step_from_road':abs(last['walk_low_y']-(int(live_heights[z+384,x+384])+1))}) - assert all(n['step_from_road']<=.5 for n in native),(rid,native) - r['endpoint_kind']=last['kind'] - r['endpoint_block_y']=last['block_y'] - r['endpoint_walk_low_y']=last['walk_low_y'] - r['native_toe']=native - endpoint_toes.append({'id':rid,'last_road_column':list(end),'last_road_kind':last['kind'],'last_road_block_y':last['block_y'],'native_front_three':native}) -stairs=[ - {'z':-77,'block_y':95,'kind':'full'}, - {'z':-78,'block_y':96,'kind':'stairs','facing':'north'}, - {'z':-79,'block_y':96,'kind':'full'}, - {'z':-80,'block_y':97,'kind':'stairs','facing':'north'}, - {'z':-81,'block_y':97,'kind':'full'}, - {'z':-82,'block_y':98,'kind':'stairs','facing':'north'}, - {'z':-83,'block_y':98,'kind':'full'}] -plan={ - 'schema':'shacraft-foundation-elevation-design-v1', - 'status':'DESIGN ONLY; root builder must compare actual voxels before applying and verify final live world.', - 'world':actual['world'],'source_capture_finished_at':actual['capture_finished_at'], - 'y_convention':'floor_y/block_y is the Minecraft block coordinate. A full block at Y95 is walked on at Y96. A bottom stair at Y96 spans feet heights96.5..97.', - 'selected_districts':['01','02'], - 'surfaces':surfaces,'roads':roads,'verified_native_endpoint_toes':endpoint_toes, - 'station_entrance_stair':{'x_min':-16,'x_max':4,'width':21,'travel_direction':'north','rows':stairs,'max_walk_step':.5}, - 'station_ne_connection':{'geometry':'polygon','points':[[35,-103],[42,-103],[44,-95],[42,-91],[35,-94],[35,-103]],'floor_y':98,'intent':'Small local upper landing joins east wing to the northeast descending road. Keep inside this polygon; no platform around the whole station.'}, - 'plaza_inlay':{'center':[0,9],'outer_radius':16,'floor_y':95,'intent':'Flat green hexagonal ring with cream Shacraft S inlay. Reserve the core for later fountain/arrival feature if desired; never raise the inlay above paving.'}, - 'masonry':{'foundation_core':'minecraft:stone','dark_footing':'minecraft:deepslate_bricks','wall':'minecraft:stone_bricks','secondary_wall':'minecraft:andesite','light_coping':'minecraft:smooth_sandstone','paving':'minecraft:smooth_sandstone','paving_bands':'minecraft:smooth_stone','accent':'minecraft:green_concrete','notes':['Do not fill the surrounding rectangle. Shape all exposed plinth walls to the exact existing footprint.','The west station wing has up to16 blocks of foundation. Use vertical pilasters every8 blocks and recessed blind arch panels; one plain flat wall would look excessive.','Leave district02 building floor usable and flat. Footing lines can indicate tower and pavilions; no tall unfinished walls across doors.','Put parapets only on exposed drops, outside the clear walking width; leave all route openings unblocked.','Finish side road ends with a full-width landing; east87/northeast86 match the existing future bridge levels, so keep the boundary to those markers precise.']}, - 'quality_checks':['Actual block-for-block scan of finished footprint and road surfaces.','Cardinal-direction walking graph from spawn to station floor and every road endpoint, including stair orientation and at least2 air blocks headroom.','Every descent has half-block treads; never create a full block jump as a path transition.','Foundation columns extend down to solid existing ground; no hidden unsupported floating perimeter.','No new block in a water column; no edit outside foundation/road footprints except explicitly approved local rail, light and footing cells.','Check road width across diagonal curves; evaluate the union of row footprints rather than nearest centerline points alone.','Remove obsolete colored markers and text only inside the stage01 mutation envelope; preserve all other district reservations.','Compare orthographic live surface map with plan and inspect actual west station foundation + north axis from player height.'], - 'endpoint_join_notes':['South road ends at z110/block79 near actual ground78..80; a few local grading cells or one final stair row may be required after live voxel inspection.','Portal endpoint(-72,-46) fullY89 joins native89. Lake endpoint(-90,32) uses terminal east-facing stairY84, joining native83 beyond x-90 across the center3 cells with half-block steps; never replace this terminal stair with a full-block landing.','Northeast road must originate at the station upper landing98, not the forecourt95.','The existing camera is spectator; physical walking correctness still requires geometric collision checks.'] -} -(OUT/'foundation-design.json').write_text(json.dumps(plan,indent=2)+'\n') - -# Inspectable plan/sections are computed from immutable natural heights. -# Live surface deltas are measured separately in the JSON metrics above. -fig=plt.figure(figsize=(15,11),layout='constrained') -gs=fig.add_gridspec(2,2,width_ratios=[1.22,1],height_ratios=[1,1]) -ax=fig.add_subplot(gs[:,0]); extent=(-110,105,125,-165) -h=natural[219:510,274:490] -shade=LightSource(315,40).hillshade(h,vert_exag=2,dx=1,dy=1) -ax.imshow(h,cmap='gist_earth',extent=extent,alpha=.8,vmin=48,vmax=150) -ax.imshow(shade,cmap='gray',extent=extent,alpha=.2) -colors=['#f2dfbd','#ddd2b6','#f0c75b'] -for s,c in zip(surfaces,colors): - pp=np.array(s['points']); ax.fill(pp[:,0],pp[:,1],c,alpha=.8);ax.plot(pp[:,0],pp[:,1],color='#263e39',lw=1.2) - cc=pp.mean(axis=0);ax.text(cc[0],cc[1],s['id'].replace('-',' ')+'\nblock Y'+str(s['floor_y']),ha='center',va='center',fontsize=9,bbox=dict(facecolor='white',alpha=.8,edgecolor='none')) -for r in roads: - p=np.array(r['waypoints']);ax.plot(p[:,0],p[:,1],color='#ede3cf',lw=r['width']*.55,solid_capstyle='round');ax.plot(p[:,0],p[:,1],color='#485b53',lw=.6) - if 'end_floor_y' in r:ax.text(p[-1,0],p[-1,1],'Y'+str(r.get('endpoint_block_y',r['end_floor_y']))+(' stair' if r.get('endpoint_kind')=='stairs' else ''),fontsize=8,ha='center',va='bottom',bbox=dict(facecolor='white',alpha=.85,edgecolor='none')) -ax.set(xlim=(-105,100),ylim=(125,-165),xlabel='X — east →',ylabel='Z — south →',title='Shacraft · stage 01 foundations and local streets') -ax.set_aspect('equal');ax.grid(alpha=.15) -ax2=fig.add_subplot(gs[0,1]);xs=np.arange(-80,47); zs=-120*np.ones_like(xs); ground=natural[zs+384,xs+384] -ax2.fill_between(xs,ground,70,color='#718356',alpha=.65,label='Existing natural ground'); ax2.plot(xs,ground,color='#354d2c',lw=1) -inside=(xs>=-74)&(xs<=40);ax2.plot(xs[inside],np.full(sum(inside),99),color='#ae7731',lw=2,label='Station walking plane Y99');ax2.fill_between(xs[inside],ground[inside],99,color='#c4bda9',alpha=.6,label='Supported station plinth') -ax2.set(xlabel='X across station at Z−120',ylabel='Y elevation',ylim=(78,103),title='Station: one continuous floor; articulated west retaining base');ax2.legend(fontsize=8,loc='lower right');ax2.grid(alpha=.2) -ax3=fig.add_subplot(gs[1,1]);zvals=np.arange(-100,111);xvals=np.where(zvals<0,-6,0);ground=natural[zvals+384,xvals+384];ax3.fill_between(zvals,ground,65,color='#718356',alpha=.65) -walk=np.full(len(zvals),96.);walk[zvals<=-83]=99 -for row in stairs: - k=np.where(zvals==row['z'])[0][0];walk[k]=row['block_y']+(0.75 if row['kind']=='stairs' else 1) -for row in roads[1]['rows']: - k=np.where(zvals==row['coordinate'])[0][0];walk[k]=(row['walk_low_y']+row['walk_high_y'])/2 -ax3.plot(zvals,walk,color='#a47230',lw=2,label='Planned walking surface');ax3.plot(zvals,ground,color='#354d2c',lw=1,label='Natural centerline') -ax3.set(xlabel='Z along north/south arrival route',ylabel='Y elevation',ylim=(74,103),title='Continuous arrival sequence: station → forecourt → square → approach');ax3.grid(alpha=.2);ax3.legend(fontsize=8) -fig.savefig(OUT/'foundation-plan-and-sections.png',dpi=170) -print(json.dumps({'plan':str(OUT/'foundation-design.json'),'figure':str(OUT/'foundation-plan-and-sections.png'),'surfaces':[{k:v for k,v in s.items() if k in ['id','floor_y','metrics']} for s in surfaces]},indent=2)) diff --git a/scripts/foundation-study/geometry.py b/scripts/foundation-study/geometry.py deleted file mode 100644 index e96d026..0000000 --- a/scripts/foundation-study/geometry.py +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/env python3 -"""Rasterize the reviewed Shacraft stage-one design without editing the world. - -The public build_geometry(design, layout) function uses only the Python standard -library. Coordinate keys are (x, z). Each cell has block_y, kind, group and clear; -straight bottom stairs additionally have facing. block_y is not player feet Y. - -Precedence: exact foundation polygons, northeast landing, local roads, central -station staircase. Existing dense approved centerlines are used for road curves. -Roads extend a few flat rows back into their origin plaza so an avenue cannot -pinch down to the one-block vertex of the hexagonal square. -""" -from __future__ import annotations - -import argparse -import json -import math -from pathlib import Path -from typing import Any - -Point = tuple[int, int] -Cell = dict[str, Any] - - -def _on_segment(x: int, z: int, a, b) -> bool: - cross = (x-a[0])*(b[1]-a[1]) - (z-a[1])*(b[0]-a[0]) - return abs(cross) < 1e-8 and min(a[0],b[0]) <= x <= max(a[0],b[0]) and min(a[1],b[1]) <= z <= max(a[1],b[1]) - - -def polygon_cells(vertices) -> set[Point]: - """Integer block columns inside OR on the exact polygon, with no box fill.""" - vertices = [tuple(p) for p in vertices] - if len(vertices) < 3: - raise ValueError('A foundation polygon needs three vertices') - segments = list(zip(vertices, vertices[1:]+vertices[:1])) - result: set[Point] = set() - for z in range(math.floor(min(p[1] for p in vertices)), math.ceil(max(p[1] for p in vertices))+1): - for x in range(math.floor(min(p[0] for p in vertices)), math.ceil(max(p[0] for p in vertices))+1): - inside = False - boundary = False - for a,b in segments: - if _on_segment(x,z,a,b): - boundary = True - break - if (a[1] > z) != (b[1] > z): - cross_x = a[0] + (z-a[1])*(b[0]-a[0])/(b[1]-a[1]) - if x < cross_x: - inside = not inside - if boundary or inside: - result.add((x,z)) - return result - - -def _distance_squared(x: int, z: int, a, b) -> float: - dx,dz=b[0]-a[0],b[1]-a[1] - if not dx and not dz: - return (x-a[0])**2+(z-a[1])**2 - t=max(0.,min(1.,((x-a[0])*dx+(z-a[1])*dz)/(dx*dx+dz*dz))) - return (x-a[0]-t*dx)**2+(z-a[1]-t*dz)**2 - - -def _densify(points) -> list[Point]: - result=[] - for a,b in zip(points,points[1:]): - n=max(1,math.ceil(max(abs(a[0]-b[0]),abs(a[1]-b[1])))) - for i in range(n+1): - p=(round(a[0]+(b[0]-a[0])*i/n),round(a[1]+(b[1]-a[1])*i/n)) - if not result or result[-1] != p: - result.append(p) - if not result and points: - result=[tuple(map(round,points[0]))] - return result - - -def _cardinal_path(points: list[Point]) -> list[Point]: - """Insert cardinal intermediate samples for collision checks on diagonals.""" - if not points: - return [] - result=[points[0]] - for x,z in points[1:]: - ax,az=result[-1] - while (ax,az)!=(x,z): - if ax!=x: - ax += 1 if x>ax else -1 - else: - az += 1 if z>az else -1 - result.append((ax,az)) - return result - - -def _approved_centerline(spec, layout) -> list[Point]: - base_id=spec['id'].removesuffix('-local') - approved=next((r for r in layout.get('routes',[]) if r['id']==base_id),None) - points=_densify(approved['points'] if approved else spec['waypoints']) - if spec['geometry']=='road_profile': - axis=0 if spec['axis']=='x' else 1 - lo=min(row['coordinate'] for row in spec['rows']) - hi=max(row['coordinate'] for row in spec['rows']) - points=[p for p in points if lo <= p[axis] <= hi] - # If the approved path terminates just before a reviewed local endpoint, - # add the small explicit tail without replacing its established curve. - expected=tuple(spec['waypoints'][-1]) - if points and points[-1][axis] != expected[axis]: - points += _densify([points[-1],expected])[1:] - elif 'profile_until_z' in spec: - stop=spec['profile_until_z'] - points=[p for p in points if p[1]>=stop] - if len(points)<2: - raise ValueError(f"No usable centerline for {spec['id']}") - return points - - -def _origin_extension(points: list[Point], spec) -> list[Point]: - """Overlap the origin plateau without changing its level or approved curve.""" - first=points[0] - distance=max(4,spec['width']//2+2) - target=points[min(len(points)-1,distance)] - dx,dz=target[0]-first[0],target[1]-first[1] - length=math.hypot(dx,dz) - if not length: - return points - back=(round(first[0]-dx*distance/length),round(first[1]-dz*distance/length)) - return _densify([back,first])[:-1]+points - - -def _corridor(points: list[Point], width: int, axis=None, lo=None, hi=None): - """Clear corridor plus one block of side coping, evaluated by cell centers.""" - outer=width/2+1 - clear_radius2=(width/2)**2 - outer_radius2=outer**2 - distances: dict[Point,float] = {} - for a,b in zip(points,points[1:]): - for z in range(math.floor(min(a[1],b[1])-outer),math.ceil(max(a[1],b[1])+outer)+1): - for x in range(math.floor(min(a[0],b[0])-outer),math.ceil(max(a[0],b[0])+outer)+1): - if axis is not None and not lo <= (x,z)[axis] <= hi: - continue - d=_distance_squared(x,z,a,b) - if d <= outer_radius2+1e-8 and d < distances.get((x,z),math.inf): - distances[(x,z)]=d - return {p:d<=clear_radius2+1e-8 for p,d in distances.items()} - - -def tread_height(cell: Cell, local_x: float, local_z: float) -> float: - """Collision surface of a full block or a straight bottom stair at an offset. - - Use .25/.75 offsets to inspect both tread halves, avoiding the central edge. - """ - if cell['kind']=='full': - return cell['block_y']+1 - high={'north':local_z<.5,'south':local_z>.5, - 'west':local_x<.5,'east':local_x>.5}[cell['facing']] - return cell['block_y']+(1. if high else .5) - - -def build_geometry(design, layout): - """Return cells, route metadata and compact invariant checks. - - cells[(x,z)] -> {block_y:int,kind:'full'|'stairs',facing?:str, - group:str,clear:bool} - routes[] -> {id,centerline,centerline_4,corridor,clear_cells,clear_width} - - `clear` distinguishes usable road paving from exterior coping; for a main - polygon every floor cell is clear. It does not mean the world has been cleared. - """ - cells: dict[Point,Cell] = {} - routes=[] - polygon_areas={} - for surface in design['surfaces']: - footprint=polygon_cells(surface['points']) - polygon_areas[surface['id']]=len(footprint) - for p in footprint: - cells[p]={'block_y':surface['floor_y'],'kind':'full','group':surface['id'],'clear':True} - landing=design.get('station_ne_connection') - if landing: - for p in polygon_cells(landing['points']): - cells[p]={'block_y':landing['floor_y'],'kind':'full','group':'station-ne-connection','clear':True} - for spec in design['roads']: - centerline=_approved_centerline(spec,layout) - extended=_origin_extension(centerline,spec) - profiled=spec['geometry']=='road_profile' - if profiled: - axis=0 if spec['axis']=='x' else 1 - profiles={row['coordinate']:row for row in spec['rows']} - # Extend only the origin; stop exactly at the designed last road row. - endpoint=centerline[-1][axis] - origin=extended[0][axis] - lo,hi=sorted([endpoint,origin]) - corridor=_corridor(extended,spec['width'],axis,lo,hi) - first=spec['rows'][0] - else: - corridor=_corridor(extended,spec['width']) - # The last flat avenue rows must not cover the station staircase. - if 'profile_until_z' in spec: - corridor={p:clear for p,clear in corridor.items() if p[1]>=spec['profile_until_z']} - for p,clear in corridor.items(): - if profiled: - row=profiles.get(p[axis]) - if row is None: - row={'block_y':first['block_y'],'kind':'full'} - cell={'block_y':row['block_y'],'kind':row['kind'],'group':spec['id'],'clear':clear} - if row['kind']=='stairs': - cell['facing']=row['facing'] - else: - cell={'block_y':spec['floor_y'],'kind':'full','group':spec['id'],'clear':clear} - # A coping line within an already level clear plaza is a floor band, - # not an obstacle or a place for a parapet. Preserve that distinction. - previous=cells.get(p) - if previous and previous['clear'] and previous['block_y']==cell['block_y'] and previous['kind']==cell['kind']=='full': - cell['clear']=True - cells[p]=cell - routes.append({'id':spec['id'],'centerline':centerline,'centerline_4':_cardinal_path(centerline),'corridor':sorted(corridor),'clear_cells':sorted(p for p,c in corridor.items() if c),'clear_width':spec['width']}) - stair=design['station_entrance_stair'] - stair_cells=[] - stair_clear=[] - for row in stair['rows']: - for x in range(stair['x_min']-1,stair['x_max']+2): - p=(x,row['z']) - clear=stair['x_min']<=x<=stair['x_max'] - cells[p]={'block_y':row['block_y'],'kind':row['kind'],'group':'station-entrance-stair','clear':clear} - if row['kind']=='stairs': - cells[p]['facing']=row['facing'] - stair_cells.append(p) - if clear: - stair_clear.append(p) - mid=(stair['x_min']+stair['x_max'])//2 - line=[(mid,row['z']) for row in stair['rows']] - routes.append({'id':'station-entrance-stair','centerline':line,'centerline_4':_cardinal_path(line),'corridor':stair_cells,'clear_cells':stair_clear,'clear_width':stair['width']}) - missing=[] - blocked=[] - for route in routes: - for p in route['centerline_4']: - if p not in cells: - missing.append((route['id'],p)) - elif not cells[p]['clear']: - blocked.append((route['id'],p)) - if missing or blocked: - raise ValueError(f'Road centerline incomplete: missing={missing[:8]}, non-clear={blocked[:8]}') - max_step=0. - for route in routes: - path=route['centerline_4'] - route_step=0. - for p,q in zip(path,path[1:]): - dx,dz=q[0]-p[0],q[1]-p[1] - departure=tread_height(cells[p],.5+dx*.25,.5+dz*.25) - arrival=tread_height(cells[q],.5-dx*.25,.5-dz*.25) - step=abs(departure-arrival) - if step>.5: - raise ValueError(f"Unwalkable centerline in {route['id']}: {p}->{q}, {step} blocks") - route_step=max(route_step,step) - route['maximum_centerline_step']=route_step - max_step=max(max_step,route_step) - counts={} - for cell in cells.values(): - counts[cell['group']]=counts.get(cell['group'],0)+1 - return {'cells':cells,'routes':routes,'checks':{'columns':len(cells),'polygon_areas':polygon_areas,'columns_by_final_group':counts,'centerline_missing':len(missing),'centerline_nonclear':len(blocked),'maximum_centerline_step':max_step,'stairs':sum(c['kind']=='stairs' for c in cells.values()),'minimum_block_y':min(c['block_y'] for c in cells.values()),'maximum_block_y':max(c['block_y'] for c in cells.values()),'world_edits':0}} - - -def serializable(geometry): - return {**geometry,'cells':[{'x':x,'z':z,**cell} for (x,z),cell in sorted(geometry['cells'].items(),key=lambda p:(p[0][1],p[0][0]))]} - - -def main(): - root=Path(__file__).resolve().parents[2] - parser=argparse.ArgumentParser(description=__doc__) - parser.add_argument('--design',type=Path,default=root/'.runtime/foundation-study/design/foundation-design.json') - parser.add_argument('--layout',type=Path,default=root/'examples/layout/shacraft-lobby-layout.json') - parser.add_argument('--output',type=Path,default=root/'.runtime/foundation-study/design/geometry.json') - args=parser.parse_args() - geometry=build_geometry(json.loads(args.design.read_text()),json.loads(args.layout.read_text())) - args.output.parent.mkdir(parents=True,exist_ok=True) - args.output.write_text(json.dumps(serializable(geometry),separators=(',',':'))+'\n') - print(json.dumps({'output':str(args.output),'checks':geometry['checks']},indent=2)) - - -if __name__=='__main__': - main() diff --git a/scripts/foundation-study/verify_geometry.py b/scripts/foundation-study/verify_geometry.py deleted file mode 100644 index af43256..0000000 --- a/scripts/foundation-study/verify_geometry.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env python3 -"""Independently audit planned foundation navigation on a half-block surface grid. - -This is geometry QA, not a Minecraft collision simulator or a live block survey. -Only clear cells are traversable. Four quarter-center samples describe each full -block or straight bottom stair; adjacent samples require <= 0.5-block height change. -""" -import argparse -from collections import Counter, deque -import hashlib -import json -from pathlib import Path -import unittest - - -def normalize_cells(document): - result = {} - for cell in document['cells']: - if any(type(cell.get(key)) is not int for key in ('x', 'z', 'block_y')): - raise ValueError('Geometry coordinates and block_y must be integers') - if type(cell.get('clear')) is not bool or cell.get('kind') not in ('full', 'stairs'): - raise ValueError('Geometry needs explicit clear and full/stairs kind') - if cell['kind'] == 'stairs' and cell.get('facing') not in ('north', 'east', 'south', 'west'): - raise ValueError('Stairs need an explicit cardinal facing') - at = (cell['x'], cell['z']) - if at in result: - raise ValueError(f'Duplicate geometry cell {at}') - result[at] = cell - return result - - -def cell_nodes(x, z): - return [(2 * x + i, 2 * z + j) for i in (0, 1) for j in (0, 1)] - - -def height2(cell, sample): - if cell['kind'] == 'full': - return 2 * cell['block_y'] + 2 - ix, iz = sample[0] % 2, sample[1] % 2 - facing = cell['facing'] - high = ((facing == 'north' and iz == 0) or (facing == 'south' and iz == 1) - or (facing == 'west' and ix == 0) or (facing == 'east' and ix == 1)) - return 2 * cell['block_y'] + (2 if high else 1) - - -def surface_graph(cells): - return {node: height2(cell, node) for (x, z), cell in cells.items() if cell['clear'] - for node in cell_nodes(x, z)} - - -def neighbors(node): - x, z = node - return ((x - 1, z), (x + 1, z), (x, z - 1), (x, z + 1)) - - -def reachable(graph, source): - starts = [p for p in cell_nodes(*source) if p in graph] - if len(starts) != 4: - raise ValueError(f'Navigation source {source} is missing or non-clear') - # One source prevents accidentally joining two disconnected halves of a cell. - queue, reached = deque(starts[:1]), {starts[0]: 0} - while queue: - node = queue.popleft() - for other in neighbors(node): - if other in graph and other not in reached and abs(graph[node] - graph[other]) <= 1: - reached[other] = reached[node] + 1 - queue.append(other) - return reached - - -def at_node(node, height=None): - value = {'x': node[0] / 2 + .25, 'z': node[1] / 2 + .25} - if height is not None: - value['standing_y'] = height / 2 - return value - - -def endpoint_report(name, at, graph, reached): - nodes = cell_nodes(*at) - existing = [p for p in nodes if p in graph] - connected = [p for p in nodes if p in reached] - return {'name': name, 'x': at[0], 'z': at[1], 'surface_samples': len(existing), - 'reachable_samples': len(connected), 'passed': len(connected) == 4, - 'shortest_surface_route_blocks': min((reached[p] / 2 for p in connected), default=None)} - - -def route_report(route, cells, graph, reached): - path = [tuple(p) for p in route.get('centerline_4', route['centerline'])] - corridor = {tuple(p) for p in route['corridor']} - clear = {tuple(p) for p in route['clear_cells']} - width = route['clear_width'] - if type(width) is not int or width < 1 or width % 2 != 1: - raise ValueError('This cross-section audit requires positive odd clear_width') - missing_corridor = sorted(corridor - cells.keys()) - nonclear = sorted(p for p in clear if p not in cells or not cells[p]['clear']) - unreachable = sorted(p for p in clear if any(n not in reached for n in cell_nodes(*p))) - jumps, invalid_steps = [], [] - max_boundary_step = 0 - for p, q in zip(path, path[1:]): - dx, dz = q[0] - p[0], q[1] - p[1] - if abs(dx) + abs(dz) != 1: - invalid_steps.append({'from': list(p), 'to': list(q)}) - continue - # Check both parallel quarter-center lanes across the shared block face. - for a in cell_nodes(*p): - b = (a[0] + dx, a[1] + dz) - if (b[0] // 2, b[1] // 2) != q: - continue - if a not in graph or b not in graph: - jumps.append({'from': at_node(a), 'to': at_node(b), 'reason': 'missing_clear_surface'}) - continue - step = abs(graph[a] - graph[b]) / 2 - max_boundary_step = max(max_boundary_step, step) - if step > .5: - jumps.append({'from': at_node(a, graph[a]), 'to': at_node(b, graph[b]), - 'height_change': step, 'reason': 'height_step_exceeds_half_block'}) - cross_sections, narrow = [], [] - for i, p in enumerate(path): - # Use at least one corridor-width of tangent support. Inserted cardinal - # elbows near an axis-clipped endpoint must not rotate the cross-section - # to measure longitudinally past that deliberate terminal boundary. - window = max(6, width) - before, after = path[max(0, i - window)], path[min(len(path) - 1, i + window)] - tx, tz = after[0] - before[0], after[1] - before[1] - normal = (0, 1) if abs(tx) > abs(tz) else (1, 0) - radius = width // 2 - samples = [(p[0] + normal[0] * offset, p[1] + normal[1] * offset) - for offset in range(-radius, radius + 1)] - present = sum(s in cells for s in samples) - usable = sum(s in cells and cells[s]['clear'] for s in samples) - connected = sum(all(n in reached for n in cell_nodes(*s)) for s in samples) - cross_sections.append((present, usable, connected)) - if min(present, usable, connected) < width: - narrow.append({'x': p[0], 'z': p[1], 'normal': list(normal), 'required_width': width, - 'structural_columns': present, 'clear_columns': usable, - 'reachable_columns': connected, - 'problem_columns': [list(s) for s in samples if s not in cells or not cells[s]['clear'] - or any(n not in reached for n in cell_nodes(*s))]}) - ends = [endpoint_report(route['id'] + ':' + name, at, graph, reached) - for name, at in [('start', path[0]), ('end', path[-1])]] - passed = not (missing_corridor or nonclear or unreachable or jumps or invalid_steps or narrow) - return {'id': route['id'], 'passed': passed, 'declared_clear_width': width, - 'corridor_columns': len(corridor), 'missing_corridor_columns': missing_corridor, - 'declared_clear_columns': len(clear), 'nonclear_declared_columns': nonclear, - 'unreachable_declared_columns': unreachable, - 'cross_sections': len(cross_sections), - 'minimum_structural_cross_section': min(row[0] for row in cross_sections), - 'minimum_clear_cross_section': min(row[1] for row in cross_sections), - 'minimum_reachable_cross_section': min(row[2] for row in cross_sections), - 'narrow_cross_sections': narrow, 'centerline_invalid_steps': invalid_steps, - 'centerline_maximum_boundary_step': max_boundary_step, - 'centerline_jumps': jumps, 'endpoints': ends} - - -def audit(document, source=(0, 9), station=(-6, -105)): - cells = normalize_cells(document) - graph = surface_graph(cells) - reached = reachable(graph, source) - unreachable = sorted(p for p, cell in cells.items() if cell['clear'] - and any(n not in reached for n in cell_nodes(*p))) - routes = [route_report(route, cells, graph, reached) for route in document['routes']] - goal = endpoint_report('clock-station', station, graph, reached) - return {'version': 1, 'source': {'x': source[0], 'z': source[1]}, 'world_edits': 0, - 'method': 'Four quarter-center surface samples per clear cell; cardinal half-block BFS, rise/drop <= 0.5 block.', - 'limitations': 'Planned surface topology only. No headroom, body-width collision, material state or live-world verification. Width checks are cardinal cross-sections using the dominant local tangent; full declared masks are also checked.', - 'geometry_sha256': hashlib.sha256(json.dumps(document, sort_keys=True, separators=(',', ':')).encode()).hexdigest(), - 'planned_columns': len(cells), 'clear_columns': sum(c['clear'] for c in cells.values()), - 'surface_samples': len(graph), 'reachable_samples': len(reached), - 'unreachable_clear_columns': [list(p) for p in unreachable], - 'unreachable_by_group': dict(Counter(cells[p].get('group', 'unknown') for p in unreachable)), - 'station': goal, 'routes': routes, - 'passed': not unreachable and goal['passed'] and all(r['passed'] for r in routes)} - - -class GeometryAuditTests(unittest.TestCase): - def test_full_block_jump_separates_components(self): - cells = {(x, 0): {'kind': 'full', 'block_y': 10 if x == 0 else 11, 'clear': True} - for x in range(2)} - graph = surface_graph(cells) - self.assertEqual(len(reachable(graph, (0, 0))), 4) - - def test_stairs_connect_two_levels_for_all_directions(self): - for facing, direction in [('north', (0, -1)), ('south', (0, 1)), ('west', (-1, 0)), ('east', (1, 0))]: - with self.subTest(facing=facing): - dx, dz = direction - cells = {(-dx, -dz): {'kind': 'full', 'block_y': 9, 'clear': True}, - (0, 0): {'kind': 'stairs', 'block_y': 10, 'facing': facing, 'clear': True}, - (dx, dz): {'kind': 'full', 'block_y': 10, 'clear': True}} - graph = surface_graph(cells) - self.assertEqual(len(reachable(graph, (-dx, -dz))), 12) - - def test_nonclear_surface_is_never_traversable(self): - graph = surface_graph({(0, 0): {'kind': 'full', 'block_y': 10, 'clear': False}}) - self.assertFalse(graph) - - def test_missing_width_column_is_reported(self): - cells = {(x, z): {'x': x, 'z': z, 'kind': 'full', 'block_y': 10, 'clear': True} - for x in range(-1, 2) for z in range(3)} - del cells[(-1, 1)] - graph = surface_graph(cells) - route = {'id': 'test', 'centerline': [(0, 0), (0, 1), (0, 2)], - 'corridor': list(cells), 'clear_cells': list(cells), 'clear_width': 3} - report = route_report(route, cells, graph, reachable(graph, (0, 0))) - self.assertFalse(report['passed']) - self.assertEqual(report['minimum_structural_cross_section'], 2) - self.assertEqual(report['narrow_cross_sections'][0]['problem_columns'], [[-1, 1]]) - - -def main(): - root = Path(__file__).resolve().parents[2] - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--geometry', type=Path, default=root / '.runtime/foundation-study/design/geometry.json') - parser.add_argument('--report', type=Path, default=root / '.runtime/foundation-study/design/navigation-qa.json') - parser.add_argument('--source', type=int, nargs=2, default=(0, 9), metavar=('X', 'Z')) - parser.add_argument('--station', type=int, nargs=2, default=(-6, -105), metavar=('X', 'Z')) - parser.add_argument('--self-test', action='store_true') - args = parser.parse_args() - if args.self_test: - result = unittest.TextTestRunner().run(unittest.defaultTestLoader.loadTestsFromTestCase(GeometryAuditTests)) - raise SystemExit(0 if result.wasSuccessful() else 1) - report = audit(json.loads(args.geometry.read_text()), tuple(args.source), tuple(args.station)) - args.report.parent.mkdir(parents=True, exist_ok=True) - args.report.write_text(json.dumps(report, indent=2) + '\n') - print(json.dumps({'passed': report['passed'], 'clear_columns': report['clear_columns'], - 'reachable_samples': report['reachable_samples'], 'surface_samples': report['surface_samples'], - 'unreachable_clear_columns': len(report['unreachable_clear_columns']), - 'unreachable_by_group': report['unreachable_by_group'], 'station': report['station'], - 'routes': [{'id': r['id'], 'passed': r['passed'], - 'minimum_clear_width': r['minimum_clear_cross_section'], - 'narrow_sections': len(r['narrow_cross_sections']), - 'centerline_jumps': len(r['centerline_jumps']), - 'unreachable_columns': len(r['unreachable_declared_columns'])} for r in report['routes']], - 'report': str(args.report.resolve())}, indent=2)) - raise SystemExit(0 if report['passed'] else 1) - - -if __name__ == '__main__': - main() diff --git a/scripts/foundation-survey.py b/scripts/foundation-survey.py index bf1f711..ea56f01 100644 --- a/scripts/foundation-survey.py +++ b/scripts/foundation-survey.py @@ -371,7 +371,7 @@ def main(): capture.add_argument('--columns', type=Path) capture.add_argument('--out', type=Path, required=True) capture.add_argument('--resume', action='store_true', help='Explicitly reuse older partial observations') - capture.add_argument('--config', type=Path, default=terrain.ROOT / '.runtime/server/plugins/MinecraftBuilderMCP/config.yml') + capture.add_argument('--config', type=Path, default=terrain.RUNTIME / 'server/plugins/MinecraftBuilderMCP/config.yml') verify = commands.add_parser('verify-walk', help='Check surfaces against an observed snapshot') verify.add_argument('--snapshot', type=Path, required=True) verify.add_argument('--points', type=Path, required=True, help='JSON {scope:{...},points:[{x,z,standing_y,sub_x?,sub_z?,route?}]}') diff --git a/scripts/layout-study/README.md b/scripts/layout-study/README.md deleted file mode 100644 index cc8d383..0000000 --- a/scripts/layout-study/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Shacraft layout study - -`plan.py` produces a terrain-aware block marking plan and a review image. It reads the approved, big-endian 768 × 768 float height field at `.runtime/terrain-study/natural.f32`; it does not connect to Minecraft or write world blocks. - -Run with the terrain study's NumPy / Matplotlib environment: - -```sh -.runtime/terrain-study/venv/bin/python scripts/layout-study/plan.py -``` - -Outputs are `.runtime/layout-study/layout.json` and `layout-preview.png`. The image is a computed planning diagram, not an in-game photograph or a live map. Live world state and safe replacement materials must be checked separately before marking. - -The plan retains the Shacraft reference's district order while adapting footprints to the natural valley. A central clock avenue gives spawn a clear destination. The promenade links the station, portals, waterworks, market, gardens and harbor without forcing every trip through spawn. Scenic branches stay subordinate to that main circulation. - -The market uses six separate foundations on the calmer southern shoulder. The portal hall is moved north of the lake edge. All building and plaza footprints in the plan lie on dry ground. No route centerline cuts through a building interior by more than a doorway allowance. This is a geometric check, not a human navigation playtest. - -JSON coordinates are `[x,z]`, with north in negative Z. Feature polylines and route points are already sampled and rounded. Routes also retain their sparse `waypoints`. `role: bridge` routes have a planned `deck_y` and `abutments` with measured ground height and a future stairs flag. The northeastern bridge needs a substantial east landing stair: its deck is Y86 while that bank is approximately Y69. The three harbor piers and flagship reservation also carry explicit design heights. - -Ground contours reserve future buildings; they do not specify large flat platforms. The station and portal hall still span sloped ground, so later architecture should use separate foundations, lower wings and short stairs. The actual terrain, rivers and lake remain the governing geometry. Side trails on mountain shoulders may need stair segments when built. diff --git a/scripts/layout-study/plan.py b/scripts/layout-study/plan.py deleted file mode 100644 index dfc3f93..0000000 --- a/scripts/layout-study/plan.py +++ /dev/null @@ -1,188 +0,0 @@ -"""Design-stage contours and circulation derived from Shacraft's approved height field. -No world access or mutation. Coordinates use north=-Z; fields are planning data only. -""" -from pathlib import Path -import json, math -import numpy as np -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -from matplotlib.colors import LightSource -from matplotlib.path import Path as MplPath -ROOT=Path(__file__).resolve().parents[2] -OUT=ROOT/'.runtime/layout-study'; OUT.mkdir(parents=True,exist_ok=True) -h=np.fromfile(ROOT/'.runtime/terrain-study/natural.f32',dtype='>f4').reshape(768,768).astype(float) -features=[];routes=[] -colors={'01':'#84d440','02':'#f4d35e','03':'#bc80ef','04':'#40c8e3','05':'#64d9a0','06':'#ef9863','07':'#f5f4e9','08':'#4ca3ff','09':'#f07eaf'} -districts=[ - ('01','Arrival square',[0,8],'An open hexagonal plaza; preserve the raised grassy crown. Main north axis reveals the clock tower.'), - ('02','Clock station',[-18,-119],'A long station hall with projecting clock tower and two end pavilions. Its forecourt faces spawn.'), - ('03','Portal concourse',[-201,-156],'A chamfered hall with six separate portal-bay footprints and a sunken-feeling garden approach.'), - ('04','Airship harbor',[179,-137],'East-bank terminal with three west-facing piers above the water gorge. One future flagship uses the middle pier.'), - ('05','Sky gardens',[207,88],'Three distinct rounded landmarks connected by winding garden paths: greenhouse, winter garden, observatory.'), - ('06','Market quarter',[-195,225],'Six small footprints follow the slope around a compact square. Use stepped streets and individual foundations.'), - ('07','Arrival viaduct',[8,284],'A long south approach follows the valley ridge; the final viaduct crosses the merging river branches.'), - ('08','Lake waterworks',[-135,43],'A compact pumping house above the east lake shore and a low over-water promenade.'), - ('09','Scenic overlooks',[48,-180],'Small optional lookouts frame the valley, lake and arrival route; no mountain flattening.')] -districts=[dict(id=i,name=n,label=p,color=colors[i],intent=t) for i,n,p,t in districts] - -def closed(points): return points+[points[0]] if points[0]!=points[-1] else points - -def add(i,d,n,p,role='building',**kw): - f=dict(id=i,district=d,name=n,type='polyline' if role in ('detail','promenade','pier','bridge','axis') else 'polygon',points=p,color=colors[d],role=role,**kw) - features.append(f);return f - -def ellipse(cx,cz,rx,rz=None,n=72,angle=0): - rz=rx if rz is None else rz;a=math.radians(angle) - return closed([[round(cx+math.cos(t)*rx*math.cos(a)-math.sin(t)*rz*math.sin(a)),round(cz+math.cos(t)*rx*math.sin(a)+math.sin(t)*rz*math.cos(a))]for t in np.linspace(0,2*math.pi,n,endpoint=False)]) - -def rect(cx,cz,w,d,angle=0): - a=math.radians(angle) - return closed([[round(cx+x*math.cos(a)-z*math.sin(a)),round(cz+x*math.sin(a)+z*math.cos(a))]for x,z in[(-w/2,-d/2),(w/2,-d/2),(w/2,d/2),(-w/2,d/2)]]) - -def smooth(points): - p=np.array([points[0]]+points+[points[-1]],float);out=[] - for k in range(1,len(p)-2): - a,b,c,d=p[k-1:k+3];n=max(2,int(np.linalg.norm(c-b))) - for t in np.linspace(0,1,n,endpoint=False): - q=.5*((2*b)+(-a+c)*t+(2*a-5*b+4*c-d)*t*t+(-a+3*b-3*c+d)*t*t*t) - q=[round(float(q[0])),round(float(q[1]))] - if not out or q!=out[-1]:out.append(q) - out.append(points[-1]);return out - -def route(i,name,points,width=7,role='main',**kw): - r=dict(id=i,name=name,points=smooth(points),waypoints=points,width=width,role=role,**kw);routes.append(r);return r - -add('arrival-hex','01','Arrival square',closed([[0,-37],[39,-15],[43,31],[0,57],[-43,31],[-39,-15]]),'plaza') -add('spawn-medallion','01','Shacraft medallion reserve',ellipse(0,9,16,n=36),'plaza') -add('clock-station','02','Clock station and tower',closed([[-74,-139],[-51,-139],[-51,-145],[16,-145],[16,-139],[40,-139],[40,-98],[13,-98],[13,-83],[-25,-83],[-25,-98],[-74,-98]])) -add('station-clock-base','02','Clock tower base',rect(-6,-105,18,18),'detail') -for cx in[-63,29]:add('station-pavilion-'+str(cx),'02','End pavilion',rect(cx,-119,16,30),'detail') -add('station-forecourt','02','Station forecourt',ellipse(-5,-65,36,15,n=48),'plaza') -add('portal-hall','03','Portal concourse',closed([[-227,-163],[-161,-163],[-152,-154],[-152,-122],[-161,-113],[-227,-113],[-236,-122],[-236,-154]])) -for k,(cx,cz) in enumerate([(x,z)for z in[-151,-125]for x in[-218,-194,-170]],1):add('portal-bay-'+str(k),'03','Portal bay '+str(k),rect(cx,cz,13,8),'detail',number=k) -add('portal-forecourt','03','Portal garden court',ellipse(-159,-93,17,12,n=48),'plaza') -add('harbor-terminal','04','Airship terminal',closed([[169,-170],[191,-170],[199,-162],[199,-103],[191,-95],[169,-95],[161,-103],[161,-162]])) -for k,z in enumerate([-158,-132,-106],1): - add('airship-pier-'+str(k),'04','Airship pier '+str(k),closed([[161,z-4],[107,z-4],[103,z],[107,z+4],[161,z+4]]),'pier',deck_y=79,number=k) -add('flagship-reserve','04','Flagship mooring reserve',ellipse(78,-132,29,10,n=48),'detail',deck_y=92) -for i,(x,z,rx,rz,n) in enumerate([(185,23,25,25,'Palm glasshouse'),(211,94,22,18,'Winter garden'),(233,152,15,15,'Observatory')],1): - add('garden-'+str(i),'05',n,ellipse(x,z,rx,rz), 'building') - add('garden-court-'+str(i),'05',n+' surrounding walk',ellipse(x,z,rx+7,rz+7),'plaza') -add('market-square','06','Market square',ellipse(-195,225,22,25,n=48),'plaza') -for i,(x,z,w,d,a,n) in enumerate([(-219,188,24,18,-8,'Bakery'),(-177,195,24,18,7,'Crafts hall'),(-162,231,17,26,8,'Tea house'),(-178,272,22,18,-8,'Guild shop'),(-214,272,23,17,8,'Workshop'),(-233,239,22,26,-5,'Guild hall')],1): - add('market-'+str(i),'06',n,rect(x,z,w,d,a)) -add('waterworks-pump','08','Pumping house',rect(-135,43,14,18,-15)) -add('waterworks-lookout','08','Waterworks lookout',ellipse(-135,69,10,n=32),'plaza') -shore=[] -for z in range(-69,76,3): - wet=np.where(h[z+384,:384]<48)[0]-384 - if len(wet):shore.append([int(wet[-1]-3),z]) -add('lake-boardwalk','08','Lake boardwalk alignment',smooth(shore),'promenade',deck_y=51) -for i,(x,z,r,n) in enumerate([(43,-173,8,'North clock-view belvedere'),(-130,-188,7,'Portal ridge overlook'),(229,-76,8,'Harbor overlook'),(266,110,7,'Garden lookout'),(-258,258,7,'Valley approach lookout')],1): - add('overlook-'+str(i),'09',n,ellipse(x,z,r,n=32),'plaza') -# Pull the portal footprint north onto its drier shoulder; retain all six bays. -for f in features: - if f['id']=='portal-hall' or f['id'].startswith('portal-bay-'): - f['points']=[[x-7,round(-156+(z+138)*.8)] for x,z in f['points']] -# Direct spawn routes make the principal destinations easy to find. -route('station-axis','Clock avenue',[[0,-37],[-4,-48],[-5,-65],[-6,-83]],9) -route('portal-radial','Portal approach',[[-35,-14],[-67,-43],[-107,-65],[-135,-75],[-159,-81]],7) -route('portal-forecourt-link','Portal court entrance',[[-159,-105],[-154,-120],[-164,-139]],5,'secondary') -route('lake-radial','Lake walk',[[-42,18],[-77,27],[-104,38],[-126,40]],5,'secondary') -route('east-radial','Garden approach',[[42,16],[65,10],[82,8]],7) -route('south-axis','Arrival avenue',[[0,57],[3,97],[-5,143],[4,189],[8,237],[7,281],[5,321]],9) -# A district promenade closes two large loops and bypasses spawn. -route('ring-northwest','Station to portals',[[-40,-65],[-77,-76],[-116,-89],[-145,-105],[-152,-122],[-164,-139]],7) -route('ring-west','West inner promenade',[[-147,-111],[-125,-80],[-118,-41],[-110,4],[-115,47],[-113,88],[-127,126]],7) -route('ring-market-north','Market north street',[[-213,128],[-218,148],[-205,171],[-193,182],[-195,200]],7) -route('ring-market-south','Market south street',[[-195,250],[-187,253],[-170,252],[-145,242]],7) -route('ring-south','South inner promenade',[[-72,242],[-36,238],[8,237],[31,218],[46,194],[59,166]],7) -route('ring-garden-south','Garden south promenade',[[143,166],[172,179],[203,172],[218,157]],7) -route('ring-gardens','Garden promenade',[[218,137],[204,117],[186,109],[176,88],[184,65],[201,46]],7) -route('ring-harbor','Harbor garden promenade',[[185,-9],[197,-43],[192,-72],[181,-95]],7) -route('ring-northeast','Station to east bridge',[[40,-95],[60,-91],[82,-78]],7) -route('harbor-bridge-landing','Harbor bridge landing',[[155,-81],[171,-83],[180,-95]],7) -route('garden-bridge-landing','Garden bridge landing',[[151,8],[157,16],[160,23]],7) -route('market-shop-street','Market north street',[[-207,192],[-205,198],[-203,202]],5,'secondary') -route('market-south-shop-street','Market south shops',[[-187,253],[-178,259],[-178,262]],5,'secondary') -route('market-workshop-street','Workshop approach',[[-198,250],[-207,254],[-214,262]],5,'secondary') -route('market-guild-street','Guild approach',[[-216,231],[-220,234],[-222,236]],5,'secondary') -route('market-teahouse-street','Tea house approach',[[-173,229],[-171,229]],5,'secondary') -route('market-crafts-street','Crafts approach',[[-184,203],[-179,206]],5,'secondary') -route('waterworks-connector','Waterworks approach',[[-114,44],[-122,44],[-128,43]],5,'secondary') -route('waterworks-lookout-path','Waterworks viewing path',[[-135,52],[-135,59]],3,'secondary') -# Bridges are measured separately. Grade is a later construction task, not terrain flattening. -for i,n,a,b,w in [ - ('bridge-northeast','Clock / harbor bridge',[82,-78],[155,-81],7), - ('bridge-east','Spawn / garden bridge',[82,8],[151,8],7), - ('bridge-southeast','Garden / south bridge',[59,166],[143,166],7), - ('bridge-market-north','Market / lake bridge',[-127,126],[-213,128],7), - ('bridge-market-south','Market / arrival bridge',[-145,242],[-72,242],7), - ('arrival-viaduct','Arrival viaduct',[5,321],[5,383],11)]: - pts=np.linspace(a,b,int(np.linalg.norm(np.array(b)-a))+1).round().astype(int) - heights=h[pts[:,1]+384,pts[:,0]+384] - deck=int(math.ceil(max(heights[0],heights[-1])))+2 - if i=='arrival-viaduct':deck=max(deck,65) - r=route(i,n,[a,b],w,'bridge',deck_y=deck,minimum_ground=float(heights.min()),endpoint_ground=[float(heights[0]),float(heights[-1])]); - # Edge lines are appropriate for construction marking; they are not a complete bridge. - r['intent']='Mark both parapet alignments and abutments; preserve the river and banks.' - r['abutments']=[dict(point=q,ground_y=round(float(y),1),deck_y=deck,future_stairs=(deck-math.floor(y)>4),rise=deck-math.floor(y)) for q,y in zip([a,b],[heights[0],heights[-1]])] -# Scenic branches never substitute for main circulation. -for i,n,p in[ - ('north-overlook-path','North overlook',[[40,-128],[58,-140],[62,-161],[50,-171]]), - ('portal-overlook-path','Portal ridge path',[[-159,-151],[-136,-158],[-123,-172],[-129,-181]]), - ('harbor-overlook-path','Harbor lookout trail',[[196,-80],[213,-75],[221,-76]]), - ('garden-overlook-path','Garden lookout trail',[[232,96],[249,99],[259,107]]), - ('south-overlook-path','Valley lookout trail',[[-218,236],[-224,219],[-248,223],[-263,240],[-264,251]])]:route(i,n,p,3,'secondary') -# Geometric checks on the immutable approved field; live blocks must still be checked before writes. -zz,xx=np.mgrid[-384:384,-384:384] -for f in features: - pts=np.array(f['points']);sample=h[np.clip(pts[:,1]+384,0,767),np.clip(pts[:,0]+384,0,767)] - if f['type']=='polygon': - x0,z0=pts.min(axis=0);x1,z1=pts.max(axis=0);px,pz=np.meshgrid(np.arange(x0,x1+1),np.arange(z0,z1+1)) - mask=MplPath(pts).contains_points(np.column_stack((px.flat,pz.flat)),radius=.01).reshape(px.shape) - sample=h[z0+384:z1+385,x0+384:x1+385][mask] - f['ground']={'min':round(float(sample.min()),2),'max':round(float(sample.max()),2),'water_samples':int((sample<48).sum()),'samples':len(sample)} -for r in routes: - pts=np.array(r['points']);v=h[pts[:,1]+384,pts[:,0]+384] - ds=np.linalg.norm(np.diff(pts,axis=0),axis=1);grade=np.abs(np.diff(v))/np.maximum(ds,.001) - r['analysis']={'length':round(float(ds.sum()),1),'ground_min':round(float(v.min()),1),'ground_max':round(float(v.max()),1),'p95_raw_grade':round(float(np.percentile(grade,95)),2),'water_samples':int((v<48).sum())} -plan={'schema':'shacraft-layout-study-v1','world':'shacraft_lobby_v2','bounds':{'min_x':-384,'max_x':383,'min_z':-384,'max_z':383},'water_y':48,'status':'offline design; not a world mutation or live snapshot','districts':districts,'features':features,'routes':routes,'construction_notes':[ - 'Colored outlines are building reservations, not instructions to level their entire bounding boxes.', - 'Walks remain aligned to terrain; steep local runs need short stairs during the later building phase.', - 'Main avenues 9 blocks, district ring 7, side paths 5, scenic trails 3; marker widths can be thinner than final clear widths.', - 'Keep protected water intact. Bridges have a separate planned deck Y above their endpoint terrain.', - 'The natural western lake bank is too steep for a main ring street. The main route uses the calm inner/east lake shoulder; the low over-water boardwalk is secondary.', - 'No minigame arenas. Six portal bays, three harbor piers, one future flagship reservation.' -]} -(OUT/'layout.json').write_text(json.dumps(plan,indent=2)+'\n') -# Render a design review from data, not a Minecraft screenshot. -dz,dx=np.gradient(h);rock=np.clip((np.hypot(dx,dz)-.55)/1.5,0,1)[...,None] -rgb=np.array([.32,.40,.27])*(1-rock)+np.array([.49,.49,.46])*rock -rgb=LightSource(315,42).shade_rgb(rgb,h,vert_exag=1,blend_mode='soft');rgb=np.where((h<48)[...,None],np.array([.13,.28,.35]),rgb) -fig,ax=plt.subplots(figsize=(14,14),facecolor='#151b18');ax.set_facecolor('#151b18') -ax.imshow(rgb,extent=(-384,384,384,-384)) -for r in routes: - p=np.array(r['points']);lw=2.2 if r['role']!='secondary' else 1 - ax.plot(p[:,0],p[:,1],color='#f7f2de' if r['role']!='bridge' else '#ffffff',lw=lw,alpha=.92,linestyle='-' if r['role']!='bridge' else '--') -for f in features: - p=np.array(f['points']);ax.plot(p[:,0],p[:,1],color=f['color'],lw=2 if f['role'] not in ('detail','promenade') else 1.25) -for d in districts: - if d['id']=='09':continue - x,z=d['label'];ax.text(x,z,d['id'],color='#111914',ha='center',va='center',fontsize=12,fontweight='bold',bbox=dict(boxstyle='circle,pad=.35',fc=d['color'],ec='#111914',lw=1)) -ax.set(xlim=(-325,310),ylim=(384,-255),xlabel='X / blocks',ylabel='Z / blocks; north up') -ax.set_title('SHACRAFT / terrain-adapted lobby layout',loc='left',color='#f5f3ea',fontsize=19,pad=22) -ax.tick_params(colors='#b5c0b8');ax.xaxis.label.set_color('#b5c0b8');ax.yaxis.label.set_color('#b5c0b8') -for i,d in enumerate(districts): - fig.text(.085+(i%3)*.302,.065-(i//3)*.021,d['id']+' '+d['name'],color=d['color'],fontsize=11) -fig.text(.085,.009,'DESIGN STUDY • Exact approved height field; outlines not yet placed. White: routes / dashed: future bridge spans.',color='#b5c0b8',fontsize=9) -fig.subplots_adjust(left=.06,right=.985,top=.945,bottom=.11) -fig.savefig(OUT/'layout-preview.png',dpi=140);plt.close(fig) -print(json.dumps({'features':len(features),'routes':len(routes),'path_length':round(sum(r['analysis']['length']for r in routes)), 'output':str(OUT)})) -print('BUILDING HEIGHT SPANS') -for f in features: - if f['role']=='building':print(f['id'],f['ground']) -print('BRIDGES') -for r in routes: - if r['role']=='bridge':print(r['id'],r['deck_y'],r['endpoint_ground'],r['analysis']) diff --git a/scripts/layout.py b/scripts/layout.py index a378c21..66e3328 100644 --- a/scripts/layout.py +++ b/scripts/layout.py @@ -15,6 +15,7 @@ spec = importlib.util.spec_from_file_location('layout_terrain', Path(__file__).w terrain = importlib.util.module_from_spec(spec) spec.loader.exec_module(terrain) ROOT = terrain.ROOT +RUNTIME = terrain.RUNTIME MAX_BLOCKS = 4096 MAX_OPERATIONS = 256 MAX_READ_CELLS = 32 @@ -287,7 +288,7 @@ def main(): if name != 'undo': p.add_argument('layout', type=Path) p.add_argument('--manifest', type=Path, required=True) - p.add_argument('--config', type=Path, default=ROOT / '.runtime/server/plugins/MinecraftBuilderMCP/config.yml') + p.add_argument('--config', type=Path, default=RUNTIME / 'server/plugins/MinecraftBuilderMCP/config.yml') p.add_argument('--console', action='store_true', help='Only for an explicitly configured isolated fixture') if name != 'prepare': p.add_argument('--execute', action='store_true', required=True, help='Explicitly perform this world edit') diff --git a/scripts/light-shacraft-station.py b/scripts/light-shacraft-station.py deleted file mode 100644 index 98b905b..0000000 --- a/scripts/light-shacraft-station.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env python3 -"""Compile concealed station lighting against an explicit observed snapshot. - -No world I/O. Each fitting replaces full cubes only: brown glass forms a flush -floor tile or a recessed ceiling lens, with glowstone hidden directly behind it. -The caller applies and independently verifies the resulting checked recipe. -""" - -import argparse -from collections import Counter -from datetime import datetime, timezone -import hashlib -import importlib.util -import json -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[1] -STAGE = ROOT / ".runtime/station-stage07" -GLOW = "minecraft:glowstone" -LENS = "minecraft:brown_stained_glass" -WOOD = "minecraft:spruce_planks" -CREAM = "minecraft:smooth_sandstone" - - -def _module(name, path): - spec = importlib.util.spec_from_file_location(name, path) - value = importlib.util.module_from_spec(spec) - spec.loader.exec_module(value) - return value - - -survey = _module("station_lighting_survey", ROOT / "scripts/foundation-survey.py") - - -def compile_lighting(before, station): - if station["scope"] != before.scope: - raise ValueError("Station metadata and observed snapshot have different scopes") - footprint = {tuple(p) for p in station["footprint"]} - inside = {(x, z) for x, z in footprint - if all((x + dx, z + dz) in footprint - for dx in range(-2, 3) for dz in range(-2, 3))} - targets = {feet: {(t["x"] + dx, t["z"] + dz) - for t in station["interior"]["navigation"][str(feet)]["named_targets"] - for dx in range(-1, 2) for dz in range(-1, 2)} - for feet in (99, 113)} - # Keep both cabin landings, controls, doorway and immediate approach unchanged. - lift_exclusion = {(x, z) for x in range(-11, 0) for z in range(-125, -112)} - changes = {} - fixtures = [] - - def full(state): - return state.split("[", 1)[0].removeprefix("minecraft:") in survey.FULL - - def put(x, y, z, value, group): - if (x, z) not in inside or not 97 <= y <= 124: - raise ValueError(f"Lighting write outside authorized volume: {(x, y, z)}") - old = before.state(x, y, z) - if not full(old) or not full(value): - raise ValueError(f"Lighting may replace full cubes only: {(x, y, z)} {old}") - if old == value: - return - p = (x, y, z) - if p in changes and changes[p]["block"] != value: - raise ValueError(f"Lighting layers disagree at {p}") - changes[p] = {"x": x, "y": y, "z": z, "expected": old, - "block": value, "group": group} - - for feet, ceiling in ((99, 110), (113, 123)): - public = {(p["x"], p["z"]) for p in station["interior"]["walk_points_by_floor"][str(feet)]} - public &= inside - candidates = {"floor": set(), "ceiling": set()} - for x, z in sorted(public - lift_exclusion): - # Existing flooring mosaics and green/brass bands are not recoloured. - if (x, z) not in targets[feet] and before.state(x, feet - 1, z) == CREAM: - neighborhood = {(x + dx, z + dz) for dx in range(-1, 2) for dz in range(-1, 2)} - if neighborhood <= public and all( - before.state(xx, y, zz) in survey.AIR - for xx, zz in neighborhood for y in range(feet, feet + 4) - ) and full(before.state(x, feet - 2, z)): - candidates["floor"].add((x, z)) - # Plain spruce cells only: beams, chains, chandeliers, panels and lift - # geometry are excluded by the observed material and air-column tests. - if (before.state(x, ceiling, z) == WOOD - and full(before.state(x, ceiling + 1, z)) - and all(before.state(x, y, z) in survey.AIR for y in range(feet, ceiling))): - candidates["ceiling"].add((x, z)) - - for kind in ("floor", "ceiling"): - chosen = set() - # A common architectural grid; a maximum two-block adjustment avoids - # a column, mosaic or ceiling beam without creating dense bright rows. - for gx in range(-69, 40, 9): - for gz in range(-139, -83, 9): - nearby = [(x, z) for x in range(gx - 2, gx + 3) - for z in range(gz - 2, gz + 3) - if (x, z) in candidates[kind]] - nearby.sort(key=lambda p: ((p[0] - gx) ** 2 + (p[1] - gz) ** 2, - abs(p[0] - gx) + abs(p[1] - gz), p)) - selected = next((p for p in nearby if all( - (p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2 >= 36 for q in chosen)), None) - if selected is None: - continue - x, z = selected - chosen.add(selected) - lens_y, emit_y = (feet - 1, feet - 2) if kind == "floor" else (ceiling, ceiling + 1) - group = f"station-lighting:{feet}:{kind}" - put(x, lens_y, z, LENS, group) - put(x, emit_y, z, GLOW, group) - fixtures.append({"floor_walk_y": feet, "kind": kind, - "lens": [x, lens_y, z], "emitter": [x, emit_y, z], - "grid_anchor_xz": [gx, gz]}) - - recipe = {"version": 1, "scope": before.scope, - "blocks": [changes[p] for p in sorted(changes)]} - metadata = { - "version": 1, "scope": before.scope, "world_writes": 0, - "status": "checked lighting candidate; live application and visual review pending", - "style": "Sparse warm floor tiles and small concealed ceiling lenses on a nine-block grid", - "grid_spacing": 9, "maximum_anchor_adjustment": 2, - "minimum_same_layer_fixture_distance": 6, - "owned_y": [97, 124], "perimeter_setback": 2, - "changed_states": len(changes), "fixtures": fixtures, - "fixture_counts": dict(sorted(Counter(f"{f['floor_walk_y']}:{f['kind']}" for f in fixtures).items())), - "changed_materials": dict(Counter(row["block"] for row in changes.values())), - "checks": {"only_full_cube_replacements": True, "no_new_collision_obstructions": True, - "existing_mosaic_bands_preserved": True, "lift_landings_and_doorway_excluded": True, - "floor_fixtures_outside_named_target_neighborhoods": True, - "floor_fixtures_have_clear_three_by_three_surrounds": True, - "ceiling_lenses_replace_only_plain_spruce": True, - "decorative_furniture_and_diorama_preserved": True}, - "limits": "Fixture geometry does not predict the client's final rendered brightness. Review actual Minecraft images after applying and waiting for lighting updates.", - } - return recipe, metadata - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--before", type=Path, default=STAGE / "after.json.gz") - parser.add_argument("--station-metadata", type=Path, default=STAGE / "compiled/station.metadata.json") - parser.add_argument("--output", type=Path, default=STAGE / "lighting") - args = parser.parse_args() - recipe, metadata = compile_lighting(survey.load_snapshot(args.before), json.loads(args.station_metadata.read_text())) - metadata["compiled_at_utc"] = datetime.now(timezone.utc).isoformat() - metadata["inputs"] = {str(path): hashlib.sha256(path.read_bytes()).hexdigest() - for path in (args.before, args.station_metadata, Path(__file__))} - args.output.mkdir(parents=True, exist_ok=True) - for name, doc in (("lighting.json", recipe), ("lighting.metadata.json", metadata)): - destination = args.output / name - if destination.exists(): - raise FileExistsError(f"Preserve the previous candidate: {destination}") - destination.write_text(json.dumps(doc, indent=2) + "\n") - print(json.dumps({"recipe": str(args.output / "lighting.json"), "changed_states": len(recipe["blocks"]), - "fixture_counts": metadata["fixture_counts"]})) - - -if __name__ == "__main__": - main() diff --git a/scripts/live-camera-test.py b/scripts/live-camera-test.py index f2d8713..72fdd25 100755 --- a/scripts/live-camera-test.py +++ b/scripts/live-camera-test.py @@ -24,6 +24,7 @@ import uuid import zlib ROOT = Path(__file__).resolve().parents[1] +RUNTIME = Path(os.environ.get('MCB_RUNTIME_DIR') or ROOT / '.runtime').expanduser().resolve() MAX_RESPONSE = 12 * 1024 * 1024 PNG_SIGNATURE = b'\x89PNG\r\n\x1a\n' SAFE_METADATA = { @@ -271,7 +272,7 @@ def run(args): safe_json = json.dumps(metadata, ensure_ascii=False, allow_nan=False, indent=2).encode('utf-8') + b'\n' if agent_token.encode() in safe_json or camera_token.encode() in safe_json: raise TestFailure('Refusing to save metadata containing a component secret.') - directory = ROOT / '.runtime/camera-test' + directory = RUNTIME / 'camera-test' directory.mkdir(parents=True, exist_ok=True, mode=0o700) prefix = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ') + '-' + capture_id[:8] image_path, metadata_path = directory / (prefix + '.png'), directory / (prefix + '.json') @@ -286,7 +287,7 @@ def run(args): def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('--config', type=Path, default=ROOT / '.runtime/server/plugins/MinecraftBuilderMCP/config.yml') + parser.add_argument('--config', type=Path, default=Path(os.environ.get('MCB_CAMERA_PAPER_CONFIG') or RUNTIME / 'server/plugins/MinecraftBuilderMCP/config.yml').expanduser()) source = parser.add_mutually_exclusive_group() source.add_argument('--camera-id', default='test', help='Saved in-game camera name; default: test') source.add_argument('--pose', type=float, nargs=5, metavar=('X', 'Y', 'Z', 'YAW', 'PITCH'), help='Explicit feet position and viewing angles') diff --git a/scripts/mark-shacraft-layout.py b/scripts/mark-shacraft-layout.py deleted file mode 100644 index 38581d8..0000000 --- a/scripts/mark-shacraft-layout.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python3 -"""Compile the Shacraft spatial study into checked, reversible survey blocks. - -This only writes a desired-block document. scripts/layout.py performs live edits. -Ground lines replace one observed surface block; they never level terrain. -""" -import argparse -from collections import Counter -import json -import math -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -COLORS = {'01': 'lime', '02': 'yellow', '03': 'purple', '04': 'cyan', - '05': 'red', '06': 'orange', '07': 'white', '08': 'blue', '09': 'pink'} -RGB = {'lime':'#98d84d','yellow':'#f6d34a','purple':'#9460ce','cyan':'#23b6b6', - 'green':'#527c31','orange':'#f78d27','white':'#f0f0e6','blue':'#4a69d8', - 'pink':'#f394b5','black':'#26282d','light_gray':'#a4aaa6','gray':'#545c61', - 'red':'#e3544b','light_blue':'#68c8ec'} -FONT = { - '0':['111','101','101','101','111'], '1':['010','110','010','010','111'], - '2':['111','001','111','100','111'], '3':['111','001','111','001','111'], - '4':['101','101','111','001','001'], '5':['111','100','111','001','111'], - '6':['111','100','111','101','111'], '7':['111','001','010','010','010'], - '8':['111','101','111','101','111'], '9':['111','101','111','001','111'], - 'S':['111','100','111','001','111'], -} - - -def raster_line(points, radius=.6): - """Integer columns whose centres meet a piecewise line, with no diagonal holes.""" - result=set() - for (ax,az),(bx,bz) in zip(points,points[1:]): - dx,dz=bx-ax,bz-az; length=dx*dx+dz*dz - for z in range(math.floor(min(az,bz)-radius),math.ceil(max(az,bz)+radius)+1): - for x in range(math.floor(min(ax,bx)-radius),math.ceil(max(ax,bx)+radius)+1): - t=max(0,min(1,((x-ax)*dx+(z-az)*dz)/length)) if length else 0 - if (x-ax-t*dx)**2+(z-az-t*dz)**2 <= radius**2: - result.add((x,z)) - return result - - -def offset(points, distance): - result=[] - for i,(x,z) in enumerate(points): - a=points[max(0,i-3)];b=points[min(len(points)-1,i+3)] - dx,dz=b[0]-a[0],b[1]-a[1];norm=math.hypot(dx,dz) or 1 - result.append((x-dz/norm*distance,z+dx/norm*distance)) - return result - - -def main(): - p=argparse.ArgumentParser(description=__doc__) - p.add_argument('--study',type=Path,required=True);p.add_argument('--surface',type=Path,required=True) - p.add_argument('--scope',type=Path,required=True);p.add_argument('--output',type=Path,required=True) - args=p.parse_args();study=json.loads(args.study.read_text());surface=json.loads(args.surface.read_text()) - scope=json.loads(args.scope.read_text()) - if surface['world_uuid']!=scope['world_id'] or surface['world']!=study['world']: - raise ValueError('Study, world surface and authorization scope disagree') - W=surface['width'];MINX=surface['min_x'];MINZ=surface['min_z'] - heights=surface['surface_y'];palette=surface['palette'];materials=surface['material_index'] - desired={}; priorities={};skipped=Counter();raised=[] - - def at(x,z): - if not MINX<=x<=surface['max_x'] or not MINZ<=z<=surface['max_z']: - return None,None - i=(z-MINZ)*W+x-MINX - return heights[i],palette[materials[i]] - - def put(x,z,color,group,priority=10,y=None): - x,z=round(x),round(z);top,material=at(x,z) - if top is None:skipped['outside']+=1;return - if y is None: - if material=='minecraft:water':skipped['water']+=1;return - y=max(50,top) - if y=priorities.get(key,-1): - desired[key]={'x':x,'y':y,'z':z,'block':block,'expected':expected,'group':group} - priorities[key]=priority - - def stroke(points,color,group,radius=.6,priority=10,y=None): - for x,z in sorted(raster_line(points,radius)): - put(x,z,color,group,priority,y) - - def text(value,cx,cz,color,group,scale=2): - width=(len(value)*4-1)*scale - for j,char in enumerate(value): - for row,bits in enumerate(FONT[char]): - for col,bit in enumerate(bits): - if bit=='1': - for dz in range(scale): - for dx in range(scale): - put(cx-width//2+j*4*scale+col*scale+dx,cz-5*scale//2+row*scale+dz, - color,group,35) - - # The main route is a corridor reservation, with a clear green interior. - # Thin edge lines and spaced centre ticks keep the terrain visually dominant. - for route in study['routes']: - pts=route['points'];group=route['id'];width=route['width'];deck=route.get('deck_y') - is_bridge=route['role']=='bridge' - edgecolor='white' if width>=5 else 'light_gray' - if is_bridge:edgecolor='white' - for side in (-1,1): - stroke(offset(pts,side*(width-1)/2),edgecolor,group,.65,10,deck) - if width>=7: - for i in range(0,len(pts),18): - stroke(pts[i:i+3],'light_gray',group,.55,11,deck) - if is_bridge: - # Cross ties define the future deck without filling it or the river. - for i in range(0,len(pts),12): - a=offset(pts,-(width-1)/2)[i];b=offset(pts,(width-1)/2)[i] - stroke([a,b],'red' if group=='arrival-viaduct' else 'light_gray',group,.55,12,deck) - for end in (0,-1): - for side in (-1,1): - x,z=offset(pts,side*(width-1)/2)[end] - for y in range(deck+1,deck+5):put(x,z,'white',group,28,y) - put(x,z,'glowstone',group,29,deck+5) - - for feature in study['features']: - color=COLORS[feature['district']];role=feature['role'];group=feature['id'] - pts=feature['points'];deck=feature.get('deck_y') - if feature['type']=='polygon' and pts[0]!=pts[-1]:pts=pts+[pts[0]] - radius=1.05 if role in ('building','pier') or group=='arrival-hex' else .65 - stroke(pts,color,group,radius,20,deck) - if role=='building': - # Four sparse survey stakes make footprints recognizable at eye level. - for point in [pts[i] for i in sorted(set([0,(len(pts)-1)//4,(len(pts)-1)//2,3*(len(pts)-1)//4]))]: - x,z=point;top,_=at(x,z) - if top is None:continue - for y in range(top+1,top+4):put(x,z,color,group,27,y) - put(x,z,'glowstone',group,28,top+4) - - # Wayfinding IDs appear as blocks as well as external map labels. - # Small districts use one-block pixels to keep their reservations readable. - for district in study['districts']: - ident=district['id'];x,z=district['label'];color=COLORS[ident] - if ident=='09':continue - x,z={'01':(-22,27),'07':(26,271),'08':(-136,63)}.get(ident,(x,z)) - text(ident,x,z,'white','label-'+ident,1 if ident=='08' else 2) - # The brand medallion remains a reserved ring; its S is a simple block glyph. - text('S',0,8,'lime','spawn-monogram',3) - - # Monumental colored posts stand outside the main entry points, never in a path. - posts=[('01',33,48),('02',11,-73),('03',-151,-125),('04',197,-97), - ('05',174,75),('06',-171,226),('07',18,287),('08',-132,43)] - labels=[] - for ident,x,z in posts: - top,material=at(x,z) - if material=='minecraft:water':raise ValueError('Wayfinding post in water') - color=COLORS[ident];group='wayfinding-'+ident - for xx in range(x-1,x+2): - for zz in range(z-1,z+2):put(xx,zz,color,group,40) - for y in range(top+1,top+9):put(x,z,color,group,40,y) - put(x,z,'glowstone',group,41,top+9) - for xx in (x-1,x+1):put(xx,z,color,group,40,top+7) - district=next(d for d in study['districts'] if d['id']==ident) - labels.append({'id':ident,'name':district['name'],'x':x+.5,'y':top+11,'z':z+.5,'color':RGB[color]}) - - blocks=sorted(desired.values(),key=lambda b:(b['z']//16,b['x']//16,b['y']//16,b['y'],b['z'],b['x'])) - out={'version':1,'scope':scope,'blocks':blocks} - args.output.parent.mkdir(parents=True,exist_ok=True) - args.output.write_text(json.dumps(out,separators=(',',':'))+'\n') - metadata={'source_surface':str(args.surface),'world':surface['world'],'blocks':len(blocks), - 'by_material':dict(Counter(b['block'] for b in blocks)), - 'by_group':dict(Counter(b['group'] for b in blocks)), - 'skipped_strokes':dict(skipped),'raised_fixed_height_strokes':raised, - 'wayfinding_labels':labels,'districts':study['districts'], - 'note':'Surface contours preserve height; bridge outlines reserve a future deck. Walkability requires later paths and stairs.'} - args.output.with_suffix('.metadata.json').write_text(json.dumps(metadata,indent=2)+'\n') - print(json.dumps({'blocks':len(blocks),'groups':len(metadata['by_group']), - 'skipped':dict(skipped),'raised_fixed_height_strokes':len(raised)})) - - -if __name__=='__main__':main() diff --git a/scripts/plaza-assets.py b/scripts/plaza-assets.py deleted file mode 100644 index 41b6bc5..0000000 --- a/scripts/plaza-assets.py +++ /dev/null @@ -1,177 +0,0 @@ -"""Small, stateless voxel assets for the Shacraft arrival gardens. - -Every public builder returns ``{(x, y, z): full_block_state}`` in local block -coordinates. Origin Y is the first air block above the paving/soil: translate -by Y96 for a paving block at Y95. No builder reads or changes a Minecraft world. -Trees need soil under their trunk; the composer owns foundations, collision -checks, leaf-distance propagation, block connection updates, and placement. -""" - -from __future__ import annotations - -import json -import math -import random - -Position = tuple[int, int, int] -Asset = dict[Position, str] -_DIRECTIONS = ("north", "east", "south", "west") - - -def _state(material: str, **properties: object) -> str: - suffix = ",".join( - f"{key}={str(value).lower()}" for key, value in sorted(properties.items()) - ) - return f"minecraft:{material}" + (f"[{suffix}]" if suffix else "") - - -def _stair(material: str, facing: str) -> str: - return _state(material, facing=facing, half="bottom", shape="straight", waterlogged=False) - - -def _connections(material: str, *directions: str) -> str: - return _state(material, **{d: d in directions for d in _DIRECTIONS}, waterlogged=False) - - -def _rotate(asset: Asset, quarter_turns: int) -> Asset: - """Rotate north to east, including stair backs and fence/bar connections.""" - result: Asset = {} - direction = {d: _DIRECTIONS[(i + quarter_turns) % 4] for i, d in enumerate(_DIRECTIONS)} - for (x, y, z), state in asset.items(): - for _ in range(quarter_turns): - x, z = -z, x - if "[" in state: - material, raw = state[:-1].split("[", 1) - properties = dict(pair.split("=", 1) for pair in raw.split(",")) - properties = {direction.get(k, k): direction.get(v, v) for k, v in properties.items()} - if quarter_turns % 2 and properties.get("axis") in ("x", "z"): - properties["axis"] = "z" if properties["axis"] == "x" else "x" - state = _state(material.removeprefix("minecraft:"), **properties) - result[x, y, z] = state - return result - - -def conifer(height: int = 13, seed: int = 0) -> Asset: - """A narrow spruce with irregular connected whorls and three clear trunk rows. - - Height is the occupied block count, 11..15. The maximum canopy radius is - three blocks, and foliage begins at local Y3. Leaves deliberately start at - distance=7; recompute distances against the composed world before applying. - """ - if type(height) is not int or not 11 <= height <= 15: - raise ValueError("Conifer height must be an integer from 11 through 15") - if type(seed) is not int: - raise ValueError("Conifer seed must be an integer") - rng = random.Random(seed) - phase = rng.uniform(0, math.tau) - phase2 = rng.uniform(0, math.tau) - leaves = _state("spruce_leaves", distance=7, persistent=True, waterlogged=False) - asset: Asset = {} - for y in range(3, height): - progress = (y - 3) / (height - 4) - tier = (0.30, -0.30, -0.65)[(y - 3) % 3] - radius = max(0.0, 2.95 * (1.0 - progress) ** 0.85 + tier) - if y == height - 1: - radius = 0.0 - for x in range(-3, 4): - for z in range(-3, 4): - angle = math.atan2(z, x) - edge = radius + 0.23 * math.cos(4 * angle + phase) + 0.16 * math.sin(3 * angle + phase2) - edge += rng.uniform(-0.10, 0.10) - if (x == 0 and z == 0) or math.hypot(x, z) <= edge: - asset[x, y, z] = leaves - # Leave a connected green leader above the last woody branch; overwriting - # these narrow upper layers with logs creates visible brown pegs at the tip. - for y in range(height - 5): - asset[0, y, 0] = _state("spruce_log", axis="y") - # Angular variation can leave a diagonal-only leaf at a narrow upper tier. - # Keep the face-connected canopy so no isolated foliage floats beside it. - connected = {(0, 0, 0)} - pending = [(0, 0, 0)] - while pending: - x, y, z = pending.pop() - for dx, dy, dz in ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)): - neighbor = (x + dx, y + dy, z + dz) - if neighbor in asset and neighbor not in connected: - connected.add(neighbor) - pending.append(neighbor) - return {position: state for position, state in asset.items() if position in connected} - - -def bench(length: int = 5, facing: str = "north") -> Asset: - """A 3..5 block overall bench, including its two stone armrests. - - ``facing`` is the seated viewer's direction, not Minecraft's stair-facing - property: a north-looking seat has a south-facing stair/high back. The - default occupies X=-2..2, Z=0..1, Y=0; its front opens toward negative Z. - The fence back has Minecraft's 1.5-block collision height. This is decorative - seating and does not add a sit interaction. - """ - if type(length) is not int or not 3 <= length <= 5: - raise ValueError("Bench overall length must be an integer from 3 through 5") - if facing not in _DIRECTIONS: - raise ValueError("Bench facing must be north, east, south, or west") - first = -(length // 2) - last = first + length - 1 - asset: Asset = {} - for x in range(first + 1, last): - asset[x, 0, 0] = _stair("spruce_stairs", "south") - asset[x, 0, 1] = _connections("spruce_fence", "north", "east", "west") - for x in (first, last): - asset[x, 0, 0] = _state("stone_bricks") - asset[x, 0, 1] = _state("stone_bricks") - return _rotate(asset, _DIRECTIONS.index(facing)) - - -def lamp(height: int = 6) -> Asset: - """A slender warm street lamp with a copper hood and a small brass collar. - - Height is 6..8 occupied blocks. The stone/chain stem occupies one column; - the 3x3 cross-shaped housing begins at height-3, above pedestrian headroom. - Its hanging lantern has a full copper block immediately above it. The four - inward-facing copper stairs form the hood eaves; no trapdoors are used. - """ - if type(height) is not int or not 6 <= height <= 8: - raise ValueError("Lamp height must be an integer from 6 through 8") - collar_y = height - 3 - light_y = height - 2 - roof_y = height - 1 - asset: Asset = { - (0, 0, 0): _state("chiseled_stone_bricks"), - (0, 1, 0): _state("stone_brick_wall", east="none", north="none", south="none", up=True, waterlogged=False, west="none"), - (0, collar_y, 0): _state("gold_block"), - (0, light_y, 0): _state("lantern", hanging=True, waterlogged=False), - (0, roof_y, 0): _state("waxed_oxidized_cut_copper"), - } - for y in range(2, collar_y): - asset[0, y, 0] = _state("iron_chain", axis="y", waterlogged=False) - for x, z, inward in ((-1, 0, "east"), (1, 0, "west"), (0, -1, "south"), (0, 1, "north")): - asset[x, collar_y, z] = _connections("iron_bars", inward) - asset[x, light_y, z] = _connections("iron_bars") - asset[x, roof_y, z] = _stair("waxed_oxidized_cut_copper_stairs", inward) - return asset - - -def describe(asset: Asset) -> dict: - """Compact occupied bounds and ground contact cells for the layout composer.""" - if not asset: - raise ValueError("Cannot describe an empty asset") - low = [min(p[i] for p in asset) for i in range(3)] - high = [max(p[i] for p in asset) for i in range(3)] - return { - "blocks": len(asset), - "min": dict(zip(("x", "y", "z"), low)), - "max": dict(zip(("x", "y", "z"), high)), - "size": dict(zip(("x", "y", "z"), (high[i] - low[i] + 1 for i in range(3)))), - "ground_contacts": sorted([x, z] for x, y, z in asset if y == 0), - "materials": sorted({state.split("[", 1)[0] for state in asset.values()}), - } - - -if __name__ == "__main__": - print(json.dumps({ - "coordinate_convention": "Local Y0 is first air above paving; add Y96 over paving Y95.", - "conifer": describe(conifer()), - "bench_north": describe(bench()), - "lamp": describe(lamp()), - }, indent=2)) diff --git a/scripts/polish-gothic-hall.py b/scripts/polish-gothic-hall.py deleted file mode 100644 index 3cd6582..0000000 --- a/scripts/polish-gothic-hall.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python3 -"""Plan, apply, or verify the Gothic hall polish against its saved canonical baseline.""" -import argparse -from collections import defaultdict -import fcntl -import importlib.util -import json -from pathlib import Path -import time - -from builds.gothic_hall.scene import boxes - -ROOT = Path(__file__).resolve().parents[1] -OUTPUT = ROOT / '.runtime/gothic-hall/polish' -MANIFEST = OUTPUT / 'manifest.json' -LEDGER = OUTPUT / 'ledger.json' -AIR = 'minecraft:air' -spec = importlib.util.spec_from_file_location('gothic_finish', ROOT / 'scripts/finish-gothic-hall.py') -finish = importlib.util.module_from_spec(spec) -spec.loader.exec_module(finish) -require, digest, point, position = finish.require, finish.digest, finish.point, finish.position - - -class Backend(finish.Backend): - def rpc(self, method, params): - try: - return super().rpc(method, params) - except RuntimeError as error: - raise RuntimeError(str(error).replace('finish ledger', 'polish ledger')) from None - - -def matches(actual, requested): - """Match explicit requested properties while accepting registry defaults.""" - def split(state): - name, _, suffix = state.partition('[') - properties = dict(item.split('=', 1) for item in suffix.rstrip(']').split(',') if item) - return name, properties - actual_name, actual_properties = split(actual) - requested_name, requested_properties = split(requested) - return actual_name == requested_name and all( - actual_properties.get(key) == value for key, value in requested_properties.items()) - - -def baseline(): - model, originals, plans = finish.references() - patch = json.loads(finish.LEDGER.read_text()) - finish.validate_saved_finish(patch, finish.targets(model, originals), model, plans) - states = dict(originals) - states.update({point(c['pos']): c['desired'] for c in patch['changes']}) - return model, states, plans, patch - - -def bounds(positions): - lo = tuple(min(p[i] for p in positions) for i in range(3)) - hi = tuple(max(p[i] for p in positions) for i in range(3)) - require((hi[0] - lo[0] + 1) * (hi[1] - lo[1] + 1) * (hi[2] - lo[2] + 1) <= 2048, - 'Inspection volume exceeds the 16x8x16 tile budget') - return {'min': position(lo), 'max': position(hi)} - - -def manifest(model, states, plans, patch): - from builds.gothic_hall.polish_scene import finished_scene, build_scene - before, after = finished_scene().blocks, build_scene().blocks - origin = model['origin'] - - def world(local): - return tuple(a + b for a, b in zip(local, origin)) - - require({world(p) for p in before} == set(states), 'Finished scene baseline mask changed') - require(all(matches(states[world(p)], value) for p, value in before.items()), - 'Finished scene differs from the original plans plus the 18 finish states') - changes, tiles = {}, defaultdict(dict) - for local in sorted(before.keys() | after.keys()): - old, desired = before.get(local, AIR), after.get(local, AIR) - if finish.canonical(old) == finish.canonical(desired): - continue - require(-2 <= local[0] <= 68 and -1 <= local[1] <= 64 and -2 <= local[2] <= 68, - 'Polish extends beyond the agreed local bounds') - at = world(local) - expected = states.get(at, AIR) - # Different spellings that only make existing default properties explicit - # have no world effect and are omitted before requesting a server plan. - if finish.canonical(expected) == finish.canonical(desired): - continue - changes[local] = {'pos': position(at), 'expected': expected, 'desired': desired} - tiles[(local[0] // 16, local[1] // 8, local[2] // 16)][local] = desired - batches = [] - for tile, cells in sorted(tiles.items()): - compressed = boxes(cells) - for start in range(0, len(compressed), 256): - operations, selected = [], [] - for x1, y1, z1, x2, y2, z2, block in compressed[start:start + 256]: - operations.append({'type': 'box', 'min': position(world((x1, y1, z1))), - 'max': position(world((x2, y2, z2))), 'block': block}) - selected.extend(changes[(x, y, z)] - for x in range(x1, x2 + 1) for y in range(y1, y2 + 1) - for z in range(z1, z2 + 1)) - selected.sort(key=lambda c: point(c['pos'])) - require(len(selected) == len({point(c['pos']) for c in selected}) <= 2048, - 'Compressed batch contains duplicates or exceeds the block budget') - batches.append({'key': '_'.join(map(str, (*tile, start // 256))), - **bounds([point(c['pos']) for c in selected]), 'changes': selected, - 'recipe': {'version': 1, 'operations': operations}}) - require(sum(len(b['changes']) for b in batches) == len(changes), 'Incomplete polish batch mask') - return {'version': 1, 'name': 'Gothic hall polish', 'origin': origin, - 'world_id': model['world_id'], 'world_epoch': model['world_epoch'], - 'baseline_manifest_sha256': digest(model), 'finish_ledger_sha256': digest(patch), - 'source_plans': {key: {'plan_id': value['plan_id'], 'sha256': value['sha256']} - for key, value in plans.items()}, - 'before_blocks': len(before), 'after_blocks': len(after), 'changes': len(changes), - 'added': sum(p not in before for p in changes), - 'removed': sum(p not in after for p in changes), 'batches': batches} - - -def prepared(record, batch, model, scope): - plan, checksum = finish.envelope( - finish.CONFIG.parent / 'journal/plans' / (record['plan']['plan_id'] + '.json'), 'plan') - require(checksum == record['plan']['plan_hash'], 'Prepared polish plan hash changed') - require(plan['worldEpoch'] == model['world_epoch'] and plan['region']['worldId'] == model['world_id'], - 'Prepared polish world identity changed') - require(plan['projectId'] == scope['project_id'], 'Prepared polish project changed') - expected = {point(c['pos']): c for c in batch['changes']} - actual = {point(c['pos']): c for c in plan['changes']} - require(len(plan['changes']) == len(actual) == len(expected) and actual.keys() == expected.keys(), - 'Prepared polish mask differs from the reviewed diff') - for at, change in actual.items(): - require(change['expected'] == expected[at]['expected'], - f'Prepared expected state changed at world {at}; preserve current world') - require(matches(change['desired'], expected[at]['desired']), - f'Prepared desired properties differ at world {at}') - require(record['idempotency_key'] == 'gothic-hall-polish-' + plan['id'], - 'Polish idempotency identity changed') - return {at: c['desired'] for at, c in actual.items()} - - -def inspect(backend, region, states, planter_soil=frozenset()): - result = backend.rpc('region_inspect', {**region, 'detail': 'blocks'}) - live = {point(block['pos']): block['state'] for block in result['blocks']} - variations = [] - for at, expected in states.items(): - # Verification may observe grass spreading over the newly planted soil. - # Preparation and application pass no such allowance and remain exact. - if at in planter_soil and expected == 'minecraft:dirt' and live.get(at) == 'minecraft:grass_block[snowy=false]': - variations.append({'pos': position(at), 'expected': expected, 'observed': live[at]}) - continue - require(live.get(at) == expected, - f'Live state differs at world {at}; preserve current world and inspect before continuing') - return variations - - -def completed(backend, record, apply=False): - ident = finish.operation_id(record) - if ident is None and apply: - # The record and key have already been fsynced. A transport exception - # escapes immediately; a later invocation first inspects the journal. - result = backend.rpc('build_apply', {**record['plan'], 'idempotency_key': record['idempotency_key']}) - ident = result['operation_id'] - require(ident is not None, 'A required operation has not been applied') - for _ in range(600 if apply else 1): - status = backend.rpc('operation_status', {'operation_id': ident}) - if status['status'] not in ('queued', 'applying'): - break - if apply: - time.sleep(.1) - require(status['status'] == 'applied', - f'Operation {ident} stopped at {status["status"]}; inspect before continuing') - return ident - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('action', choices=['plan', 'apply', 'verify']) - args = parser.parse_args() - model, states, source_plans, patch = baseline() - wanted = manifest(model, states, source_plans, patch) - if MANIFEST.exists(): - require(digest(json.loads(MANIFEST.read_text())) == digest(wanted), - 'Immutable polish manifest differs from the current generator or baseline') - else: - require(args.action == 'plan', 'Run plan before apply or verify') - finish.save(MANIFEST, wanted, immutable=True) - backend = Backend() - context = backend.rpc('project_context', {}) - require(context['world_id'] == model['world_id'] and context['world_epoch'] == model['world_epoch'], - 'Live world identity changed') - require(patch['scope'] == backend.scope, 'Finished baseline owner/project scope changed') - completed(backend, patch) - if LEDGER.exists(): - ledger = json.loads(LEDGER.read_text()) - else: - require(args.action == 'plan', 'The polish ledger is missing; run plan first') - ledger = {'version': 1, 'manifest_sha256': digest(wanted), 'scope': backend.scope, 'batches': {}} - finish.save(LEDGER, ledger, immutable=True) - require(ledger['version'] == 1 and ledger['manifest_sha256'] == digest(wanted) - and ledger['scope'] == backend.scope, 'Polish ledger identity changed') - require(set(ledger['batches']) <= {b['key'] for b in wanted['batches']}, 'Unknown polish ledger batch') - canonical_targets = {} - for batch in wanted['batches']: - key = batch['key'] - if key not in ledger['batches']: - require(args.action == 'plan', 'Some polish batches are not prepared; run plan first') - inspect(backend, {'min': batch['min'], 'max': batch['max']}, - {point(c['pos']): c['expected'] for c in batch['changes']}) - summary = backend.rpc('build_prepare', {'recipe': batch['recipe']}) - record = {'plan': summary, 'idempotency_key': 'gothic-hall-polish-' + summary['plan_id']} - prepared(record, batch, wanted, backend.scope) - # Existing records are never replaced. Atomic publication follows - # server-plan validation and always precedes any build_apply call. - ledger['batches'][key] = record - finish.save(LEDGER, ledger) - canonical_targets.update(prepared(ledger['batches'][key], batch, wanted, backend.scope)) - if args.action == 'plan': - print(json.dumps({'status': 'prepared', 'changes': wanted['changes'], - 'batches': len(wanted['batches']), 'ledger': str(LEDGER)})) - return - operations = [] - for index, batch in enumerate(wanted['batches'], 1): - ident = completed(backend, ledger['batches'][batch['key']], apply=args.action == 'apply') - operations.append(ident) - if args.action == 'apply': - inspect(backend, {'min': batch['min'], 'max': batch['max']}, - {point(c['pos']): canonical_targets[point(c['pos'])] for c in batch['changes']}) - print(json.dumps({'status': 'applied', 'batch': index, 'batches': len(wanted['batches']), - 'operation_id': ident}), flush=True) - if args.action == 'apply': - return - final, tiles = {**states, **canonical_targets}, defaultdict(dict) - for at, value in final.items(): - local = tuple(a - b for a, b in zip(at, wanted['origin'])) - tiles[(local[0] // 16, local[1] // 8, local[2] // 16)][at] = value - planter_soil = frozenset(at for at, state in canonical_targets.items() - if state == 'minecraft:dirt' and at[1] == wanted['origin'][1]) - soil_variations = [] - for _, group in sorted(tiles.items()): - soil_variations.extend(inspect(backend, bounds(list(group)), group, planter_soil)) - report = {'status': 'verified', 'checked_positions': len(final), - 'blocks': sum(value != AIR for value in final.values()), - 'checked_air': sum(value == AIR for value in final.values()), - 'changes': wanted['changes'], 'inspection_tiles': len(tiles), - 'accepted_planter_grass': soil_variations, - 'manifest_sha256': digest(wanted), 'ledger_sha256': digest(ledger), - 'operations': operations, 'scope': backend.scope, 'verified_at': time.time()} - finish.save(OUTPUT / 'verification.json', report) - print(json.dumps(report)) - - -if __name__ == '__main__': - try: - OUTPUT.mkdir(parents=True, exist_ok=True) - with (OUTPUT / '.lock').open('a') as lock: - fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) - main() - except (OSError, ValueError, KeyError, RuntimeError) as error: - raise SystemExit('Polish stopped: ' + str(error)) from None diff --git a/scripts/render-layout-map.py b/scripts/render-layout-map.py deleted file mode 100644 index 9c7f044..0000000 --- a/scripts/render-layout-map.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -"""Render a survey atlas from live surface data; optional blocks are a labelled preview.""" -import argparse -import json -import math -from pathlib import Path -import numpy as np -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -from matplotlib.colors import to_rgb, LightSource - -ROOT=Path(__file__).resolve().parents[1] -COLOR={'01':'#98d84d','02':'#f6d34a','03':'#9460ce','04':'#23b6b6','05':'#e3544b', - '06':'#f78d27','07':'#f0f0e6','08':'#4a69d8','09':'#f394b5'} -BLOCK={'lime':'#98d84d','yellow':'#f6d34a','purple':'#9460ce','cyan':'#23b6b6', - 'green':'#527c31','orange':'#f78d27','white':'#f0f0e6','blue':'#4a69d8', - 'pink':'#f394b5','black':'#26282d','light_gray':'#a4aaa6','red':'#e3544b'} - -def main(): - p=argparse.ArgumentParser();p.add_argument('surface',type=Path);p.add_argument('--study',type=Path,required=True) - p.add_argument('--blocks',type=Path);p.add_argument('--output',type=Path,required=True) - args=p.parse_args();s=json.loads(args.surface.read_text());study=json.loads(args.study.read_text()) - h=np.array(s['surface_y']).reshape(s['length'],s['width']).astype(float) - indexes=np.array(s['material_index']).reshape(h.shape);palette=list(s['palette']);colors=list(s['palette_rgb']) - if args.blocks: - for b in json.loads(args.blocks.read_text())['blocks']: - x,z=b['x']-s['min_x'],b['z']-s['min_z'] - if b['y']',color=fg,lw=1.5)) - ax.plot([-288,-224],[355,355],color=fg,lw=3);ax.text(-256,370,'64 blocks',color=fg,ha='center',fontsize=9) - fig.text(.06,.953,'SHACRAFT',color=fg,fontsize=28,fontweight='bold') - fig.text(.242,.958,'LOBBY / SITE MARKING',color=muted,fontsize=16) - fig.text(.746,.881,'DISTRICTS',color=muted,fontsize=12,fontweight='bold') - descriptions={ - '01':'Hexagonal arrival plaza\nCentral Shacraft medallion', - '02':'Clock tower + station hall\nPavilions and forecourt', - '03':'Portal concourse\nSix individual portal bays', - '04':'Airship terminal\nThree piers + flagship reserve', - '05':'Palm house · winter garden\nObservatory and garden walks', - '06':'Market square\nSix separate building plots', - '07':'Arrival avenue and viaduct\nSouthern entrance to the valley', - '08':'Lake pumping house\nBoardwalk and viewing terrace', - '09':'Scenic overlooks\nSmall optional ridge trails'} - for i,d in enumerate(study['districts']): - y=.842-i*.067;ident=d['id'] - fig.text(.749,y,ident,color=COLOR[ident],fontsize=16,fontweight='bold') - fig.text(.779,y,descriptions[ident],color=fg,fontsize=10,linespacing=1.55) - fig.text(.747,.20,'READING THE MARKS',color=muted,fontsize=11,fontweight='bold') - fig.text(.747,.169,'Color = building / courtyard boundary\nWhite = future path edges\nRaised ribs = bridge deck reservation\nLit stakes = corners and wayfinding',color=fg,fontsize=9,linespacing=1.7,va='top') - note='DESIGN PREVIEW · proposed blocks over a live survey' if args.blocks else 'ACTUAL WORLD SURFACE · captured after placement · no player camera required' - fig.text(.06,.055,note,color=fg,fontsize=10) - fig.text(.06,.035,'768 × 768 world · central development shown · terrain heights and water preserved · paths and stairs are still reservations',color=muted,fontsize=9) - args.output.parent.mkdir(parents=True,exist_ok=True);fig.savefig(args.output,dpi=150,facecolor=bg);plt.close(fig) - print(args.output) - -if __name__=='__main__':main() diff --git a/scripts/station-exterior.py b/scripts/station-exterior.py deleted file mode 100644 index 5cf50c0..0000000 --- a/scripts/station-exterior.py +++ /dev/null @@ -1,423 +0,0 @@ -#!/usr/bin/env python3 -"""Deterministic, detached exterior for the approved two-floor Shacraft station. - -The caller supplies the exact station foundation cells and combines this shell with -the interior before taking expected-state snapshots. This module never reads or -writes a Minecraft world. Coordinates and block states are explicit and stable. -""" - -from collections import Counter, defaultdict -from math import ceil, hypot - - -AIR = "minecraft:air" -STONE = "minecraft:stone_bricks" -DARK = "minecraft:polished_deepslate" -CREAM = "minecraft:smooth_sandstone" -ASHLAR = "minecraft:cut_sandstone" -PALE = "minecraft:smooth_quartz" -PILLAR = "minecraft:quartz_pillar[axis=y]" -GREEN = "minecraft:waxed_oxidized_cut_copper" -DEEP_GREEN = "minecraft:green_concrete" -GOLD = "minecraft:gold_block" -WOOD = "minecraft:spruce_planks" -GLASS = "minecraft:brown_stained_glass" -CLEAR_GLASS = "minecraft:glass" -LAMP = "minecraft:lantern[hanging=true,waterlogged=false]" -CHAIN = "minecraft:iron_chain[axis=y,waterlogged=false]" -GLOW = "minecraft:glowstone" -CARDINALS = ((0, -1, "north"), (1, 0, "east"), (0, 1, "south"), (-1, 0, "west")) - - -def _dilate(cells, radius): - return {(x + dx, z + dz) for x, z in cells - for dx in range(-radius, radius + 1) for dz in range(-radius, radius + 1)} - - -def _erode(cells, radius): - return {(x, z) for x, z in cells if all((x + dx, z + dz) in cells - for dx in range(-radius, radius + 1) for dz in range(-radius, radius + 1))} - - -def _runs(values): - result = [] - for value in sorted(values): - if result and value == result[-1][-1] + 1: - result[-1].append(value) - else: - result.append([value]) - return result - - -def _line(x0, y0, x1, y1): - """Inclusive integer line for legible clock hands and roof trim.""" - dx, dy = abs(x1 - x0), -abs(y1 - y0) - sx, sy = 1 if x0 < x1 else -1, 1 if y0 < y1 else -1 - error = dx + dy - while True: - yield x0, y0 - if (x0, y0) == (x1, y1): - return - twice = 2 * error - if twice >= dy: - error += dy - x0 += sx - if twice <= dx: - error += dx - y0 += sy - - -def compile_exterior(footprint: set[tuple[int, int]], layout: dict): - """Return (block states, ownership groups, metadata), with no external effects. - - Structural floors are exactly the supplied footprint. Interior ornament may - replace their finish, but must retain the two complete separating decks. - All exterior windows are full glass blocks, including their inner wall layer. - Only the south entrance removes wall blocks below the sealed roof space. - """ - footprint = {(int(x), int(z)) for x, z in footprint} - if not footprint: - raise ValueError("Station foundation must not be empty") - if any(not (-74 <= x <= 40 and -145 <= z <= -83) for x, z in footprint): - raise ValueError("Foundation exceeds the approved station envelope") - if layout.get("perimeter_wall_thickness", 2) != 2: - raise ValueError("The approved shell requires two-block perimeter walls") - inner = _erode(footprint, 2) - walls = footprint - inner - boundary = footprint - _erode(footprint, 1) - shadow = _dilate(footprint, 2) - maximum_shadow = _dilate(footprint, 3) - states, groups = {}, {} - - def put(x, y, z, state, group): - x, y, z = int(x), int(y), int(z) - if not (-78 <= x <= 44 and -149 <= z <= -80 and 98 <= y <= 160): - raise ValueError(f"Exterior escaped approved coordinate bounds: {(x, y, z)}") - if (x, z) not in maximum_shadow: - raise ValueError(f"Exterior overhang exceeds three blocks: {(x, y, z)}") - if y == 98 and (x, z) not in footprint: - raise ValueError(f"Ground floor escaped foundation: {(x, y, z)}") - states[x, y, z] = state - groups[x, y, z] = group - - def box(x0, x1, y0, y1, z0, z1, state, group, mask=None): - for z in range(z0, z1 + 1): - for x in range(x0, x1 + 1): - if mask is not None and (x, z) not in mask: - continue - for y in range(y0, y1 + 1): - put(x, y, z, state, group) - - def stair(material, facing, half="bottom"): - return f"minecraft:{material}[facing={facing},half={half},shape=straight,waterlogged=false]" - - # The upper cabin will be stationary; neither deck contains a lift-shaft hole. - for x, z in sorted(footprint): - for y, state, group in ((98, CREAM, "floor.ground"), - (111, WOOD, "floor.intermediate.structure"), - (112, CREAM, "floor.upper"), - (124, WOOD, "ceiling.upper.structure"), - (125, CREAM, "ceiling.upper.seal")): - put(x, y, z, state, group) - for x, z in sorted(walls): - for y in range(99, 125): - state = ASHLAR - if y == 99: - state = DARK - elif y == 100: - state = STONE - elif y in (101, 109, 113, 122, 123): - state = CREAM - elif y in (110, 111): - state = STONE - elif y in (112, 124): - state = PALE - put(x, y, z, state, "wall.masonry") - - # Identify straight stretches from the exact contour, including its eastern recess. - faces = [] - for nx, nz, facing in CARDINALS: - planes = defaultdict(list) - for x, z in boundary: - if (x + nx, z + nz) not in footprint: - planes[z if nz else x].append(x if nz else z) - for plane, positions in sorted(planes.items()): - for run in _runs(positions): - if len(run) >= 7: - faces.append((nx, nz, facing, plane, run[0], run[-1])) - - def face_position(nx, nz, plane, tangent, depth=0): - # Positive depth is toward the inside; negative depth is a relief projection. - return (tangent - nx * depth, plane - nz * depth) if nz else (plane - nx * depth, tangent - nz * depth) - - windows, pilasters, facade_lamps = [], [], [] - for nx, nz, facing, plane, lo, hi in faces: - phase = -6 if nz else -119 - centers = [c for c in range(lo + 3, hi - 2) if (c - phase) % 10 == 0] - if not centers and hi - lo >= 8: - centers = [(lo + hi) // 2] - for center in centers: - for base, top in ((102, 108), (115, 122)): - # A five-wide pointed arch, enclosed by cream voussoirs and piers. - for tangent in range(center - 3, center + 4): - delta = abs(tangent - center) - for y in range(base - 1, top + 2): - cap = top - max(0, delta - 1) - opening = delta <= 2 and base <= y <= cap - state = GLASS if opening else CREAM - if opening and tangent == center and y < top - 1: - state = WOOD - if opening and y == base + 3: - state = WOOD - if delta == 3 and y <= top - 1: - state = PILLAR - for depth in (0, 1): - x, z = face_position(nx, nz, plane, tangent, depth) - if (x, z) in walls: - put(x, y, z, state, "window.frame" if state != GLASS else "window.glass") - # Bottom sill projects by one block but never opens the shell. - for tangent in range(center - 3, center + 4): - x, z = face_position(nx, nz, plane, tangent, -1) - if (x, z) in maximum_shadow: - put(x, base - 1, z, CREAM, "window.sill") - windows.append({"normal": facing, "plane": plane, "center": center, - "base_y": base, "apex_y": top, "glass_layers": 2}) - # Vertical bays use quiet, regular dressed-stone pilasters, not material noise. - pier_centers = sorted({lo, hi, *[c + 5 for c in centers if c + 5 <= hi]}) - for center in pier_centers: - x, z = face_position(nx, nz, plane, center) - for depth in (0, 1): - px, pz = face_position(nx, nz, plane, center, depth) - if (px, pz) not in walls: - continue - for y in range(101, 124): - put(px, y, pz, PILLAR if y not in (110, 111, 112) else PALE, "wall.pilaster") - pilasters.append([x, z]) - if center not in (lo, hi) and hi - lo >= 15: - px, pz = face_position(nx, nz, plane, center, -1) - # Eave-mounted lighting: clear below, and supported immediately above. - put(px, 124, pz, CREAM, "lamp.bracket") - put(px, 123, pz, CHAIN, "lamp.chain") - put(px, 122, pz, LAMP, "lamp.lantern") - facade_lamps.append([px, 122, pz]) - - # Continuous layered cornices connect every projection of the exact footprint. - first_relief = _dilate(footprint, 1) - inner - second_relief = shadow - _erode(footprint, 1) - for x, z in sorted(first_relief): - put(x, 110, z, CREAM, "cornice.floor.lower") - put(x, 112, z, PALE, "cornice.floor.upper") - put(x, 124, z, CREAM, "cornice.eave.lower") - for x, z in sorted(second_relief): - put(x, 125, z, CREAM, "cornice.eave.upper") - - # Union of three pitched masses: a broad central hall and two hipped end wings. - # Every roof column is closed, without making the inaccessible attic solid fill. - def roof_height(x, z): - candidates = [] - if -53 <= x <= 18 and -147 <= z <= -95: - candidates.append(140 - ceil(abs(z + 121) * 14 / 26)) - if -76 <= x <= -50 and -141 <= z <= -96: - candidates.append(126 + max(0, min(13 - abs(x + 63), z + 141, -96 - z))) - if 14 <= x <= 42 and -141 <= z <= -96: - candidates.append(126 + max(0, min(14 - abs(x - 28), z + 141, -96 - z))) - if -27 <= x <= 15 and -100 <= z <= -81: - candidates.append(126 + max(0, min(10 - abs(z + 91), x + 27, 15 - x))) - return max([126, *candidates]) - - roof_heights = {p: min(140, roof_height(*p)) for p in shadow} - shadow_boundary = shadow - _erode(shadow, 1) - for x, z in sorted(shadow): - top = roof_heights[x, z] - # Close all verge/gable faces down to the continuous eave line. - if (x, z) in shadow_boundary: - for y in range(126, top): - put(x, y, z, ASHLAR if y < top - 1 else GREEN, "roof.gable") - put(x, top - 1, z, GREEN, "roof.underlay") - uphill = [(roof_heights.get((x + dx, z + dz), top - 1), facing) - for dx, dz, facing in CARDINALS] - high, facing = max(uphill, key=lambda item: item[0]) - state = stair("waxed_oxidized_cut_copper_stairs", facing) if high > top else GREEN - if (x + 6) % 12 == 0 and top < 139: - state = GREEN - put(x, top, z, state, "roof.copper") - if top == 140: - put(x, 140, z, GREEN, "roof.ridge") - - # Four pavilion lantern roofs lend a clear rhythm to the ends of the facade. - pavilions = [(-66, -131, 7), (-66, -106, 7), (32, -131, 7), (28, -106, 6)] - for cx, cz, radius in pavilions: - pavilion = {(x, z) for x in range(cx - radius, cx + radius + 1) - for z in range(cz - radius, cz + radius + 1) if (x, z) in shadow} - for x, z in sorted(pavilion): - ring = max(abs(x - cx), abs(z - cz)) - top = 137 - ring - # The cap only raises the parent roof; it never cuts an accidental opening. - if top <= roof_heights[x, z]: - continue - for y in range(roof_heights[x, z], top): - put(x, y, z, GREEN if y >= top - 1 else CREAM, "pavilion.roof.support") - facing = "east" if x < cx else "west" if x > cx else "south" if z < cz else "north" - put(x, top, z, stair("waxed_oxidized_cut_copper_stairs", facing) if ring else GREEN, "pavilion.roof.copper") - for y, state in ((138, GREEN), (139, GOLD), (140, CHAIN)): - put(cx, y, cz, state, "pavilion.finial") - - # Shacraft-green hanging stone panels, with a small gold S motif on the end bays. - # These are block reliefs, not entities or inventory-backed banner blocks. - shield_centers = [(-65, -97, 1), (28, -97, 1), (-65, -140, -1), (31, -140, -1)] - glyph = ("111", "100", "111", "001", "111") - for cx, plane, normal in shield_centers: - for y in range(110, 123): - half = 2 if y >= 112 else y - 109 - for dx in range(-half, half + 1): - put(cx + dx, y, plane, GOLD if abs(dx) == half else DEEP_GREEN, "ornament.green.shield") - for row, bits in enumerate(glyph): - for col, bit in enumerate(bits): - if bit == "1": - put(cx + col - 1, 120 - row, plane + normal, GOLD, "ornament.gold.s") - for dx in range(-3, 4): - put(cx + dx, 123, plane, CREAM, "ornament.shield.cap") - - # The projecting entrance arch remains seven blocks clear at useful head height. - axis = int(layout.get("entrance_axis_x", -6)) - entrance = layout.get("entrance_opening_x", [-9, -3]) - for x in range(axis - 6, axis + 7): - dx = abs(x - axis) - for z in (-85, -84): - if (x, z) not in footprint: - continue - for y in range(99, 111): - if dx in (4, 5): - put(x, y, z, PILLAR if y > 100 else DARK, "entrance.pier") - elif y >= 109 - min(dx, 4): - put(x, y, z, CREAM, "entrance.arch") - entrance_air = [] - for x in range(int(entrance[0]), int(entrance[1]) + 1): - top = 109 - abs(x - axis) - for z in (-85, -84, -83): - for y in range(99, top + 1): - put(x, y, z, AIR, "entrance.clear") - entrance_air.append([x, y, z]) - for px in (axis - 6, axis + 6): - put(px, 108, -83, CREAM, "entrance.lamp.bracket") - put(px, 108, -82, CREAM, "entrance.lamp.bracket") - put(px, 107, -82, CHAIN, "entrance.lamp.chain") - put(px, 106, -82, LAMP, "entrance.lamp") - # A high, glazed lancet over the portal echoes the long window below the reference clock. - for x in range(axis - 3, axis + 4): - for y in range(115, 124): - cap = 123 - abs(x - axis) - state = GLASS if y <= cap else CREAM - if x == axis or y == 118: - state = WOOD if y <= cap else CREAM - for z in (-85, -84): - if (x, z) in walls: - put(x, y, z, state, "entrance.upper.lancet") - - # Sealed clock tower: solid underside already exists at Y124/125, no future-floor doorway. - tx0, tx1, tz0, tz1 = axis - 9, axis + 9, -102, -84 - # Carry the tower's front corner piers down through both public-storey facades. - for x in (tx0, tx0 + 1, tx1 - 1, tx1): - for z in (tz1, tz1 - 1): - for y in range(101, 125): - put(x, y, z, PALE if y in (110, 111, 112, 124) else PILLAR, "tower.facade.pier") - tower = {(x, z) for x in range(tx0, tx1 + 1) for z in range(tz0, tz1 + 1)} - tower_inner = _erode(tower, 2) - tower_wall = tower - tower_inner - for x, z in sorted(tower): - put(x, 126, z, CREAM, "tower.base.seal") - for x, z in sorted(tower_wall): - corner = (x <= tx0 + 1 or x >= tx1 - 1) and (z <= tz0 + 1 or z >= tz1 - 1) - for y in range(127, 148): - state = PILLAR if corner else ASHLAR - if y in (128, 130, 147): - state = PALE - put(x, y, z, state, "tower.masonry") - # Clock disks have a stone backing, recessed cream face and four gold bezels. - clock_faces = [("south", axis, -82), ("north", axis, -104), - ("west", -16, -93), ("east", 4, -93)] - hour_marks = {(0, 6), (0, -6), (6, 0), (-6, 0), (4, 4), (-4, 4), (4, -4), (-4, -4)} - hands = set(_line(0, 0, -3, 4)) | set(_line(0, 0, 3, 3)) - for facing, center, plane in clock_faces: - nx, nz = {"south": (0, 1), "north": (0, -1), "west": (-1, 0), "east": (1, 0)}[facing] - for u in range(-8, 9): - for v in range(-8, 9): - radius = hypot(u, v) - if radius > 8.3: - continue - x, z = (center + u, plane) if nz else (center, plane + u) - # All ornament is supported by one continuous backing layer. - put(x - nx, 139 + v, z - nz, CREAM, "clock.backing") - state = CREAM - if radius > 7.4: - state = PALE - elif radius > 6.5: - state = GOLD - elif (u, v) in hour_marks: - state = DEEP_GREEN - elif (u, v) in hands: - state = DARK - if (u, v) == (0, 0): - state = GOLD - put(x, 139 + v, z, state, "clock.face." + facing) - # Broad cream cap, steep patinated copper crown and a restrained gold finial. - tower_cap = _dilate(tower, 1) - for x, z in sorted(tower_cap): - put(x, 148, z, PALE, "tower.cornice") - for x, z in sorted(tower): - radius = max(abs(x - axis), abs(z + 93)) - top = 149 + max(0, 7 - radius) - for y in range(149, top): - put(x, y, z, GREEN, "tower.roof.underlay") - facing = "east" if x < axis else "west" if x > axis else "south" if z < -93 else "north" - put(x, top, z, stair("waxed_oxidized_cut_copper_stairs", facing) if radius else GREEN, "tower.roof.copper") - for x, z in ((tx0, tz0), (tx1, tz0), (tx0, tz1), (tx1, tz1)): - put(x, 150, z, GREEN, "tower.corner.finial") - put(x, 151, z, GOLD, "tower.corner.finial") - put(x, 152, z, CHAIN, "tower.corner.finial") - for y, state in ((157, GOLD), (158, CHAIN)): - put(axis, y, -93, state, "tower.central.finial") - for x, z in ((tx0 - 1, tz1), (tx1 + 1, tz1), (tx0 - 1, tz0), (tx1 + 1, tz0)): - put(x, 148, z, PALE, "tower.lamp.bracket") - put(x, 147, z, CHAIN, "tower.lamp.chain") - put(x, 146, z, LAMP, "tower.lamp") - - # Deterministic checks describe the shell; the root performs full merged navigation QA. - entrance_columns = {(x, z) for x in range(entrance[0], entrance[1] + 1) for z in (-85, -84, -83)} - assert all(states.get((x, y, z)) == AIR for x, z in entrance_columns for y in range(99, 103)) - assert all((x, z) in footprint for (x, y, z) in states if y == 98) - assert all(states[x, 112, z] != AIR and states[x, 125, z] != AIR for x, z in footprint) - assert {p for p, state in states.items() if state == AIR} == {tuple(p) for p in entrance_air} - solid = {p for p, state in states.items() if state != AIR} - remaining = set(solid) - queue = [remaining.pop()] - while queue: - x, y, z = queue.pop() - for p in ((x + 1, y, z), (x - 1, y, z), (x, y + 1, z), - (x, y - 1, z), (x, y, z + 1), (x, y, z - 1)): - if p in remaining: - remaining.remove(p) - queue.append(p) - assert not remaining, "Exterior contains detached floating ornament" - metadata = { - "generator": "station-exterior-v1", "world_writes": 0, - "foundation_columns": len(footprint), "two_block_wall_columns": len(walls), - "bounds": {"min": [min(p[i] for p in states) for i in range(3)], - "max": [max(p[i] for p in states) for i in range(3)]}, - "floor_block_y": [98, 112], "walk_y": [99, 113], - "solid_intermediate_deck": [111, 112], "sealed_roof_ceiling": [124, 125], - "main_roof_eaves_y": 126, "main_roof_ridge_y": 140, "clocktower_peak_y": 158, - "entrance_clear_x": list(entrance), "entrance_clear_z": [-85, -84, -83], - "entrance_minimum_clear_height": 8, "windows": windows, "pilasters": pilasters, - "facade_lamps": facade_lamps, "pavilions": [list(p) for p in pavilions], - "clock_faces": [{"facing": f, "center_or_x": c, "plane_or_z": p, - "center_y": 139, "radius": 8} for f, c, p in clock_faces], - "materials": sorted({state.split("[")[0] for state in states.values()}), - "states_by_group": dict(sorted(Counter(groups.values()).items())), - "blocks": len(states), "entrance_air_blocks": len(entrance_air), - "checks": {"ground_floor_on_exact_footprint": True, "two_solid_separating_decks": True, - "only_main_entrance_is_open": True, "roof_and_tower_have_no_doorway": True, - "maximum_overhang_blocks": 3, "all_windows_full_block_glass": True, - "all_solid_states_share_one_cardinally_connected_component": True}, - } - return states, groups, metadata diff --git a/scripts/station-interior.py b/scripts/station-interior.py deleted file mode 100644 index 250138c..0000000 --- a/scripts/station-interior.py +++ /dev/null @@ -1,514 +0,0 @@ -#!/usr/bin/env python3 -"""Compile the two furnished Shacraft station floors without world I/O. - -The caller owns the exterior, authoritative survey and expected-state writes. -This module owns only the two-block-eroded footprint at block Y 98..123. -All six SMASH panels are decorative, unassigned slots. Functional signs, text -displays and the individual lift transition are deliberately metadata only. -""" - -from collections import Counter, deque - - -AIR = "minecraft:air" -CREAM = "minecraft:smooth_sandstone" -PALE = "minecraft:smooth_quartz" -CUT = "minecraft:cut_sandstone" -GREEN = "minecraft:waxed_oxidized_cut_copper" -GOLD = "minecraft:gold_block" -WOOD = "minecraft:spruce_planks" -DARK = "minecraft:dark_oak_planks" -CHAIN = "minecraft:iron_chain[axis=y,waterlogged=false]" -LANTERN = "minecraft:lantern[hanging=true,waterlogged=false]" -LEAF = "minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false]" -LOG = "minecraft:spruce_log[axis=y]" - - -def _slab(material, top=False): - return f"minecraft:{material}[type={'top' if top else 'bottom'},waterlogged=false]" - - -def _stair(material, facing, top=False): - return (f"minecraft:{material}[facing={facing},half={'top' if top else 'bottom'}," - "shape=straight,waterlogged=false]") - - -def _rect(box): - a, b, c, d = map(int, box) - return {(x, z) for x in range(a, b + 1) for z in range(c, d + 1)} - - -def _pose(x, y, z, text, facing="south", scale=0.7, **extra): - return {"position": {"x": x, "y": y, "z": z}, "text": text, - "facing": facing, "scale": scale, **extra} - - -class _Interior: - def __init__(self, footprint, layout): - self.footprint = {(int(x), int(z)) for x, z in footprint} - self.inside = {(x, z) for x, z in self.footprint - if all((x + dx, z + dz) in self.footprint - for dx in range(-2, 3) for dz in range(-2, 3))} - if not self.inside: - raise ValueError("The station needs an interior after its two-block setback") - self.layout = layout - self.states = {} - self.groups = {} - self.features = [] - self.texts = [] - self.targets = {99: [], 113: []} - self.selections = [] - self.lift = {} - self.floor_walk = {} - - def put(self, x, y, z, state, group): - p = (int(x), int(y), int(z)) - if (p[0], p[2]) not in self.inside or not 98 <= p[1] <= 123: - raise ValueError(f"Interior write outside the owned volume: {p} ({group})") - self.states[p] = state - self.groups[p] = group - - def box(self, bounds, lo, hi, state, group, clipped=False): - for x, z in sorted(_rect(bounds)): - if clipped and (x, z) not in self.inside: - continue - for y in range(lo, hi + 1): - self.put(x, y, z, state, group) - - def target(self, feet, x, z, name): - self.targets[feet].append({"name": name, "x": x, "y": feet, "z": z}) - - def record(self, kind, bounds, feet, **extra): - self.features.append({"kind": kind, "bounds_xz": list(bounds), - "walk_y": feet, **extra}) - - def shell(self): - for x, z in sorted(self.inside): - for y in range(98, 124): - if y in (98, 112): - state, group = CREAM, "interior:continuous-floor" - elif y == 111: - state, group = "minecraft:stone_bricks", "interior:solid-intermediate-deck" - else: - state, group = AIR, "interior:owned-room-air" - self.put(x, y, z, state, group) - - def floors(self, feet): - floor = feet - 1 - group = f"interior:{feet}:floor-inlay" - # Large calm limestone panels, outlined by the architectural column grid. - for x, z in sorted(self.inside): - if x in (-51, -46, -21, -16, 15, 20) or z in (-129, -124, -106, -101): - self.put(x, floor, z, PALE, group) - if x in (-50, -17, 19) and -135 <= z <= -93: - self.put(x, floor, z, CUT, group) - for x in (-11, -10, -2, -1): - for z in range(-115, -84): - if (x, z) in self.inside: - self.put(x, floor, z, GREEN if x in (-10, -2) else PALE, group) - for z in (-111, -103, -95, -87): - for x in (-10, -2): - if (x, z) in self.inside: - self.put(x, floor, z, GOLD, group) - centres = [(-59, -117), (25, -117), (-34, -103), (5, -99)] - if feet == 99: - centres += [(-33, -117), (-31, -135), (5, -135)] - for cx, cz in centres: - for dx in range(-5, 6): - for dz in range(-5, 6): - if (cx + dx, cz + dz) not in self.inside: - continue - r = abs(dx) + abs(dz) - if r == 5: - state = GREEN - elif r == 4: - state = PALE - elif r <= 2 and (dx == 0 or dz == 0): - state = GOLD if r == 0 else CUT - else: - continue - self.put(cx + dx, floor, cz + dz, state, group) - if feet == 113: - # A terracotta runner belongs to SMASH, while the centre axis stays pale. - for x, z in sorted(_rect([-46, 11, -134, -130])): - self.put(x, floor, z, "minecraft:orange_terracotta", group) - for x in range(-46, 12): - for z in (-135, -129): - self.put(x, floor, z, PALE if x % 10 else GOLD, group) - for x, z in sorted(_rect([22, 29, -124, -112])): - edge = x in (22, 29) or z in (-124, -112) - self.put(x, floor, z, GREEN if edge else "minecraft:orange_terracotta", group) - - def arcade(self, feet, ceiling): - group = f"interior:{feet}:arcade" - panel_y = ceiling - 1 - # A solid coffer ceiling closes the upper floor below the inaccessible attic. - for x, z in sorted(self.inside): - self.put(x, panel_y, z, WOOD, group) - if z in (-139, -128, -125, -105, -102, -91) or x in (-50, -47, -20, -17, 16, 19): - self.put(x, panel_y, z, DARK, group) - if z in (-128, -125, -105, -102): - self.put(x, panel_y - 1, z, CREAM, group) - for bounds in self.layout["aligned_columns"]: - a, b, c, d = bounds - self.box(bounds, feet, feet, CUT, group) - self.box(bounds, feet + 1, feet + 1, PALE, group) - self.box(bounds, feet + 2, ceiling - 4, - "minecraft:quartz_pillar[axis=y]", group) - self.box(bounds, ceiling - 3, ceiling - 1, CREAM, group) - self.box([a - 1, b + 1, c - 1, d + 1], ceiling - 3, ceiling - 3, - _slab("smooth_sandstone_slab", top=True), group, clipped=True) - self.box([a - 1, b + 1, c - 1, d + 1], ceiling - 2, ceiling - 2, - CREAM, group, clipped=True) - self.record("aligned-column", bounds, feet) - # High corbels make the two column rows read as a shallow cream arcade. - for bounds in self.layout["aligned_columns"]: - a, b, c, d = bounds - for x, facing in ((a - 2, "east"), (b + 2, "west")): - for z in (c, d): - if (x, z) in self.inside: - self.put(x, ceiling - 3, z, - _stair("smooth_sandstone_stairs", facing, top=True), group) - - def chandelier(self, x, z, feet, ceiling): - group = f"interior:{feet}:chandelier" - collar = ceiling - 4 - for y in range(collar + 1, ceiling): - self.put(x, y, z, CHAIN, group) - self.put(x, collar, z, GOLD, group) - for dx, dz in ((-1, 0), (1, 0), (0, -1), (0, 1)): - self.put(x + dx, collar, z + dz, GREEN, group) - self.put(x + dx, collar - 1, z + dz, LANTERN, group) - self.record("four-lantern-chandelier", [x - 1, x + 1, z - 1, z + 1], - feet, lowest_block_y=collar - 1) - - def bench(self, bounds, feet, facing="south", name="bench"): - a, b, c, d = bounds - if b - a < 3 or d - c != 1: - raise ValueError("Station benches use a long, two-block-deep rectangle") - group = f"interior:{feet}:bench" - back_z, seat_z = (c, d) if facing == "south" else (d, c) - stair_facing = "north" if facing == "south" else "south" - for x in range(a, b + 1): - if x in (a, b): - for z in (c, d): - self.put(x, feet, z, CUT, group) - self.put(x, feet + 1, z, _slab("smooth_sandstone_slab"), group) - else: - self.put(x, feet, seat_z, _stair("spruce_stairs", stair_facing), group) - self.put(x, feet, back_z, WOOD, group) - self.put(x, feet + 1, back_z, _slab("spruce_slab"), group) - front = d + 1 if facing == "south" else c - 1 - for x in range(a + 1, b): - self.target(feet, x, front, f"{name}-access-{x}") - self.record("spruce-bench", bounds, feet, facing=facing) - - def planter(self, x, z, feet, name="topiary"): - group = f"interior:{feet}:planter" - bounds = [x - 1, x + 1, z - 1, z + 1] - self.box(bounds, feet, feet, CUT, group) - self.put(x, feet, z, "minecraft:dirt", group) - self.put(x, feet + 1, z, LOG, group) - self.put(x, feet + 2, z, LOG, group) - for dx, dz in ((-1, 0), (0, -1), (0, 0), (0, 1), (1, 0)): - self.put(x + dx, feet + 3, z + dz, LEAF, group) - self.put(x, feet + 4, z, LEAF, group) - self.record(name, bounds, feet) - - def lift_cabin(self, feet, ceiling): - group = f"interior:{feet}:lift" - housing = self.layout["lift"]["housing"] - cabin = self.layout["lift"]["clear_cabin"] - inner = _rect(cabin) - a, b, c, d = housing - for x, z in sorted(_rect(housing)): - if (x, z) in inner: - self.put(x, feet - 1, z, "minecraft:polished_andesite", group) - continue - for y in range(feet, ceiling): - state = GREEN - if x in (a, b) and z in (c, d): - state = CUT if y in (feet, feet + 5) else CREAM - if y == feet + 5 and z == d: - state = GOLD - self.put(x, y, z, state, group) - # The unused volume above each cabin is solid, never an open shaft. - self.box(cabin, feet + 7, ceiling - 1, WOOD, group) - self.box(cabin, feet, feet + 6, AIR, group) - self.box([-8, -4, -117, -116], feet, feet + 4, AIR, group) - self.box([-8, -4, -117, -116], feet + 5, feet + 5, GOLD, group) - for x in (-8, -4): - for z in range(-122, -117): - self.put(x, feet - 1, z, GREEN, group) - self.put(-6, feet + 6, -120, CHAIN, group) - self.put(-6, feet + 5, -120, LANTERN, group) - selector = (-6, feet + 2, -123) - self.put(*selector, GOLD, group) - number = 1 if feet == 99 else 2 - self.lift[str(number)] = { - "housing_bounds_xz": housing, "clear_cabin_bounds_xz": cabin, - "walk_y": feet, "door_bounds_xz": [-8, -4, -117, -116], - "door_clear_height": 5, "selector_block": list(selector), - "destination": {"x": -5.5, "y": 113 if feet == 99 else 99, - "z": -119.5, "yaw": 0, "pitch": 0}, - "landing_block": [-6, feet, -120], "floor_is_solid": True, - } - self.texts.append(_pose(-5.5, feet + 6.2, -114.93, - "1 ВЕСТИБЮЛЬ" if number == 1 else "2 SMASH", - scale=0.9, id=f"lift-{number}-front")) - self.texts.append(_pose(-5.5, feet + 3.3, -121.92, - "Вверх · SMASH" if number == 1 else "Вниз · Вестибюль", - scale=0.45, id=f"lift-{number}-selector")) - self.target(feet, -6, -120, f"lift-{number}-landing") - self.target(feet, -6, -114, f"lift-{number}-approach") - self.record("enclosed-lift-cabin", housing, feet, floor_number=number) - - def gallery_panel(self, x, z, feet, title, subtitle, facing="east"): - group = f"interior:{feet}:gallery-panel" - # East-facing panels stand along the western wall, clear of the arcade. - for dz in range(-2, 3): - for dy in range(0, 6): - state = CUT if abs(dz) == 2 or dy in (0, 5) else DARK - self.put(x, feet + dy, z + dz, state, group) - for dz in (-2, 2): - self.put(x, feet + 2, z + dz, GOLD, group) - self.texts.append(_pose(x + 1.04, feet + 3.9, z + 0.5, - title + "\n" + subtitle, facing=facing, scale=0.52, - id=f"gallery-{feet}-{x}-{z}")) - self.target(feet, x + 3, z, title) - self.record("rules-panel" if feet == 113 else "station-gallery-panel", - [x, x, z - 2, z + 2], feet) - - def diorama(self): - feet = 113 - a, b, c, d = self.layout["smash_display"] - group = "interior:113:contained-island-diorama" - self.box([a, b, c, d], feet, feet, "minecraft:black_concrete", group) - for x, z in sorted(_rect([a, b, c, d])): - if x in (a, b) or z in (c, d): - self.put(x, feet, z, CUT, group) - for y in (feet + 1, feet + 2): - self.put(x, y, z, "minecraft:glass", group) - self.put(x, feet + 3, z, _slab("smooth_sandstone_slab"), group) - # Miniatures hover above an opaque, intact display base, inside a glass case. - for ix, (cx, cz, radius) in enumerate(((-38, -115, 2), (-29, -115, 2), (-34, -120, 2))): - self.put(cx, 114, cz, "minecraft:deepslate[axis=y]", group) - for dx in range(-radius, radius + 1): - for dz in range(-radius, radius + 1): - dist = abs(dx) + abs(dz) - if dist <= 2: - self.put(cx + dx, 115, cz + dz, "minecraft:stone", group) - if dist <= 3: - self.put(cx + dx, 116, cz + dz, "minecraft:moss_block", group) - self.put(cx, 117, cz, LOG, group) - self.put(cx, 118, cz, LOG, group) - for dx, dz in ((-1, 0), (0, -1), (0, 0), (0, 1), (1, 0)): - self.put(cx + dx, 119, cz + dz, LEAF, group) - self.put(cx, 120, cz, LEAF, group) - self.put(cx + (1 if ix != 1 else -1), 117, cz + 1, - "minecraft:red_concrete" if ix == 1 else "minecraft:light_blue_concrete", group) - self.texts.append(_pose((a + b) / 2 + 0.5, 115, d + 1.05, - "SMASH · УДЕРЖИСЬ НА ОСТРОВЕ", scale=0.6, - id="smash-diorama-caption")) - self.target(feet, -33, d + 2, "diorama-south-view") - self.target(feet, a - 2, -117, "diorama-west-view") - self.target(feet, b + 2, -117, "diorama-east-view") - self.record("closed-display-only-diorama", [a, b, c, d], feet, - enclosed=True, arena=False, floor_intact=True, top_y=120) - - def selection_bays(self): - feet = 113 - group = "interior:113:smash-selection-bays" - # Solid infill behind the recessed panels prevents unfinished rear rooms. - for x, z in sorted(self.inside): - if -47 <= x <= 13 and z <= -140: - for y in range(feet, 124): - self.put(x, y, z, CREAM, group) - for index, bounds in enumerate(self.layout["smash_selection_bays"], 1): - a, b, c, d = bounds - mid = (a + b) // 2 - for x in range(a, b + 1): - for y in range(feet, feet + 8): - self.put(x, y, c, DARK, group) - for x in (a, b): - for z in range(c + 1, d + 1): - for y in range(feet, feet + 7): - self.put(x, y, z, CUT if y == feet else CREAM, group) - self.box([a, b, c + 1, d], feet + 7, feet + 7, CREAM, group) - self.box([a + 1, b - 1, c + 1, c + 1], feet + 1, feet + 6, GOLD, group) - # Five-wide miniature reliefs are explicitly generic, unassigned previews. - for dx in range(-2, 3): - for dy in range(4): - material = "light_blue_concrete" - summit = 1 + ((dx + index) % 3 == 0) - if dy < summit: - material = "stone" if dy == 0 else "moss_block" - if dy == 3 and dx == (index % 3) - 1: - material = "white_concrete" - self.put(mid + dx, feet + 2 + dy, c + 2, - "minecraft:" + material, group) - for x in range(mid - 1, mid + 2): - self.put(x, feet, d, CUT, group) - self.put(x, feet + 1, d, WOOD, group) - self.put(mid, feet + 6, c + 2, LANTERN, group) - self.put(mid, feet + 7, c + 2, GREEN, group) - sign = [mid, feet + 1, d + 1] - support = [mid, feet + 1, d] - self.selections.append({ - "slot": f"{index:02d}", "bounds_xz": bounds, - "assigned": False, "destination": None, - "relief_is_decorative": True, - "sign_block": sign, "sign_support_block": support, - "sign_facing": "south", "sign_lines": [f"АРЕНА {index:02d}", "Не назначено", "", ""], - "standing_block": [mid, feet, d + 2], - "label_pose": _pose(mid + 0.5, feet + 6.6, d + 1.05, - f"{index:02d}", scale=0.55), - }) - self.target(feet, mid, d + 2, f"arena-{index:02d}-sign") - self.record("unassigned-arena-selection-bay", bounds, feet, slot=index) - self.box([-35, 1, -139, -138], 121, 123, DARK, group) - self.texts.append(_pose(-16.5, 122.2, -136.94, "S M A S H", scale=2.2, - id="smash-header")) - - def furniture(self, feet): - for i, bounds in enumerate(self.layout["wing_benches"]): - self.bench(bounds, feet, "south", f"wing-bench-{i + 1}") - for x, z in ((-56, -131), (-56, -106), (21, -131), (21, -106)): - self.planter(x, z, feet) - # The north and south galleries are furnished public rooms, without doors - # suggesting additional unfinished wings or upper-floor circulation. - for i, (bounds, facing) in enumerate((([-22, -15, -94, -93], "north"), - ([0, 7, -94, -93], "north"))): - self.bench(bounds, feet, facing, f"south-gallery-bench-{i + 1}") - for x, z in ((-30, -94), (12, -94)): - if _rect([x - 1, x + 1, z - 1, z + 1]) <= self.inside: - self.planter(x, z, feet) - if feet == 99: - for i, bounds in enumerate(([-44, -37, -137, -136], [-26, -19, -137, -136], - [1, 8, -137, -136])): - self.bench(bounds, feet, "south", f"north-gallery-bench-{i + 1}") - for x in (-31, 11): - self.planter(x, -137, feet) - self.gallery_panel(-72, -116, feet, "SHACRAFT", "Площадь прибытия") - self.texts.append(_pose(-5.5, 108, -114.93, - "SHACRAFT", scale=0.9, - id="vestibule-heading")) - else: - for z, title, subtitle in ((-122, "УРОН", "Больше урона — сильнее отбрасывание"), - (-115, "ОТБРАСЫВАНИЕ", "Удержись на острове"), - (-108, "ДВОЙНОЙ ПРЫЖОК", "Вернись на площадку")): - self.gallery_panel(-72, z, feet, title, subtitle) - self.texts.append(_pose(26, 118.5, -105.9, "ЗОНА ОЖИДАНИЯ", scale=0.7, - id="smash-waiting-heading")) - - def leaf_distances(self): - leaves = {p for p, s in self.states.items() if s.startswith("minecraft:spruce_leaves[")} - distance = {} - queue = deque() - for p, s in self.states.items(): - if s.startswith("minecraft:spruce_log["): - queue.append((p, 0)) - while queue: - (x, y, z), d = queue.popleft() - if d >= 6: - continue - for p in ((x - 1, y, z), (x + 1, y, z), (x, y - 1, z), - (x, y + 1, z), (x, y, z - 1), (x, y, z + 1)): - if p in leaves and d + 1 < distance.get(p, 7): - distance[p] = d + 1 - queue.append((p, d + 1)) - for p in leaves: - self.states[p] = (f"minecraft:spruce_leaves[distance={distance.get(p, 7)}," - "persistent=true,waterlogged=false]") - - def navigation(self): - details = {} - for feet, seed in ((99, (-6, -86)), (113, (-6, -120))): - clear = {(x, z) for x, z in self.inside - if all(self.states[(x, y, z)] == AIR for y in range(feet, feet + 4))} - if seed not in clear: - raise ValueError(f"Interior navigation start is obstructed at {feet}: {seed}") - reached = {seed} - queue = deque([seed]) - while queue: - x, z = queue.popleft() - for p in ((x - 1, z), (x + 1, z), (x, z - 1), (x, z + 1)): - if p in clear and p not in reached: - reached.add(p) - queue.append(p) - for target in self.targets[feet]: - if (target["x"], target["z"]) not in reached: - raise ValueError(f"Unreachable interior target: {target}") - # Close genuine inaccessible air pockets; never leave a half-built room. - sealed = clear - reached - for x, z in sorted(sealed): - for y in range(feet, 111 if feet == 99 else 124): - self.put(x, y, z, CREAM, f"interior:{feet}:sealed-service-infill") - reserved = [self.layout["reserved_clear_aisles"]["entry"]] - if feet == 113: - reserved.append(self.layout["reserved_clear_aisles"]["bay_front"]) - for bounds in reserved: - required = _rect(bounds) & self.inside - blocked = required - reached - if blocked: - raise ValueError(f"Reserved circulation is blocked at Y{feet}: {sorted(blocked)[:5]}") - self.floor_walk[feet] = [{"x": x, "y": feet, "z": z} - for x, z in sorted(reached)] - details[str(feet)] = {"walk_columns": len(reached), - "minimum_verified_headroom": 4, - "named_targets": self.targets[feet], - "sealed_inaccessible_columns": len(sealed), - "all_named_targets_reachable": True, - "reserved_aisles_clear": True} - return details - - -def compile_interior(footprint, layout): - """Return canonical voxel states, per-voxel groups, and JSON-safe metadata. - - ``footprint`` contains the actual surveyed foundation's (x, z) columns. - Geometry is deterministic and absolute, as specified by the approved layout. - The returned air states are intentional room clearance; callers must apply - the normal expected-state and protected-volume checks before any live write. - """ - b = _Interior(footprint, layout) - floors = [(int(f["walk_y"]), int(f["ceiling_underside_y"])) for f in layout["floors"]] - if floors != [(99, 111), (113, 124)]: - raise ValueError("This approved interior is defined only for walk Y99 and Y113") - b.shell() - for feet, ceiling in floors: - b.floors(feet) - b.arcade(feet, ceiling) - b.furniture(feet) - lights = [(-58, -116), (25, -116), (5, -107), (-32, -101)] - if feet == 99: - lights += [(-33, -116), (-30, -132)] - for x, z in lights: - b.chandelier(x, z, feet, ceiling) - b.lift_cabin(feet, ceiling) - b.diorama() - b.selection_bays() - b.leaf_distances() - navigation = b.navigation() - palette = sorted({s.partition("[")[0] for s in b.states.values()}) - metadata = { - "schema_version": 1, - "name": "Shacraft station furnished vestibule and SMASH gallery", - "status": "compiled candidate; requires caller survey, collision and live verification", - "owned_y": [98, 123], "perimeter_setback_blocks": 2, - "foundation_columns": len(b.footprint), "interior_columns": len(b.inside), - "state_count": len(b.states), "non_air_count": sum(s != AIR for s in b.states.values()), - "group_counts": dict(sorted(Counter(b.groups.values()).items())), - "palette": palette, "features": b.features, "lift": b.lift, - "selection_bays": b.selections, "text_displays": b.texts, - "walk_points": b.floor_walk[99] + b.floor_walk[113], - "walk_points_by_floor": {str(k): v for k, v in b.floor_walk.items()}, - "navigation": navigation, - "design_invariants": { - "only_two_furnished_floors": True, "upper_floor_has_no_holes": True, - "lift_has_no_open_shaft": True, "no_attic_or_clocktower_access": True, - "diorama_is_closed_and_display_only": True, "arena_slots_are_unassigned": True, - "functional_text_signs_and_lift_are_caller_owned": True, - }, - } - return b.states, b.groups, metadata diff --git a/scripts/terrain-study/NaturalTerrainStudy.java b/scripts/terrain-study/NaturalTerrainStudy.java deleted file mode 100644 index b483344..0000000 --- a/scripts/terrain-study/NaturalTerrainStudy.java +++ /dev/null @@ -1,19 +0,0 @@ -import com.google.gson.JsonParser; -import io.github.minecraftbuilder.core.TerrainRecipe; -import java.io.*; -import java.nio.file.*; - -/** Offline composition experiment, not a world writer or a production recipe. */ -public final class NaturalTerrainStudy { - private static final io.github.minecraftbuilder.terrainworld.NaturalTerrain terrain = - new io.github.minecraftbuilder.terrainworld.NaturalTerrain(28092005); - static float height(int x,int z) { return terrain.height(x,z); } - public static void main(String[] args) throws Exception { - var old=new TerrainRecipe(JsonParser.parseString(Files.readString(Path.of(args[0]))).getAsJsonObject()); - Path output=Path.of(args[1]);Files.createDirectories(output); - try(var a=new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(output.resolve("before.f32")))); - var b=new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(output.resolve("natural.f32"))))) { - for(int z=-384;z<384;z++)for(int x=-384;x<384;x++) {a.writeFloat(old.surfaceHeight(x,z));b.writeFloat(height(x,z));} - } - } -} diff --git a/scripts/terrain-study/README.md b/scripts/terrain-study/README.md deleted file mode 100644 index 1589294..0000000 --- a/scripts/terrain-study/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Shacraft terrain composition study - -This is an **offline prototype** responding to the first lobby terrain's rectangular platforms and repetitive slopes. The original study did not alter a live world. Its exact height field is now available through the production `shacraft-natural-v1` initial-world profile; it still does not extend the production MCP recipe schema. - -## Findings and design decisions - -The original recipe combines three correlated octaves of grid-based value noise, then flattens large rectangular areas. It also paints grass on every exposed top step regardless of slope. The screenshots show both geometric platform edges and repetitive green/brown contour bands. Adding more octaves alone cannot remove those platform boundaries. - -[Red Blob Games](https://www.redblobgames.com/maps/terrain-from-noise/) explains independent octave sampling, Simplex noise as a way to reduce directional artifacts, ridged transformations, and elevation redistribution. The study uses seeded OpenSimplex2S with distinct fields for broad relief, ridges, coordinate warping and fine detail. - -[libnoise tutorial 5](https://libnoise.sourceforge.net/tutorials/tutorial5.html) separates the terrain-type control map from mountain and lowland height fields. Here explicit curved mountain corridors provide artistic control: north is the main skyline, side ranges are lower, and the central valley stays comparatively calm. Mountain detail is weighted by those corridors instead of covering the entire map uniformly. - -[FastNoiseLite's documentation](https://github.com/Auburn/FastNoiseLite/wiki/Documentation) provides fBm, ridged fractals, octave weighting and domain warping. The prototype uses two independent low-frequency fields to displace X/Z coordinates before evaluating terrain, producing irregular ridge paths and water margins. This is a modest 27-block warp, not unlimited distortion. - -There are no rectangular plateau features in the study. Broad smooth hills leave space for architecture without committing to enormous flat pads. Later construction should flatten only the actual footprint and blend the foundation into the slope. Natural terrain alone does not guarantee walkable routes; those need a separate route/grade pass after the silhouette is selected. - -The lakes and connected river paths share Y=48. Shorelines arise from the intersection between the computed ground and that water plane. River cuts are curved and variable in width. The shape is authored and noise-modulated; it is **not** a drainage or hydraulic erosion simulation. Small remaining angular changes around path vertices should be replaced by spline sampling in a production implementation. - -## Prototype parameters - -- Broad lowland field: scale 220 blocks, amplitude 11, 4 octaves. -- Mountain ridge field: scale 85, 5 octaves, gain 0.48, lacunarity 2.07, weighted strength 0.7. -- Warp fields: scale 165, 3 octaves, displacement 27 blocks. -- Fine detail: scale 17, amplitude about 1.2–4.4 depending on mountain influence. -- Comparison palette: slope-dependent rock/grass and depth-dependent water, **identical for both versions**. This isolates geometry; it is not a Minecraft material or shader preview. - -## Reproduce - -From the repository root, with the project Java 25 runtime on PATH: - -```bash -mkdir -p .runtime/terrain-study -javac -cp terrain-world-plugin/target/terrain-world-plugin-0.1.0-SNAPSHOT.jar -d .runtime/terrain-study \ - scripts/terrain-study/NaturalTerrainStudy.java -java -cp .runtime/terrain-study:terrain-world-plugin/target/terrain-world-plugin-0.1.0-SNAPSHOT.jar \ - NaturalTerrainStudy examples/terrain/shacraft-lobby-world.json .runtime/terrain-study -python3 scripts/terrain-study/render.py -``` - -Rendering requires numpy and matplotlib. The binary samples are 768×768 big-endian float32, row-major Z then X. No upsampling, erosion or post-sculpting is hidden in the renderer. Perspective views sample every fourth column and use true vertical scale. - -Outputs: `docs/references/shacraft-terrain-study-plan.png`, `shacraft-terrain-study-perspective.png`, and `shacraft-terrain-study.json`. These are computed previews, **not screenshots**. The original comparison images predate the version 2 world. The production height field is tested against the SHA-256 of all 589,824 original prototype float samples. - -## Third-party source - -`../../terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/noise/FastNoiseLite.java` is upstream Java source with only a package declaration added from [Auburn/FastNoiseLite](https://github.com/Auburn/FastNoiseLite), pinned to commit `785f37a9ad76e283586a379675085f2063ae03f7`. Its MIT license and copyright notice are preserved at the top of the file. The MIT notice is also bundled in the production jar under `META-INF/LICENSE-FastNoiseLite.txt`. diff --git a/scripts/terrain-study/render.py b/scripts/terrain-study/render.py deleted file mode 100644 index 76b1d9c..0000000 --- a/scripts/terrain-study/render.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Render comparable scientific previews from sampled height fields; never edits screenshots.""" -from pathlib import Path -import json -import numpy as np -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -from matplotlib.colors import LightSource - -ROOT = Path(__file__).resolve().parents[2] -DATA = ROOT / '.runtime/terrain-study' -OUT = ROOT / 'docs/references' -WATER = 48 -fields = [np.fromfile(DATA / name, dtype='>f4').reshape(768, 768).astype(float) - for name in ['before.f32', 'natural.f32']] -names = ['V1 — rectangular platforms', 'Study — ridges, soft hills, winding water'] - -def colors(ground): - dz, dx = np.gradient(ground) - slope = np.hypot(dx, dz) - rock = np.clip((slope - .55) / 1.5, 0, 1)[..., None] - green = np.array([.35, .46, .25]) - stone = np.array([.54, .54, .49]) - rgb = green * (1-rock) + stone * rock - # Identical materials and lighting for both fields: isolate the shape comparison. - shaded = LightSource(315, 42).shade_rgb(rgb, ground, vert_exag=1, blend_mode='soft') - depth = np.clip((WATER-ground)/25, 0, 1)[..., None] - water = np.array([.19, .46, .50])*(1-depth) + np.array([.12, .30, .37])*depth - return np.where((ground < WATER)[..., None], water, shaded) - -fig, axes = plt.subplots(1, 2, figsize=(16, 8), facecolor='#f4f3ee') -for ax, ground, name in zip(axes, fields, names): - ax.imshow(colors(ground), extent=(-384,384,384,-384)) - ax.set_title(name, fontsize=16, loc='left', pad=16) - ax.set_xlabel('X / blocks'); ax.set_ylabel('Z / blocks — north up') -fig.suptitle('Shacraft / terrain composition study · 768 × 768 blocks', fontsize=20, x=.06, ha='left', y=.99) -fig.text(.06,.025,'Computed height fields. Same scale, water level and shading. Right: offline prototype, not yet in Minecraft.',fontsize=11) -fig.subplots_adjust(left=.06,right=.98,top=.90,bottom=.10,wspace=.18) -fig.savefig(OUT/'shacraft-terrain-study-plan.png',dpi=130) -plt.close(fig) - -fig=plt.figure(figsize=(16,8),facecolor='#f4f3ee') -for i,(ground,name) in enumerate(zip(fields,names)): - ax=fig.add_subplot(1,2,i+1,projection='3d',facecolor='#f4f3ee') - s=4; x=np.arange(-384,384,s); z=np.arange(-384,384,s); xx,zz=np.meshgrid(x,z) - ax.plot_surface(xx,zz,np.maximum(ground[::s,::s],WATER),facecolors=colors(ground)[::s,::s], - rstride=1,cstride=1,linewidth=0,antialiased=False,shade=False) - ax.set(xlim=(-384,384),ylim=(-384,384),zlim=(25,220)) - ax.set_box_aspect((768,768,195));ax.view_init(elev=37,azim=-58);ax.set_axis_off() - ax.set_title(name,loc='left',fontsize=16,pad=0) -fig.suptitle('Shacraft / compare silhouettes before rebuilding',fontsize=20,x=.05,ha='left',y=.95) -fig.text(.05,.07,'Geometric preview at true vertical scale. No rectangular plateaus in the new study. No erosion simulation.',fontsize=11) -fig.subplots_adjust(left=.01,right=.99,top=.86,bottom=.10,wspace=-.06) -fig.savefig(OUT/'shacraft-terrain-study-perspective.png',dpi=130) -plt.close(fig) - -stats=[] -for ground,name in zip(fields,names): - dz,dx=np.gradient(ground);s=np.hypot(dx,dz) - stats.append({'name':name,'min_ground_y':float(ground.min()),'max_ground_y':float(ground.max()), - 'water_columns':int((groundI', len(data)) + kind + data + struct.pack('>I', zlib.crc32(kind + data) & 0xffffffff) + + +PNG = b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', 1, 1, 8, 2, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(b'\0\0\0\0')) + chunk(b'IEND', b'') +TOKEN = 'private-test-token-' * 3 +CAPTURE_ID = '11111111-1111-4111-8111-111111111111' +POSE = dict(x=0, y=100, z=0, yaw=0, pitch=30, dimension='minecraft:overworld', fov=80, width=1280, height=720) + + +class CameraCaptureTest(unittest.TestCase): + def reply(self): + return dict(status='completed', captureId=CAPTURE_ID, mimeType='image/png', dimension=POSE['dimension'], + capturedAt=datetime.now(timezone.utc).isoformat(), width=1, height=1, imageBase64=base64.b64encode(PNG).decode()) + + def test_invalid_request_fails_before_config_or_transport(self): + for change in ({'x': float('nan')}, {'pitch': 91}, {'yaw': -361}, {'y': 2049}, {'fov': 29}, {'width': True}, {'dimension': 'https://example.com'}, {'x': '0'}): + with self.subTest(change=change), patch.object(camera.checks, 'request_json') as request: + with self.assertRaises(camera.checks.TestFailure): + camera.capture('/does/not/exist', POSE | change, '/unused') + request.assert_not_called() + + def test_raw_image_is_unchanged_and_metadata_allowlisted(self): + result = self.reply() | {'arbitrary': {'secret': TOKEN}} + raw, receipt = camera.validated_image(result, CAPTURE_ID, POSE, TOKEN, time.time()) + self.assertEqual(raw, PNG) + metadata = json.loads(receipt) + self.assertTrue(metadata['operator_direct_worker']) + self.assertEqual(metadata['imageBytes'], len(PNG)) + self.assertNotIn('arbitrary', metadata) + self.assertNotIn('imageBase64', metadata) + + def test_wrong_identity_dimension_timestamp_and_size_rejected(self): + for change in ({'captureId': '22222222-2222-4222-8222-222222222222'}, {'dimension': 'minecraft:the_nether'}, {'width': 2}, {'mimeType': 'image/jpeg'}, {'capturedAt': '2020-01-01T00:00:00Z'}): + with self.subTest(change=change), self.assertRaises(camera.checks.TestFailure): + camera.validated_image(self.reply() | change, CAPTURE_ID, POSE, TOKEN, time.time()) + + def test_bad_png_and_private_metadata_rejected(self): + corrupt = bytearray(PNG);corrupt[30] ^= 1 + for change in ({'imageBase64': 'bad!'}, {'imageBase64': base64.b64encode(corrupt).decode()}, {'readiness': TOKEN}, {'readiness': {}}, {'eyeY': float('nan')}): + with self.subTest(change=change), self.assertRaises(camera.checks.TestFailure): + camera.validated_image(self.reply() | change, CAPTURE_ID, POSE, TOKEN, time.time()) + + def test_polled_capture_cannot_switch_identity(self): + with tempfile.TemporaryDirectory() as temp: + root = Path(temp);config = root / 'config.yml';output = root / 'frame.png' + config.write_text(f'camera-token: {TOKEN}\ncamera-port: 8766\n') + responses = [dict(status='pending', captureId=CAPTURE_ID), self.reply() | {'captureId': '22222222-2222-4222-8222-222222222222'}] + with patch.object(camera.checks, 'request_json', side_effect=responses), patch.object(camera.time, 'sleep'): + with self.assertRaises(camera.checks.TestFailure):camera.capture(config, POSE, output) + self.assertFalse(output.exists()) + + def test_oversized_and_nonobject_http_response_rejected(self): + class Response: + def __init__(self, raw):self.raw=raw + def __enter__(self):return self + def __exit__(self, *args):pass + def read(self, limit):return self.raw[:limit] + class Opener: + def __init__(self, raw):self.raw=raw + def open(self, *args, **kwargs):return Response(self.raw) + for raw in (b'[]', b'x' * (camera.checks.MAX_RESPONSE + 1)): + with self.assertRaises(camera.checks.TestFailure): + camera.checks.request_json(Opener(raw), 'http://127.0.0.1:8766/health', TOKEN) + + +if __name__ == '__main__':unittest.main() diff --git a/scripts/test_voxel_island.py b/scripts/test_voxel_island.py new file mode 100644 index 0000000..246d4ec --- /dev/null +++ b/scripts/test_voxel_island.py @@ -0,0 +1,82 @@ +"""Offline geometry regressions; no Minecraft world, MCP server or map fixtures.""" +from collections import deque +import importlib.util +import math +from pathlib import Path +import unittest + +spec = importlib.util.spec_from_file_location('voxel_island', Path(__file__).with_name('voxel-island.py')) +islands = importlib.util.module_from_spec(spec) +spec.loader.exec_module(islands) + + +class FloatingIslandTests(unittest.TestCase): + def test_same_input_is_reproducible_and_seed_changes_geometry(self): + args = (120, -80, 18, 13, 144, 30) + first = islands.columns(*args, 27) + self.assertEqual(first, islands.columns(*args, 27)) + self.assertNotEqual(first, islands.columns(*args, 28)) + + def test_negative_coordinate_envelope_and_inclusive_solid_columns(self): + cx, cz, rx, rz, top, depth = -71, -39, 12, 9, 140, 27 + columns = islands.columns(cx, cz, rx, rz, top, depth, 11, .2) + self.assertIn((cx, cz), columns) + for (x, z), column in columns.items(): + self.assertLessEqual(abs(x-cx), rx*1.3) + self.assertLessEqual(abs(z-cz), rz*1.3) + self.assertEqual(top, column['top']) + self.assertIs(type(column['bottom']), int) + thickness = column['top'] - column['bottom'] + 1 + self.assertGreaterEqual(thickness, 3) + self.assertLessEqual(thickness, math.ceil(depth + 5.5)) + self.assertGreaterEqual(column['radial'], 0) + self.assertLessEqual(column['radial'], 1) + # Adjacent columns all overlap at these top three levels. + self.assertTrue(all(column['bottom'] <= y <= column['top'] for y in range(top-2, top+1))) + + def test_representative_lobed_ellipses_are_four_connected(self): + for rx, rz in ((1, 1), (2, 8), (8, 2), (3, 17), (17, 3), (7, 11), (24, 22)): + for contour in (0, .055, .2): + for seed in range(12): + with self.subTest(rx=rx, rz=rz, contour=contour, seed=seed): + columns = islands.columns(0, 0, rx, rz, 140, 20, seed, contour) + reached, queue = set(), deque([(0, 0)]) + while queue: + point = queue.popleft() + if point in reached or point not in columns: + continue + reached.add(point) + x, z = point + queue.extend(((x-1, z), (x+1, z), (x, z-1), (x, z+1))) + self.assertEqual(set(columns), reached) + + def test_translation_preserves_outline_but_not_world_anchored_underside(self): + first = islands.columns(0, 0, 12, 9, 140, 20, 19) + moved = islands.columns(43, -27, 12, 9, 140, 20, 19) + self.assertEqual({(x+43, z-27) for x, z in first}, set(moved)) + self.assertTrue(any(first[x, z]['bottom'] != moved[x+43, z-27]['bottom'] for x, z in first)) + + def test_tiny_positive_radii_still_include_the_centre(self): + self.assertEqual({(-5, 8)}, set(islands.columns(-5, 8, .01, .01, 140, 1, 2))) + + def test_value_noise_is_bounded_and_continuous_at_negative_grid_edges(self): + for seed in (0, 1, 100): + for x, z in ((-3, .2), (.5, -7), (-4, -9), (0, 0)): + value = islands.noise(x, z, seed) + self.assertGreaterEqual(value, 0) + self.assertLessEqual(value, 1) + self.assertLess(abs(islands.noise(x-1e-7, z, seed)-islands.noise(x+1e-7, z, seed)), 1e-6) + + def test_invalid_geometry_is_rejected_before_sampling(self): + for rx, rz, depth, contour in ((0, 5, 10, .1), (5, -1, 10, .1), (5, 5, 0, .1), + (math.inf, 5, 10, .1), (5, math.nan, 10, .1), + (5, 5, math.inf, .1), (5, 5, 10, -.01), (5, 5, 10, .201)): + with self.subTest(rx=rx, rz=rz, depth=depth, contour=contour): + with self.assertRaises(ValueError): + islands.columns(0, 0, rx, rz, 140, depth, 1, contour) + with self.assertRaises(ValueError): + islands.columns(.5, 0, 5, 5, 140, 10, 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/verify-balustrade.py b/scripts/verify-balustrade.py deleted file mode 100644 index 0174aa0..0000000 --- a/scripts/verify-balustrade.py +++ /dev/null @@ -1,289 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only candidate and observed-volume QA for the arrival-square balustrade. - -Checks stable capped-wall states, cardinal continuity, grounded footings, original -road masks, preserved garden fixtures and remaining walkable floor. Actual mode -also compares every captured voxel against baseline plus the desired overlay. -""" -import argparse -from collections import Counter, deque -import hashlib -import importlib.util -import json -from pathlib import Path -import unittest - -ROOT = Path(__file__).resolve().parents[1] -SPEC = importlib.util.spec_from_file_location('balustrade_plaza_qa', ROOT / 'scripts/verify-plaza.py') -plaza = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(plaza) -survey = plaza.survey -N = {'east': (1, 0), 'north': (0, -1), 'south': (0, 1), 'west': (-1, 0)} -CAP = 'minecraft:smooth_sandstone_slab[type=bottom,waterlogged=false]' - - -def coordinates(document): - result = {} - for row in document['blocks']: - at = tuple(survey.point(row)[axis] for axis in ('x', 'y', 'z')) - if at in result: - raise ValueError(f'Duplicate desired position {at}') - result[at] = row['block'] - return result - - -def stable_wall_properties(connected, capped=True): - """Vanilla 26.2 WallBlock under a full bottom face (the bottom slab cap). - - Cap coverage makes arms tall. shouldRaisePost suppresses the post for either - opposite tall pair only after its endpoint/corner/T asymmetry condition. - """ - ns = connected['north'] != connected['south'] - ew = connected['east'] != connected['west'] - opposite = ((connected['north'] and connected['south']) or - (connected['east'] and connected['west'])) - up = ns or ew or not opposite - return {**{name: ('tall' if capped else 'low') if connected[name] else 'none' for name in N}, - 'up': str(up).lower(), 'waterlogged': 'false'} - - -def wall_neighbor(state): - name, _ = plaza.parse(state) - return name.endswith('_wall') or name in plaza.STATIC_CUBES - - -def check_guard(reader, metadata): - columns = set(map(tuple, metadata['fence_columns'])) - piers = set(map(tuple, metadata['pier_columns'])) - failures, listed = [], [] - if not columns or not piers <= columns: - raise ValueError('Fence columns and piers must form a nonempty valid set') - for index, raw in enumerate(metadata['path_runs']): - run = list(map(tuple, raw)) - if len(run) < 2 or run[0] not in piers or run[-1] not in piers: - failures.append({'reason': 'run_needs_pier_endpoints', 'run': index}) - for a, b in zip(run, run[1:]): - if abs(a[0] - b[0]) + abs(a[1] - b[1]) != 1: - failures.append({'reason': 'noncardinal_guard_gap', 'from': a, 'to': b}) - listed.extend(run) - if len(listed) != len(set(listed)) or set(listed) != columns: - failures.append({'reason': 'path_runs_do_not_cover_fence_once'}) - for x, z in sorted(columns): - state = reader.state(x, 96, z) - material, props = plaza.parse(state) - if (x, z) in piers: - if material != 'cut_sandstone': - failures.append({'at': [x, 96, z], 'reason': 'pier_material', 'actual': state}) - else: - connections = {name: wall_neighbor(reader.state(x + dx, 96, z + dz)) - for name, (dx, dz) in N.items()} - expected = stable_wall_properties(connections) - if material != 'stone_brick_wall' or props != expected: - failures.append({'at': [x, 96, z], 'reason': 'unstable_wall_state', - 'actual': state, 'expected_properties': expected}) - if reader.state(x, 97, z) != CAP: - failures.append({'at': [x, 97, z], 'reason': 'missing_continuous_bottom_cap'}) - if plaza.parse(reader.state(x, 95, z))[0] not in plaza.STATIC_CUBES: - failures.append({'at': [x, 95, z], 'reason': 'guard_without_full_base'}) - for footing in metadata['footing_columns']: - x, z, bottom = footing['x'], footing['z'], footing['support_y'] - if (x, z) not in columns or not 70 <= bottom < 95: - raise ValueError('Unexpected footing coordinate or support height') - if plaza.parse(reader.state(x, bottom, z))[0] == 'grass_block': - failures.append({'at': [x, bottom, z], 'reason': 'grass_under_opaque_footing_will_decay_to_dirt'}) - for y in range(bottom, 96): - if plaza.parse(reader.state(x, y, z))[0] not in plaza.STATIC_CUBES: - failures.append({'at': [x, y, z], 'reason': 'footing_gap_or_unknown_support'}) - # A newly connected pier also changes the reciprocal side of an old road - # rail. Inspect real neighbors, including walls absent from compiler masks. - adjacent = {(x + dx, z + dz) for x, z in columns for dx, dz in N.values() - if (x + dx, z + dz) not in columns and - plaza.parse(reader.state(x + dx, 96, z + dz))[0] == 'stone_brick_wall'} - for x, z in sorted(adjacent): - above = reader.state(x, 97, z) - if above not in survey.AIR and above != CAP: - failures.append({'at': [x, 97, z], 'reason': 'unknown_neighbor_rail_top_shape', 'actual': above}) - continue - connected = {name: wall_neighbor(reader.state(x + dx, 96, z + dz)) - for name, (dx, dz) in N.items()} - actual = reader.state(x, 96, z) - expected = stable_wall_properties(connected, above == CAP) - if plaza.parse(actual)[1] != expected: - failures.append({'at': [x, 96, z], 'reason': 'unstable_reciprocal_road_rail', - 'actual': actual, 'expected_properties': expected}) - pending, components = set(columns), [] - while pending: - seen, queue = set(), deque([next(iter(pending))]) - while queue: - p = queue.popleft() - if p not in pending: - continue - pending.remove(p) - seen.add(p) - queue.extend((p[0] + dx, p[1] + dz) for dx, dz in N.values()) - components.append(len(seen)) - if sorted(components) != sorted(len(run) for run in metadata['path_runs']): - failures.append({'reason': 'actual_cardinal_components_differ_from_runs', 'components': components}) - return {'passed': not failures, 'columns': len(columns), 'piers': len(piers), - 'cardinal_components': sorted(components), 'grounded_footings': len(metadata['footing_columns']), - 'adjacent_road_rails': len(adjacent), - 'failures': failures} - - -def captured_voxels(snapshot): - for cell in snapshot.cells.values(): - lo, hi, index = cell['min'], cell['max'], 0 - for y in range(lo['y'], hi['y'] + 1): - for z in range(lo['z'], hi['z'] + 1): - for x in range(lo['x'], hi['x'] + 1): - yield (x, y, z), cell['palette'][cell['indices'][index]] - index += 1 - - -def compare_volume(before, after, desired): - mismatches, counts = [], Counter() - for at, baseline in captured_voxels(before): - changed = at in desired - counts['desired_voxels' if changed else 'outside_desired_voxels'] += 1 - actual, expected = after.state(*at), desired.get(at, baseline) - if actual != expected: - counts['desired_mismatches' if changed else 'outside_desired_mismatches'] += 1 - if len(mismatches) < 30: - mismatches.append({'at': list(at), 'expected': expected, 'actual': actual, - 'inside_desired': changed}) - if counts['desired_voxels'] != len(desired): - raise ValueError('Desired blocks extend outside the captured baseline volume') - return {'passed': not mismatches, **dict(counts), 'mismatch_examples': mismatches, - 'scope_note': 'Every voxel inside the supplied before snapshot; no claim for unobserved exterior voxels.'} - - -def audit(before, layout, metadata, garden_plan, garden_metadata, garden_walk, nav, after=None): - scope = survey.scope_of(layout['scope']) - if any(value != scope for value in (before.scope, metadata['scope'], garden_plan['scope'], - garden_metadata['scope'], garden_walk['scope'])) or (after and after.scope != scope): - raise ValueError('Project/world/epoch differs between inputs') - desired, failures = coordinates(layout), [] - for row in layout['blocks']: - at = tuple(row[a] for a in ('x', 'y', 'z')) - if before.state(*at) != row['expected']: - raise ValueError(f'Stale expected block at {at}') - fence = set(map(tuple, metadata['fence_columns'])) - road = {tuple(p) for route in nav['routes'] for p in route['clear_cells']} - bench = set(map(tuple, garden_metadata['bench_access_columns'])) - for x, y, z in desired: - if (x, z) in road or (x, z) in bench: - failures.append({'at': [x, y, z], 'reason': 'protected_road_or_bench_column_edited'}) - if fence & (road | bench): - failures.append({'reason': 'fence_declared_on_protected_road_or_bench'}) - reader = plaza.Reader(after or before, None if after else desired) - guard = check_guard(reader, metadata) - old_desired = coordinates(garden_plan) - protected = {at: state for at, state in old_desired.items() - if plaza.parse(state)[0] in plaza.FLOWERS | {'spruce_stairs', 'spruce_fence', 'lantern', - 'iron_chain', 'iron_bars', 'gold_block'} or any(part in state for part in ('_leaves', '_log', 'waxed_oxidized_cut_copper'))} - for fixture in garden_metadata['fixtures']: - if fixture['type'] in ('conifer', 'topiary'): - at = fixture['x'], 95, fixture['z'] - protected[at] = before.state(*at) - lost = [{'at': list(at), 'expected': state, 'actual': reader.state(*at)} - for at, state in protected.items() if reader.state(*at) != state] - fixture_report = plaza.check_fixtures(reader, old_desired, garden_metadata) - blocked = set(map(tuple, garden_metadata['unwalkable_columns'])) | fence - points = [p for p in garden_walk['points'] if (p['x'], p['z']) not in fence] - walking = survey.verify_walkable(reader, points) - navigation = json.loads(json.dumps(nav)) - for cell in navigation['cells']: - if (cell['x'], cell['z']) in blocked: - cell['clear'] = False - nav_report = plaza.navigation.audit(navigation) - graph = plaza.navigation.surface_graph(plaza.navigation.normalize_cells(navigation)) - reached = plaza.navigation.reachable(graph, (0, 9)) - bench_report = [plaza.navigation.endpoint_report(f'bench-access-{x}-{z}', (x, z), graph, reached) - for x, z in sorted(bench)] - nav_report['bench_access'] = bench_report - nav_report['passed'] &= all(row['passed'] for row in bench_report) - volume = compare_volume(before, after, desired) if after else None - passed = (not failures and not lost and guard['passed'] and fixture_report['passed'] and - walking['passed'] and nav_report['passed'] and (volume is None or volume['passed'])) - return {'version': 1, 'scope': scope, 'mode': 'actual_after_snapshot' if after else 'candidate_overlay', - 'passed': passed, 'world_edits': 0, 'desired_blocks': len(desired), 'guard': guard, - 'protected_road_columns': len(road), 'protection_failures': failures, - 'preserved_fixture_states': len(protected), 'lost_fixtures': lost[:30], - 'fixtures': fixture_report, 'walking': walking, 'navigation': nav_report, - 'volume': volume, 'note': 'Point-sampled body headroom and navigation; not a full moving-player collision simulation.'} - - -class BalustradeTests(unittest.TestCase): - def test_stable_capped_wall_corner_and_straight_and_cross(self): - for sides, up in [({'north', 'south'}, 'false'), ({'east', 'west'}, 'false'), - (set(N), 'false'), ({'north', 'east'}, 'true'), - ({'north', 'east', 'south'}, 'true'), ({'north'}, 'true')]: - self.assertEqual(stable_wall_properties({n: n in sides for n in N})['up'], up) - uncapped = stable_wall_properties({n: n in {'east', 'west'} for n in N}, False) - self.assertEqual((uncapped['east'], uncapped['west'], uncapped['up']), ('low', 'low', 'false')) - - def test_volume_detects_unrelated_change_and_exact_properties(self): - before = type('Snapshot', (), {'cells': {0: {'min': dict(x=0, y=0, z=0), - 'max': dict(x=1, y=0, z=0), 'palette': ['minecraft:air'], 'indices': [0, 0]}}})() - states = {(0, 0, 0): CAP, (1, 0, 0): 'minecraft:stone'} - after = type('After', (), {'state': lambda self, x, y, z: states[x, y, z]})() - result = compare_volume(before, after, {(0, 0, 0): CAP}) - self.assertFalse(result['passed']) - self.assertEqual(result['outside_desired_mismatches'], 1) - states[(1, 0, 0)] = 'minecraft:air' - self.assertTrue(compare_volume(before, after, {(0, 0, 0): CAP})['passed']) - states[(0, 0, 0)] = CAP.replace('bottom', 'top') - self.assertEqual(compare_volume(before, after, {(0, 0, 0): CAP})['desired_mismatches'], 1) - - def test_diagonal_run_is_rejected(self): - states = {(x, y, z): 'minecraft:air' for x in range(-1, 3) for z in range(-1, 3) for y in range(95, 98)} - for x, z in [(0, 0), (1, 1)]: - states[x, 95, z] = 'minecraft:stone' - states[x, 96, z] = 'minecraft:cut_sandstone' - states[x, 97, z] = CAP - reader = type('Reader', (), {'state': lambda self, x, y, z: states[x, y, z]})() - meta = {'fence_columns': [[0, 0], [1, 1]], 'pier_columns': [[0, 0], [1, 1]], - 'path_runs': [[[0, 0], [1, 1]]], 'footing_columns': []} - result = check_guard(reader, meta) - self.assertIn('noncardinal_guard_gap', {f['reason'] for f in result['failures']}) - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - stage, garden = ROOT / '.runtime/balustrade-stage04', ROOT / '.runtime/plaza-stage03' - parser.add_argument('--before', type=Path, default=stage / 'before-full.json.gz') - parser.add_argument('--after', type=Path) - parser.add_argument('--layout', type=Path, default=stage / 'balustrade.json') - parser.add_argument('--metadata', type=Path, default=stage / 'balustrade.metadata.json') - parser.add_argument('--garden-plan', type=Path, default=garden / 'plaza-polished.json') - parser.add_argument('--garden-metadata', type=Path, default=garden / 'plaza-polished.metadata.json') - parser.add_argument('--garden-walk', type=Path, default=garden / 'plaza-polished.walk.json') - parser.add_argument('--navigation', type=Path, default=ROOT / '.runtime/foundations-stage02/navigation-verified-walkable-input.json') - parser.add_argument('--report', type=Path, default=stage / 'candidate-qa.json') - parser.add_argument('--self-test', action='store_true') - args = parser.parse_args() - if args.self_test: - result = unittest.TextTestRunner().run(unittest.defaultTestLoader.loadTestsFromTestCase(BalustradeTests)) - raise SystemExit(0 if result.wasSuccessful() else 1) - paths = {key: getattr(args, key) for key in ('layout', 'metadata', 'garden_plan', 'garden_metadata', 'garden_walk', 'navigation')} - documents = [json.loads(path.read_text()) for path in paths.values()] - before = survey.load_snapshot(args.before) - after = survey.load_snapshot(args.after, before.scope) if args.after else None - report = audit(before, *documents, after=after) - paths['before'] = args.before - if args.after: - paths['after'] = args.after - report['input_sha256'] = {key: hashlib.sha256(path.read_bytes()).hexdigest() for key, path in paths.items()} - args.report.parent.mkdir(parents=True, exist_ok=True) - survey.terrain.save(args.report, report) - print(json.dumps({key: report[key] for key in ('passed', 'mode', 'desired_blocks', 'guard', 'protection_failures', - 'lost_fixtures', 'fixtures', 'volume')} - | {'walk_points': report['walking']['checked_points'], 'walk_failures': report['walking']['failures'][:10], - 'navigation_passed': report['navigation']['passed'], - 'unreachable_columns': report['navigation']['unreachable_clear_columns'], - 'report': str(args.report.resolve())}, indent=2)) - raise SystemExit(0 if report['passed'] else 1) - - -if __name__ == '__main__': - main() diff --git a/scripts/verify-foundation-survey.py b/scripts/verify-foundation-survey.py deleted file mode 100644 index 08a3e52..0000000 --- a/scripts/verify-foundation-survey.py +++ /dev/null @@ -1,423 +0,0 @@ -#!/usr/bin/env python3 -"""Verify a foundation edit against actual before/after Paper surface maps. - -Air cuts expose saved lower voxel states; an omitted voxel never becomes assumed -air. The whole map is compared, including columns outside the edit. Optional PNG -output is a fresh material/height render of the observed after map, not a mockup. -""" -import argparse -from collections import Counter, defaultdict -import hashlib -import importlib.util -import json -from pathlib import Path -import unittest - -ROOT = Path(__file__).resolve().parents[1] -_spec = importlib.util.spec_from_file_location('foundation_snapshot', ROOT / 'scripts/foundation-survey.py') -survey = importlib.util.module_from_spec(_spec) -_spec.loader.exec_module(survey) -AIR = {'minecraft:air', 'minecraft:cave_air', 'minecraft:void_air'} -WATER = {'minecraft:water', 'minecraft:bubble_column'} -MAP_SCOPE = ('world_uuid', 'world_key', 'min_x', 'max_x', 'min_z', 'max_z', 'width', 'length') - - -def material(state): - return state.split('[', 1)[0] - - -def ignored_materials(document): - """Air stays implicit; absent metadata preserves legacy map semantics.""" - raw = document.get('ignored_materials', []) - if (not isinstance(raw, list) or any(not isinstance(value, str) for value in raw) - or len(raw) != len(set(raw)) - or not set(raw) <= AIR | {'minecraft:barrier'}): - raise ValueError('Unsupported explicit ignored_materials map policy') - return AIR | set(raw) - - -def validate_map(document): - if document.get('format') != 'minecraft-builder-surface-map-v1' or document.get('source') != 'paper_world_surface': - raise ValueError('Expected a captured Paper world surface map') - for key in ('min_x', 'max_x', 'min_z', 'max_z', 'width', 'length'): - if type(document.get(key)) is not int: - raise ValueError('Map bounds must be integers') - count = document['width'] * document['length'] - if (not 0 < count <= 4_194_304 or document['width'] != document['max_x'] - document['min_x'] + 1 - or document['length'] != document['max_z'] - document['min_z'] + 1): - raise ValueError('Map bounds and dimensions disagree') - palette = document.get('palette') - if not isinstance(palette, list) or not palette or any(not isinstance(m, str) or not survey.STATE.fullmatch(m) or '[' in m for m in palette): - raise ValueError('Map palette must contain material names') - heights, indices = document.get('surface_y'), document.get('material_index') - if not isinstance(heights, list) or len(heights) != count or any(type(y) is not int for y in heights): - raise ValueError('Map has missing or invalid surface heights') - if not isinstance(indices, list) or len(indices) != count or any(type(i) is not int or not 0 <= i < len(palette) for i in indices): - raise ValueError('Map has missing or invalid material indices') - ignored = ignored_materials(document) - if any(palette[index] in ignored - AIR for index in indices): - raise ValueError('Map surface contains a material its explicit policy ignores') - return [palette[index] for index in indices] - - -def column_index(document, x, z): - if not (document['min_x'] <= x <= document['max_x'] and document['min_z'] <= z <= document['max_z']): - raise ValueError(f'Edited column {(x, z)} is outside the surface map') - return (z - document['min_z']) * document['width'] + x - document['min_x'] - - -def expected_surface(before, snapshot, layout): - original_materials = validate_map(before) - ignored = ignored_materials(before) - scope = survey.scope_of(layout.get('scope', {})) - if snapshot.scope != scope or scope['world_id'] != before['world_uuid']: - raise ValueError('Voxel snapshot, layout and map scopes differ') - overlays = defaultdict(dict) - actual_changes = Counter() - water_replacements = [] - expected_states_checked = 0 - blocks = layout.get('blocks') - if layout.get('version') != 1 or not isinstance(blocks, list) or not blocks: - raise ValueError('Layout requires version 1 and explicit blocks') - for block in blocks: - pos = survey.point(block) - x, y, z = pos['x'], pos['y'], pos['z'] - column_index(before, x, z) - if y in overlays[(x, z)]: - raise ValueError(f'Duplicate desired block {(x, y, z)}') - desired, expected = block.get('block'), block.get('expected') - if any(not isinstance(s, str) or not survey.STATE.fullmatch(s) for s in (desired, expected)): - raise ValueError('Layout requires valid desired and expected block states') - observed = snapshot.state(x, y, z) - if observed != expected: - raise ValueError(f'Layout expected state disagrees with baseline voxel snapshot at {(x, y, z)}') - expected_states_checked += 1 - overlays[(x, z)][y] = desired - if desired != observed: - actual_changes['removed' if material(desired) in AIR else 'placed_or_replaced'] += 1 - if material(observed) in WATER and desired != observed: - water_replacements.append({'x': x, 'y': y, 'z': z, 'before': observed, 'desired': desired}) - heights = before['surface_y'].copy() - materials = original_materials.copy() - exposed_snapshot_blocks = 0 - map_only_covered_tops = 0 - for (x, z), overlay in overlays.items(): - i = column_index(before, x, z) - original_top = before['surface_y'][i] - # A map and a block capture taken at different times must still agree - # where reconstruction relies on their shared original surface. - visible_overlay_top = max((y for y, state in overlay.items() if material(state) not in ignored), - default=original_top) - try: - observed_original_top = material(snapshot.state(x, original_top, z)) - except KeyError: - # An overhead addition can be surveyed independently of the ground - # it covers. Its checked desired voxel proves the new surface lies - # above the earlier captured top; no lower voxel is reconstructed. - # Cuts and invisible-only additions still require that observation. - if visible_overlay_top <= original_top: - raise - observed_original_top = original_materials[i] - map_only_covered_tops += 1 - empty_fallback = original_materials[i] in AIR - if (observed_original_top != original_materials[i] - and not (empty_fallback and observed_original_top in ignored)): - raise ValueError(f'Before map and voxel snapshot disagree at original surface {(x, original_top, z)}') - top = max(original_top, visible_overlay_top) - while True: - if top in overlay: - state = overlay[top] - elif top == original_top: - state = original_materials[i] - elif top > original_top: - # The captured surface proves higher untouched cells are - # ignored by this explicit visibility policy. They may be - # invisible barriers; this is no claim of collision-free air. - state = 'minecraft:air' - else: - state = snapshot.state(x, top, z) - exposed_snapshot_blocks += 1 - if material(state) not in ignored: - heights[i], materials[i] = top, material(state) - break - if empty_fallback and top == original_top: - # A captured all-transparent column reports air at world min Y. - # Do not read below that explicit fallback or invent terrain. - heights[i], materials[i] = top, original_materials[i] - break - top -= 1 - return heights, materials, overlays, { - 'expected_states_checked_against_baseline': expected_states_checked, - 'placed_or_replaced_voxels': actual_changes['placed_or_replaced'], 'removed_voxels': actual_changes['removed'], - 'lower_snapshot_voxels_consulted_after_cuts': exposed_snapshot_blocks, - 'covered_original_tops_known_only_from_before_map': map_only_covered_tops, - 'observed_water_voxels_replaced': water_replacements} - - -def verify(before, after, snapshot, layout): - after_materials = validate_map(after) - if ignored_materials(before) != ignored_materials(after): - raise ValueError('Before and after map ignored_materials policies differ; capture a matching baseline') - for key in MAP_SCOPE: - if before.get(key) != after.get(key): - raise ValueError(f'Before and after map scopes differ: {key}') - expected_y, expected_m, overlays, stats = expected_surface(before, snapshot, layout) - original_materials = [before['palette'][i] for i in before['material_index']] - affected = {column_index(before, x, z) for x, z in overlays} - mismatches, outside_mismatches = [], 0 - changed_surface = 0 - water_columns = 0 - water_surface_changes = [] - for i, observed in enumerate(after_materials): - x, z = i % before['width'] + before['min_x'], i // before['width'] + before['min_z'] - if before['surface_y'][i] != after['surface_y'][i] or original_materials[i] != observed: - changed_surface += 1 - if expected_y[i] != after['surface_y'][i] or expected_m[i] != observed: - mismatch = {'x': x, 'z': z, 'expected_y': expected_y[i], 'actual_y': after['surface_y'][i], - 'expected_material': expected_m[i], 'actual_material': observed, 'inside_edit_columns': i in affected} - mismatches.append(mismatch) - if i not in affected: - outside_mismatches += 1 - if original_materials[i] in WATER: - water_columns += 1 - if observed != original_materials[i] or before['surface_y'][i] != after['surface_y'][i]: - water_surface_changes.append({'x': x, 'z': z, 'before_y': before['surface_y'][i], - 'after_y': after['surface_y'][i], 'after_material': observed}) - water_ok = not water_surface_changes and not stats['observed_water_voxels_replaced'] - return {'version': 1, 'world': before['world'], 'scope': snapshot.scope, - 'source': 'Full live Paper surface maps plus observed baseline voxels and desired edit overlay', - 'surface_ignored_materials': sorted(ignored_materials(after)), - 'verified_surface_columns': len(expected_y), 'affected_columns': len(affected), - 'outside_edit_columns_verified': len(expected_y) - len(affected), - 'changed_surface_columns': changed_surface, 'surface_mismatches': len(mismatches), - 'outside_edit_surface_mismatches': outside_mismatches, - 'mismatch_examples': mismatches[:40], - 'original_visible_water_columns': water_columns, - 'visible_water_surface_changes': len(water_surface_changes), - 'water_surface_change_examples': water_surface_changes[:20], - **stats, 'water_preservation_passed': water_ok, - 'passed': not mismatches and water_ok, - 'capture_started_at': after.get('capture_started_at'), 'capture_finished_at': after.get('capture_finished_at'), - 'atomic_snapshot': False, - 'note': 'Sequential map captures; edits must be idle during each capture. This checks every visible surface and the observed water blocks in the edit. Hidden untouched blocks outside the voxel survey are not inferred. Live voxel and headroom checks are separate.'} - - -def render(after, layout, report, path): - import numpy as np - import matplotlib - matplotlib.use('Agg') - import matplotlib.pyplot as plt - from matplotlib.colors import to_rgb - - heights = np.asarray(after['surface_y'], dtype=float).reshape(after['length'], after['width']) - ids = np.asarray(after['material_index']).reshape(heights.shape) - colors = after.get('palette_rgb') - if not isinstance(colors, list) or len(colors) != len(after['palette']): - raise ValueError('PNG rendering requires the actual map palette_rgb array') - rgb = np.asarray([to_rgb(c) for c in colors])[ids] - dz, dx = np.gradient(heights) - light = (.55 * dx + .55 * dz + .63) / np.sqrt(dx * dx + dz * dz + 1) - shade = np.clip(.76 + .36 * light, .44, 1.13) - for index, name in enumerate(after['palette']): - if name.endswith(('_concrete', '_wool', '_terracotta')): - shade[ids == index] = 1 - if name in WATER: - shade[ids == index] = .96 - rgb = np.clip(rgb * shade[:, :, None], 0, 1) - blocks = layout['blocks'] - x0, x1 = min(b['x'] for b in blocks) - 20, max(b['x'] for b in blocks) + 20 - z0, z1 = min(b['z'] for b in blocks) - 20, max(b['z'] for b in blocks) + 20 - fig, ax = plt.subplots(figsize=(11, 14), facecolor='#f1eee4') - ax.set_facecolor('#f1eee4') - ax.imshow(rgb, origin='upper', interpolation='nearest', - extent=(after['min_x'], after['max_x'] + 1, after['max_z'] + 1, after['min_z'])) - ax.set(xlim=(x0, x1), ylim=(z1, z0), xlabel='X · east →', ylabel='Z (positive south)') - ax.set_aspect('equal') - ax.tick_params(colors='#4f594d', labelsize=9) - for spine in ax.spines.values(): - spine.set_color('#9a9e8c') - labels = [ - ((0, 9), (64, -10), '01 ARRIVAL SQUARE'), - ((-14, -117), (33, -156), '02 CLOCK STATION'), - ((-6, -79), (54, -75), 'Station steps'), - ((-7, -61), (37, -44), 'Forecourt'), - ((1, 91), (-57, 115), 'South approach'), - ((-75, 30), (-74, 62), 'Lake approach'), - ((-60, -36), (-98, -23), 'Portal approach'), - ((-63, -72), (-93, -103), 'Northwest promenade'), - ((67, -84), (77, -117), 'Northeast promenade'), - ((68, 10), (72, 42), 'East approach')] - for (x, z), (tx, tz), label in labels: - ax.annotate(label, xy=(x + .5, z + .5), xytext=(tx, tz), - ha='center', va='center', fontsize=8.7 if label[:2] in ('01', '02') else 8, - color='#263b31', weight='bold' if label[:2] in ('01', '02') else 'normal', - bbox=dict(boxstyle='round,pad=.3', fc='#f8f5ea', ec='#849080', lw=.6, alpha=.96), - arrowprops=dict(arrowstyle='-', color='#354b3c', lw=.8, shrinkA=3, shrinkB=2)) - ax.annotate('N', xy=(x1 - 8, z0 + 4), xytext=(x1 - 8, z0 + 18), ha='center', va='center', - color='#20392d', fontsize=11, weight='bold', arrowprops=dict(arrowstyle='-|>', color='#20392d')) - bar_x, bar_z = x0 + 8, z1 - 8 - ax.plot([bar_x, bar_x + 25], [bar_z, bar_z], color='#20392d', lw=2) - ax.text(bar_x + 12.5, bar_z - 2, '25 blocks', ha='center', va='bottom', fontsize=8, color='#20392d', - bbox=dict(fc='#f8f5ea', ec='none', alpha=.8, pad=1)) - fig.suptitle('SHACRAFT / FOUNDATIONS 01 + 02', x=.5, y=.975, fontsize=17, weight='bold', color='#20392d') - fig.text(.5, .945, 'Actual server surface · north up · local roads and supported foundations', - ha='center', fontsize=10, color='#52614e') - status = 'surface check passed' if report['passed'] else f"{report['surface_mismatches']} surface mismatches" - fig.text(.5, .025, f"{report['verified_surface_columns']:,} map columns checked · {status}\n" - 'Material colors and relief come from the captured world. Labels are annotations.', - ha='center', fontsize=9, color='#52614e', linespacing=1.6) - fig.subplots_adjust(left=.09, right=.96, top=.923, bottom=.077) - path.parent.mkdir(parents=True, exist_ok=True) - fig.savefig(path, dpi=180, facecolor=fig.get_facecolor()) - plt.close(fig) - - -class FakeSnapshot: - scope = {'project_id': 'project', 'world_id': 'world', 'world_epoch': 'epoch'} - - def __init__(self, states): - self.states = states - - def state(self, x, y, z): - return self.states[(x, y, z)] - - -def fixture_map(heights, materials): - palette = list(dict.fromkeys(materials)) - return {'format': 'minecraft-builder-surface-map-v1', 'source': 'paper_world_surface', - 'world': 'test', 'world_uuid': 'world', 'world_key': 'minecraft:test', - 'min_x': 0, 'max_x': len(heights) - 1, 'min_z': 0, 'max_z': 0, - 'width': len(heights), 'length': 1, 'surface_y': heights, - 'palette': palette, 'material_index': [palette.index(m) for m in materials]} - - -class FoundationMapTests(unittest.TestCase): - def baseline(self): - before = fixture_map([10, 12, 8], ['minecraft:grass_block', 'minecraft:gold_block', 'minecraft:water']) - snapshot = FakeSnapshot({(0, 10, 0): 'minecraft:grass_block', (0, 9, 0): 'minecraft:air', - (0, 8, 0): 'minecraft:stone'}) - layout = {'version': 1, 'scope': snapshot.scope, - 'blocks': [{'x': 0, 'y': 10, 'z': 0, 'block': 'minecraft:air', 'expected': 'minecraft:grass_block'}]} - return before, snapshot, layout - - def test_cut_exposes_lower_observed_block_and_preserves_other_columns(self): - before, snapshot, layout = self.baseline() - after = fixture_map([8, 12, 8], ['minecraft:stone', 'minecraft:gold_block', 'minecraft:water']) - report = verify(before, after, snapshot, layout) - self.assertTrue(report['passed']) - self.assertEqual(report['lower_snapshot_voxels_consulted_after_cuts'], 2) - self.assertEqual(report['outside_edit_columns_verified'], 2) - self.assertEqual(report['original_visible_water_columns'], 1) - - def test_unrelated_column_change_is_detected(self): - before, snapshot, layout = self.baseline() - after = fixture_map([8, 11, 8], ['minecraft:stone', 'minecraft:gold_block', 'minecraft:water']) - report = verify(before, after, snapshot, layout) - self.assertFalse(report['passed']) - self.assertEqual(report['outside_edit_surface_mismatches'], 1) - self.assertEqual(report['mismatch_examples'][0]['x'], 1) - - def test_missing_lower_voxel_is_not_treated_as_air(self): - before, snapshot, layout = self.baseline() - del snapshot.states[(0, 9, 0)] - with self.assertRaises(KeyError): - expected_surface(before, snapshot, layout) - - def test_new_surface_block_properties_reduce_to_material(self): - before, snapshot, layout = self.baseline() - snapshot.states[(0, 11, 0)] = 'minecraft:air' - layout['blocks'].append({'x': 0, 'y': 11, 'z': 0, 'expected': 'minecraft:air', - 'block': 'minecraft:smooth_stone_slab[type=bottom,waterlogged=false]'}) - after = fixture_map([11, 12, 8], ['minecraft:smooth_stone_slab', 'minecraft:gold_block', 'minecraft:water']) - self.assertTrue(verify(before, after, snapshot, layout)['passed']) - - def test_stale_expected_state_and_surface_source_mismatch_rejected(self): - before, snapshot, layout = self.baseline() - layout['blocks'][0]['expected'] = 'minecraft:dirt' - with self.assertRaisesRegex(ValueError, 'baseline'): - expected_surface(before, snapshot, layout) - layout['blocks'][0]['expected'] = 'minecraft:grass_block' - before['palette'][0] = 'minecraft:dirt' - with self.assertRaisesRegex(ValueError, 'original surface'): - expected_surface(before, snapshot, layout) - - def test_explicit_barrier_roof_is_invisible_but_legacy_roof_is_visible(self): - before, snapshot, _ = self.baseline() - snapshot.states[(0, 20, 0)] = 'minecraft:air' - layout = {'version': 1, 'scope': snapshot.scope, 'blocks': [ - {'x': 0, 'y': 20, 'z': 0, 'block': 'minecraft:barrier', 'expected': 'minecraft:air'}]} - legacy_after = fixture_map([20, 12, 8], ['minecraft:barrier', 'minecraft:gold_block', 'minecraft:water']) - self.assertTrue(verify(before, legacy_after, snapshot, layout)['passed']) - visible_after = json.loads(json.dumps(before)) - before['ignored_materials'] = visible_after['ignored_materials'] = ['minecraft:barrier'] - self.assertTrue(verify(before, visible_after, snapshot, layout)['passed']) - - def test_replacing_visible_top_with_ignored_barrier_exposes_lower_observed_stone(self): - before, snapshot, layout = self.baseline() - layout['blocks'][0]['block'] = 'minecraft:barrier' - after = fixture_map([8, 12, 8], ['minecraft:stone', 'minecraft:gold_block', 'minecraft:water']) - before['ignored_materials'] = after['ignored_materials'] = sorted(AIR | {'minecraft:barrier'}) - self.assertTrue(verify(before, after, snapshot, layout)['passed']) - - def test_policy_change_is_not_silently_compared_to_legacy_map(self): - before, snapshot, layout = self.baseline() - after = fixture_map([8, 12, 8], ['minecraft:stone', 'minecraft:gold_block', 'minecraft:water']) - after['ignored_materials'] = sorted(AIR | {'minecraft:barrier'}) - with self.assertRaisesRegex(ValueError, 'policies differ'): - verify(before, after, snapshot, layout) - - def test_all_transparent_column_keeps_world_min_air_fallback(self): - before = fixture_map([-64], ['minecraft:air']) - before['ignored_materials'] = ['minecraft:barrier'] - after = json.loads(json.dumps(before)) - snapshot = FakeSnapshot({(0, -64, 0): 'minecraft:barrier', (0, 20, 0): 'minecraft:air'}) - layout = {'version': 1, 'scope': snapshot.scope, 'blocks': [ - {'x': 0, 'y': 20, 'z': 0, 'block': 'minecraft:barrier', 'expected': 'minecraft:air'}]} - self.assertTrue(verify(before, after, snapshot, layout)['passed']) - - def test_overhead_addition_uses_captured_map_below_tight_voxel_survey(self): - before = fixture_map([10], ['minecraft:grass_block']) - snapshot = FakeSnapshot({(0, 20, 0): 'minecraft:air'}) - layout = {'version': 1, 'scope': snapshot.scope, 'blocks': [ - {'x': 0, 'y': 20, 'z': 0, 'block': 'minecraft:stone', 'expected': 'minecraft:air'}]} - after = fixture_map([20], ['minecraft:stone']) - report = verify(before, after, snapshot, layout) - self.assertTrue(report['passed']) - self.assertEqual(report['covered_original_tops_known_only_from_before_map'], 1) - before['ignored_materials'] = ['minecraft:barrier'] - layout['blocks'][0]['block'] = 'minecraft:barrier' - with self.assertRaises(KeyError): - expected_surface(before, snapshot, layout) - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - maps = ROOT / '.runtime/server/plugins/ShacraftTerrain/maps' - stage = ROOT / '.runtime/foundations-stage02' - parser.add_argument('--before-map', type=Path, default=maps / 'foundations-before.json') - parser.add_argument('--after-map', type=Path, default=maps / 'foundations-after.json') - parser.add_argument('--snapshot', type=Path, default=stage / 'before.json.gz') - parser.add_argument('--layout', type=Path, default=stage / 'foundations.json') - parser.add_argument('--report', type=Path, default=stage / 'surface-verification.json') - parser.add_argument('--png', type=Path) - parser.add_argument('--self-test', action='store_true') - args = parser.parse_args() - if args.self_test: - result = unittest.TextTestRunner().run(unittest.defaultTestLoader.loadTestsFromTestCase(FoundationMapTests)) - raise SystemExit(0 if result.wasSuccessful() else 1) - before, after, layout = [json.loads(p.read_text()) for p in (args.before_map, args.after_map, args.layout)] - snapshot = survey.load_snapshot(args.snapshot, survey.scope_of(layout['scope'])) - report = verify(before, after, snapshot, layout) - report['inputs_sha256'] = {name: hashlib.sha256(path.read_bytes()).hexdigest() - for name, path in [('before_map', args.before_map), ('after_map', args.after_map), - ('baseline_voxels', args.snapshot), ('desired_layout', args.layout)]} - args.report.parent.mkdir(parents=True, exist_ok=True) - survey.terrain.save(args.report, report) - if args.png: - render(after, layout, report, args.png) - print(json.dumps({k: v for k, v in report.items() if k not in ('inputs_sha256', 'mismatch_examples', 'note', 'observed_water_voxels_replaced')}, indent=2)) - raise SystemExit(0 if report['passed'] else 1) - - -if __name__ == '__main__': - main() diff --git a/scripts/verify-layout-survey.py b/scripts/verify-layout-survey.py deleted file mode 100644 index de1d0af..0000000 --- a/scripts/verify-layout-survey.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -"""Compare every live map column to the planned visible result and inspect hidden water.""" -import argparse -from collections import defaultdict -import importlib.util -import json -from pathlib import Path - -ROOT=Path(__file__).resolve().parents[1] - -def main(): - p=argparse.ArgumentParser(description=__doc__) - p.add_argument('--before',type=Path,required=True);p.add_argument('--after',type=Path,required=True) - p.add_argument('--layout',type=Path,action='append',required=True);p.add_argument('--report',type=Path,required=True) - args=p.parse_args();before=json.loads(args.before.read_text());after=json.loads(args.after.read_text()) - for key in ('world_uuid','min_x','max_x','min_z','max_z','width','length'): - if before[key]!=after[key]:raise ValueError('Map scopes differ: '+key) - w=before['width'];expected_y=before['surface_y'].copy() - expected_m=[before['palette'][i] for i in before['material_index']] - original_m=expected_m.copy();targets=set();planned_blocks=0 - for path in args.layout: - layout=json.loads(path.read_text()) - if layout['scope']['world_id']!=before['world_uuid']:raise ValueError('Layout world differs') - for b in sorted(layout['blocks'],key=lambda b:b['y']): - i=(b['z']-before['min_z'])*w+b['x']-before['min_x'];targets.add((b['x'],b['y'],b['z']));planned_blocks+=1 - if b['y']>=expected_y[i]:expected_y[i]=b['y'];expected_m[i]=b['block'] - observed=[after['palette'][i] for i in after['material_index']] - mismatches=[i for i in range(len(expected_y)) if expected_y[i]!=after['surface_y'][i] or expected_m[i]!=observed[i]] - if mismatches:raise ValueError(f'{len(mismatches)} unexpected surface columns; first indices {mismatches[:8]}') - hidden_water=defaultdict(list) - for i,material in enumerate(original_m): - if material=='minecraft:water' and observed[i]!='minecraft:water': - x=i%w+before['min_x'];z=i//w+before['min_z'];y=before['surface_y'][i] - hidden_water[(x//16,z//16,y)].append((x,y,z)) - spec=importlib.util.spec_from_file_location('terrain',ROOT/'scripts/terrain.py') - terrain=importlib.util.module_from_spec(spec);spec.loader.exec_module(terrain) - backend=terrain.Backend(ROOT/'.runtime/server/plugins/MinecraftBuilderMCP/config.yml') - context=backend.call('project_context') - if context['world_id']!=before['world_uuid']:raise ValueError('Live verification world differs') - verified_water=0 - for points in hidden_water.values(): - lo={axis:min(point[j] for point in points) for j,axis in enumerate(('x','y','z'))} - hi={axis:max(point[j] for point in points) for j,axis in enumerate(('x','y','z'))} - read=backend.call('region_inspect',min=lo,max=hi,detail='blocks') - states={tuple(b['pos'][axis] for axis in ('x','y','z')):b['state'] for b in read['blocks']} - for point in points: - if not states[point].startswith('minecraft:water['):raise ValueError('Water changed beneath a marker: '+str(point)) - verified_water+=1 - report={'world':before['world'],'source':'live Paper surface maps + bounded RPC water reads', - 'verified_surface_columns':len(expected_y),'surface_mismatches':0, - 'unique_marker_blocks':len(targets),'planned_writes':planned_blocks, - 'original_water_columns':original_m.count('minecraft:water'),'water_columns_obscured_by_markers':verified_water, - 'water_obscured_by_markers_verified_intact':True, - 'height_unchanged_columns':sum(a==b for a,b in zip(before['surface_y'],after['surface_y'])), - 'capture_started_at':after['capture_started_at'],'capture_finished_at':after['capture_finished_at'], - 'atomic_snapshot':False,'note':'World edits were idle during each capture. Terrain follows its original heights; raised markers represent future structures, not finished traversable paths.'} - args.report.parent.mkdir(parents=True,exist_ok=True);args.report.write_text(json.dumps(report,indent=2)+'\n') - print(json.dumps(report)) - -if __name__=='__main__':main() diff --git a/scripts/verify-plaza.py b/scripts/verify-plaza.py deleted file mode 100644 index c0cd765..0000000 --- a/scripts/verify-plaza.py +++ /dev/null @@ -1,375 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only candidate/live QA for the composed Shacraft arrival gardens. - -Candidate mode overlays desired states on the observed baseline. Live mode uses -an observed after snapshot and compares every planned state exactly. The checks -cover this toolkit's static single flowers, persistent leaves, rooted log trees, -supported lanterns and supplied walking surfaces, not arbitrary Minecraft physics. -""" -import argparse -from collections import Counter, deque -import hashlib -import importlib.util -import json -from pathlib import Path -import unittest - -ROOT = Path(__file__).resolve().parents[1] - - -def module(name, path): - spec = importlib.util.spec_from_file_location(name, path) - value = importlib.util.module_from_spec(spec) - spec.loader.exec_module(value) - return value - - -survey = module('plaza_qa_survey', ROOT / 'scripts/foundation-survey.py') -navigation = module('plaza_qa_navigation', ROOT / 'scripts/foundation-study/verify_geometry.py') -FLOWERS = {'allium', 'oxeye_daisy', 'azure_bluet', 'pink_tulip', 'white_tulip'} -SOIL = {'dirt', 'grass_block'} -STATIC_CUBES = survey.FULL | {'waxed_oxidized_cut_copper'} -N3 = ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)) - - -def parse(state): - name, _, raw = state.removeprefix('minecraft:').partition('[') - props = dict(part.split('=', 1) for part in raw.rstrip(']').split(',') if '=' in part) - return name, props - - -class Reader: - def __init__(self, snapshot, overlay=None): - self.snapshot, self.scope = snapshot, snapshot.scope - self.overlay, self.cache = overlay or {}, {} - - def state(self, x, y, z): - key = (x, y, z) - if key not in self.cache: - self.cache[key] = self.overlay[key] if key in self.overlay else self.snapshot.state(x, y, z) - return self.cache[key] - - -def supported_center(state, face): - """Conservative face support for full cubes and simple dry shapes used here.""" - name, props = parse(state) - if props.get('waterlogged') == 'true': - return False - if name in STATIC_CUBES: - return True - if name.endswith('_slab'): - return props.get('type') == 'double' or props.get('type') == ('bottom' if face == 'down' else 'top') - if name.endswith('_stairs'): - return props.get('half') == ('bottom' if face == 'down' else 'top') - if name == 'iron_chain': - return props.get('axis') == 'y' - if name in ('spruce_fence', 'iron_bars'): - return True - if name == 'stone_brick_wall': - return props.get('up') == 'true' - return False - - -def leaf_distance(reader, start): - """Shortest face-connected path from a leaf to a log, capped at seven. - - Read actual adjacent states, including unchanged leaves/logs, rather than - trusting the compiler's desired-only propagation or saved distance values. - """ - pending, seen = deque([(start, 0)]), {start} - while pending: - (x, y, z), distance = pending.popleft() - if distance >= 6: - continue - for dx, dy, dz in N3: - neighbor = x + dx, y + dy, z + dz - name, _ = parse(reader.state(*neighbor)) - if name.endswith('_log'): - return distance + 1 - if name.endswith('_leaves') and neighbor not in seen: - seen.add(neighbor) - pending.append((neighbor, distance + 1)) - return 7 - - -def check_fixtures(reader, desired, metadata): - failures, counts = [], Counter() - for at, planned in desired.items(): - name, _ = parse(planned) - if name not in FLOWERS and name != 'lantern' and not name.endswith('_leaves'): - continue - x, y, z = at - state = reader.state(*at) - actual_name, props = parse(state) - if actual_name != name: - failures.append({'at': list(at), 'reason': 'fixture_material_differs', 'planned': planned, 'actual': state}) - continue - if name in FLOWERS: - counts['flowers'] += 1 - below = reader.state(x, y - 1, z) - if parse(below)[0] not in SOIL: - failures.append({'at': list(at), 'reason': 'flower_without_valid_soil', 'below': below}) - elif name == 'lantern': - counts['lanterns'] += 1 - hanging = props.get('hanging') == 'true' - support = reader.state(x, y + (1 if hanging else -1), z) - if props.get('waterlogged') != 'false' or not supported_center(support, 'down' if hanging else 'up'): - failures.append({'at': list(at), 'reason': 'lantern_without_dry_center_support', 'support': support}) - else: - counts['leaves'] += 1 - expected_distance = leaf_distance(reader, at) - if (props.get('persistent') != 'true' or props.get('waterlogged') != 'false' - or props.get('distance') != str(expected_distance)): - failures.append({'at': list(at), 'reason': 'unstable_or_wrong_leaf_state', - 'actual': state, 'expected_distance': expected_distance}) - roots = set() - for fixture in metadata['fixtures']: - if fixture['type'] not in ('conifer', 'topiary'): - continue - counts['rooted_trees'] += 1 - x, z = fixture['x'], fixture['z'] - root = (x, 96, z) - roots.add(root) - root_name, root_props = parse(reader.state(*root)) - below = reader.state(x, 95, z) - if not root_name.endswith('_log') or root_props.get('axis') != 'y' or parse(below)[0] not in SOIL: - failures.append({'at': list(root), 'reason': 'tree_root_without_vertical_log_and_soil', 'below': below}) - elif parse(below)[0] == 'grass_block': - failures.append({'at': [x, 95, z], 'reason': 'grass_under_opaque_trunk_will_decay_to_dirt'}) - trunk = sorted(p[1] for p, state in desired.items() if p[0] == x and p[2] == z and parse(state)[0].endswith('_log')) - if not trunk or trunk != list(range(96, max(trunk) + 1)): - failures.append({'at': list(root), 'reason': 'discontinuous_planned_trunk', 'log_y': trunk}) - else: - for y in trunk: - material, props = parse(reader.state(x, y, z)) - if not material.endswith('_log') or props.get('axis') != 'y': - failures.append({'at': [x, y, z], 'reason': 'trunk_gap_or_wrong_axis'}) - log_positions = {p for p, s in desired.items() if parse(s)[0].endswith('_log')} - pending, connected = deque(roots & log_positions), roots & log_positions - while pending: - x, y, z = pending.popleft() - for dx, dy, dz in N3: - p = x + dx, y + dy, z + dz - if p in log_positions and p not in connected: - pending.append(p) - connected.add(p) - if log_positions - connected: - failures.append({'reason': 'logs_disconnected_from_declared_tree_roots', 'positions': [list(p) for p in sorted(log_positions - connected)[:20]]}) - counts['root_connected_logs'] = len(connected) - return {'passed': not failures, 'checked': dict(counts), 'failures': failures} - - -def audit(before, layout, metadata, walk, base_navigation, after=None): - scope = survey.scope_of(layout['scope']) - if any(value != scope for value in (before.scope, metadata['scope'], walk['scope'])) or (after and after.scope != scope): - raise ValueError('Project/world/epoch differs between QA inputs') - desired, mismatches = {}, [] - for block in layout['blocks']: - pos = survey.point(block) - at = tuple(pos[a] for a in ('x', 'y', 'z')) - if at in desired: - raise ValueError('Duplicate desired coordinate') - if before.state(*at) != block['expected']: - raise ValueError(f'Plan expected state differs from baseline at {at}') - desired[at] = block['block'] - if after and after.state(*at) != block['block']: - mismatches.append({'at': list(at), 'expected': block['block'], 'actual': after.state(*at)}) - reader = Reader(after or before, None if after else desired) - fixtures = check_fixtures(reader, desired, metadata) - walking = survey.verify_walkable(reader, walk['points']) - nav = json.loads(json.dumps(base_navigation)) - blocked = {tuple(p) for p in metadata['unwalkable_columns']} - if any((p['x'], p['z']) in blocked for p in walk['points']): - raise ValueError('Walking samples include declared unwalkable ground') - excluded = 0 - for cell in nav['cells']: - if cell['clear'] and (cell['x'], cell['z']) in blocked: - cell['clear'] = False - excluded += 1 - navigation_result = navigation.audit(nav) - navigation_result['decorated_ground_columns_excluded'] = excluded - nav_cells = navigation.normalize_cells(nav) - graph = navigation.surface_graph(nav_cells) - reached = navigation.reachable(graph, (0, 9)) - bench_access = [] - vectors = {'north': (0, -1), 'south': (0, 1), 'west': (-1, 0), 'east': (1, 0)} - for fixture in metadata['fixtures']: - if fixture['type'] != 'bench': - continue - dx, dz = vectors[fixture['facing']] - for offset in (-1, 0, 1): - point = fixture['x'] + dx - dz * offset, fixture['z'] + dz + dx * offset - bench_access.append(navigation.endpoint_report( - f"bench-{fixture['x']}-{fixture['z']}-front-{offset}", point, graph, reached)) - navigation_result['bench_front_access'] = bench_access - navigation_result['passed'] &= all(point['passed'] for point in bench_access) - # A canopy stays traversable when it does not occupy the 1.8-block body space. - # Only explicit low obstacles are removed; overhead leaves do not mask paths. - return {'version': 1, 'mode': 'actual_after_snapshot' if after else 'candidate_overlay', 'scope': scope, - 'world_edits': 0, 'desired_blocks': len(desired), 'baseline_expected_states_verified': len(desired), - 'actual_exact_state_mismatches': len(mismatches) if after else None, - 'actual_mismatch_examples': mismatches[:30], 'fixtures': fixtures, 'walking': walking, - 'navigation': navigation_result, - 'passed': not mismatches and fixtures['passed'] and walking['passed'] and navigation_result['passed'], - 'note': 'Static support, leaf-distance and point-sampled navigation checks; not a complete moving-player collision simulation. Actual mode uses sequential observed snapshots.'} - - -def render_map(document, metadata, report, path, boundary_columns=None, boundary_note=None): - """Render actual captured surface cells with semantic material colors.""" - import numpy as np - import matplotlib - matplotlib.use('Agg') - import matplotlib.pyplot as plt - from matplotlib.colors import to_rgb - from matplotlib.collections import LineCollection - - if document.get('source') != 'paper_world_surface' or document.get('world_uuid') != metadata['scope']['world_id']: - raise ValueError('Rendering requires an actual Paper map of this project world') - semantic = {'minecraft:smooth_sandstone': '#e8ddbd', 'minecraft:cut_sandstone': '#d5c7a5', - 'minecraft:smooth_sandstone_slab': '#e8ddbd', 'minecraft:grass_block': '#87a75f', - 'minecraft:oak_leaves': '#78924d', 'minecraft:spruce_leaves': '#3e6851', - 'minecraft:spruce_log': '#765637', 'minecraft:green_concrete': '#548338', - 'minecraft:spruce_fence': '#7b5839', 'minecraft:spruce_stairs': '#967147', - 'minecraft:allium': '#b388ce', 'minecraft:oxeye_daisy': '#faf3d6', - 'minecraft:azure_bluet': '#f2f0db', 'minecraft:pink_tulip': '#efa8b7', - 'minecraft:white_tulip': '#f7f3e6', 'minecraft:lantern': '#ecc565'} - palette = [] - for name, fallback in zip(document['palette'], document['palette_rgb']): - color = '#68a494' if 'waxed_oxidized_cut_copper' in name else semantic.get(name, fallback) - palette.append(to_rgb(color)) - heights = np.asarray(document['surface_y'], dtype=float).reshape(document['length'], document['width']) - indices = np.asarray(document['material_index']).reshape(heights.shape) - rgb = np.asarray(palette)[indices] - dz, dx = np.gradient(heights) - shade = np.clip(.79 + .26 * (.55 * dx + .55 * dz + .63) / np.sqrt(1 + dx * dx + dz * dz), .64, 1.06) - for index, name in enumerate(document['palette']): - if name.removeprefix('minecraft:') in FLOWERS or name.endswith('_concrete'): - shade[indices == index] = 1 - rgb = np.clip(rgb * shade[:, :, None], 0, 1) - fig, ax = plt.subplots(figsize=(11, 12), facecolor='#f3f0e6') - ax.imshow(rgb, interpolation='nearest', origin='upper', - extent=(document['min_x'], document['max_x'] + 1, document['max_z'] + 1, document['min_z'])) - if boundary_columns: - boundary = set(map(tuple, boundary_columns)) - segments = [[(x + .5, z + .5), (x + dx + .5, z + dz + .5)] - for x, z in boundary for dx, dz in ((1, 0), (0, 1)) if (x + dx, z + dz) in boundary] - ax.add_collection(LineCollection(segments, colors='#a74640', linewidths=1, alpha=.85, zorder=5)) - ax.scatter([x + .5 for x, z in boundary], [z + .5 for x, z in boundary], - s=2, color='#a74640', alpha=.85, zorder=5, label='Invisible wall · observed barrier blocks') - ax.legend(loc='upper left', fontsize=8, facecolor='#faf7ed', framealpha=.94, edgecolor='#bcbfac') - ax.set(xlim=(-52, 53), ylim=(67, -47), xlabel='X · east →', ylabel='Z (positive south)') - ax.set_aspect('equal') - ax.tick_params(colors='#596451', labelsize=9) - for spine in ax.spines.values(): - spine.set_color('#9ca28d') - ax.annotate('N', xy=(47, -44), xytext=(47, -35), ha='center', va='center', - fontsize=11, weight='bold', color='#254534', arrowprops=dict(arrowstyle='-|>', color='#254534')) - ax.plot([-47, -37], [62, 62], color='#254534', lw=2) - ax.text(-42, 60, '10 blocks', ha='center', fontsize=8, color='#254534', - bbox=dict(fc='#faf7ed', ec='none', pad=2, alpha=.9)) - fig.suptitle('SHACRAFT / ARRIVAL GARDEN 01', x=.5, y=.975, fontsize=17, weight='bold', color='#244732') - fig.text(.5, .945, 'Captured server surface · original brand inlay · planted gardens', - ha='center', fontsize=10, color='#586751') - fig.text(.5, .035, f"{metadata['planters']} garden beds · {metadata['trees']} conifers · {metadata['benches']} benches · {metadata['new_lamps']} copper-capped lamps\n" - 'Observed block positions and materials · semantic colors and height shading.' - + ('\n' + boundary_note if boundary_note else ''), - ha='center', fontsize=9, color='#586751', linespacing=1.7) - fig.subplots_adjust(left=.09, right=.97, top=.917, bottom=.135 if boundary_note else .105) - path.parent.mkdir(parents=True, exist_ok=True) - fig.savefig(path, dpi=180, facecolor=fig.get_facecolor()) - plt.close(fig) - - -class FakeSnapshot: - scope = {'project_id': 'test', 'world_id': 'world', 'world_epoch': 'epoch'} - - def __init__(self, states): - self.states = states - - def state(self, x, y, z): - return self.states.get((x, y, z), 'minecraft:air') - - -class PlazaQATests(unittest.TestCase): - def test_flower_soil_and_hanging_lantern_support(self): - desired = {(0, 96, 0): 'minecraft:white_tulip', - (2, 100, 0): 'minecraft:lantern[hanging=true,waterlogged=false]'} - states = {**desired, (0, 95, 0): 'minecraft:grass_block[snowy=false]', (2, 101, 0): 'minecraft:waxed_oxidized_cut_copper'} - result = check_fixtures(Reader(FakeSnapshot(states)), desired, {'fixtures': []}) - self.assertTrue(result['passed']) - del states[(2, 101, 0)] - states[(0, 95, 0)] = 'minecraft:smooth_sandstone' - result = check_fixtures(Reader(FakeSnapshot(states)), desired, {'fixtures': []}) - self.assertEqual({f['reason'] for f in result['failures']}, {'flower_without_valid_soil', 'lantern_without_dry_center_support'}) - - def test_leaf_distance_uses_unchanged_neighbors_and_requires_persistence(self): - leaf = 'minecraft:spruce_leaves[distance=2,persistent=true,waterlogged=false]' - desired = {(0, 100, 0): leaf} - states = {**desired, (1, 100, 0): 'minecraft:oak_leaves[distance=1,persistent=true,waterlogged=false]', - (2, 100, 0): 'minecraft:spruce_log[axis=y]'} - result = check_fixtures(Reader(FakeSnapshot(states)), desired, {'fixtures': []}) - self.assertTrue(result['passed']) - states[(0, 100, 0)] = leaf.replace('persistent=true', 'persistent=false') - self.assertFalse(check_fixtures(Reader(FakeSnapshot(states)), desired, {'fixtures': []})['passed']) - - def test_disconnected_tree_trunk_is_rejected(self): - desired = {(0, 96, 0): 'minecraft:spruce_log[axis=y]', (0, 98, 0): 'minecraft:spruce_log[axis=y]'} - states = {**desired, (0, 95, 0): 'minecraft:dirt'} - result = check_fixtures(Reader(FakeSnapshot(states)), desired, {'fixtures': [{'type': 'conifer', 'x': 0, 'z': 0}]}) - self.assertFalse(result['passed']) - self.assertIn('discontinuous_planned_trunk', {f['reason'] for f in result['failures']}) - - def test_grass_under_opaque_root_is_unstable_but_dirt_is_valid(self): - desired = {(0, 96, 0): 'minecraft:spruce_log[axis=y]'} - states = {**desired, (0, 95, 0): 'minecraft:grass_block[snowy=false]'} - metadata = {'fixtures': [{'type': 'conifer', 'x': 0, 'z': 0}]} - result = check_fixtures(Reader(FakeSnapshot(states)), desired, metadata) - self.assertEqual(result['failures'][0]['reason'], 'grass_under_opaque_trunk_will_decay_to_dirt') - states[(0, 95, 0)] = 'minecraft:dirt' - self.assertTrue(check_fixtures(Reader(FakeSnapshot(states)), desired, metadata)['passed']) - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - stage = ROOT / '.runtime/plaza-stage03' - parser.add_argument('--before', type=Path, default=stage / 'before.json.gz') - parser.add_argument('--after', type=Path) - parser.add_argument('--layout', type=Path, default=stage / 'plaza.json') - parser.add_argument('--metadata', type=Path, default=stage / 'plaza.metadata.json') - parser.add_argument('--walk', type=Path, default=stage / 'plaza.walk.json') - parser.add_argument('--navigation', type=Path, default=ROOT / '.runtime/foundations-stage02/navigation-verified-walkable-input.json') - parser.add_argument('--report', type=Path, default=stage / 'candidate-qa.json') - parser.add_argument('--map', type=Path, help='Actual after surface map, used only with --png and --after') - parser.add_argument('--png', type=Path, help='Focused map of the captured arrival garden') - parser.add_argument('--self-test', action='store_true') - args = parser.parse_args() - if args.self_test: - result = unittest.TextTestRunner().run(unittest.defaultTestLoader.loadTestsFromTestCase(PlazaQATests)) - raise SystemExit(0 if result.wasSuccessful() else 1) - layout, metadata, walk, nav = [json.loads(p.read_text()) for p in (args.layout, args.metadata, args.walk, args.navigation)] - before = survey.load_snapshot(args.before, layout['scope']) - after = survey.load_snapshot(args.after, layout['scope']) if args.after else None - report = audit(before, layout, metadata, walk, nav, after) - paths = {'before': args.before, 'layout': args.layout, 'metadata': args.metadata, 'walk': args.walk, 'navigation': args.navigation} - if args.after: - paths['after'] = args.after - report['input_sha256'] = {key: hashlib.sha256(path.read_bytes()).hexdigest() for key, path in paths.items()} - args.report.parent.mkdir(parents=True, exist_ok=True) - survey.terrain.save(args.report, report) - if args.png: - if not args.map or not args.after: - parser.error('--png requires an actual --map and --after snapshot') - render_map(json.loads(args.map.read_text()), metadata, report, args.png) - print(json.dumps({'passed': report['passed'], 'mode': report['mode'], 'desired_blocks': report['desired_blocks'], - 'exact_mismatches': report['actual_exact_state_mismatches'], - 'fixtures': report['fixtures'], 'walk_points': report['walking']['checked_points'], - 'walk_failures': report['walking']['failures'][:20], - 'navigation_passed': report['navigation']['passed'], - 'unreachable_clear_columns': len(report['navigation']['unreachable_clear_columns']), - 'route_failures': [r['id'] for r in report['navigation']['routes'] if not r['passed']], - 'report': str(args.report.resolve())}, indent=2)) - raise SystemExit(0 if report['passed'] else 1) - - -if __name__ == '__main__': - main() diff --git a/scripts/verify-station.py b/scripts/verify-station.py deleted file mode 100644 index 5071b68..0000000 --- a/scripts/verify-station.py +++ /dev/null @@ -1,389 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only candidate/live station block, public-floor and containment QA. - -Recipes use the checked layout contract {version:1,scope,blocks:[x,y,z,block, -expected]}. Metadata declares public_floors, air-only clear_regions and optional -containment bounds/seeds/authorized_caps. Flooding treats partial/unknown shapes -as passable, so a successful result does not rely on decorative collision shapes. -""" -import argparse -from collections import Counter, deque -import hashlib -import importlib.util -import json -import math -from pathlib import Path -import unittest - -ROOT = Path(__file__).resolve().parents[1] -SPEC = importlib.util.spec_from_file_location('station_base', ROOT / 'scripts/verify-balustrade.py') -base = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(base) -survey, plaza = base.survey, base.plaza -FULL = plaza.STATIC_CUBES | {'barrier'} -N2 = ((1, 0), (-1, 0), (0, 1), (0, -1)) -N3 = ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)) - - -def full_cube(state): - return plaza.parse(state)[0] in FULL - - -def bounded_box(document): - box = survey.box_of(document['min'], document['max']) - if survey.volume(box) > survey.MAX_VOXELS: - raise ValueError('Verification box exceeds the snapshot voxel limit') - return box - - -def positions(box): - return ((x, y, z) for x in range(box['min']['x'], box['max']['x'] + 1) - for y in range(box['min']['y'], box['max']['y'] + 1) - for z in range(box['min']['z'], box['max']['z'] + 1)) - - -def inside(at, box): - return all(box['min'][axis] <= value <= box['max'][axis] - for axis, value in zip(('x', 'y', 'z'), at)) - - -def surface_shape(state): - if full_cube(state): - return [(0., 1.)] - name, props = plaza.parse(state) - if name in {'smooth_sandstone_slab', 'cut_sandstone_slab', 'waxed_oxidized_cut_copper_slab'}: - if props.get('waterlogged') != 'false': - return None - return {'bottom': [(0., .5)], 'top': [(.5, 1.)], 'double': [(0., 1.)]}.get(props.get('type')) - return survey.vertical_shape(state) - - -def public_floors(reader, floors): - reports = [] - for floor in floors: - name, feet, raw = floor['id'], floor['standing_y'], floor['clear_columns'] - if feet not in (99, 113): - raise ValueError('This station stage declares public feet at Y99 or Y113') - if (not isinstance(raw, list) or not raw or any(not isinstance(p, list) or len(p) != 2 - or any(type(v) is not int for v in p) for p in raw)): - raise ValueError('Each public floor requires integer clear_columns [x,z]') - clear = set(map(tuple, raw)) - if len(clear) != len(raw): - raise ValueError('Duplicate public floor column') - source = floor['source']['x'], floor['source']['z'] - if source not in clear: - raise ValueError('Floor source is not a declared clear column') - height = floor.get('min_headroom', 4) - if type(height) not in (int, float) or not math.isfinite(height) or height < 1.8: - raise ValueError('Headroom must be finite and at least 1.8 blocks') - failures, valid = [], set() - for x, z in sorted(clear): - support = surface_shape(reader.state(x, feet - 1, z)) - reason = None - if support is None or not any(abs(high - 1.) < 1e-8 for low, high in support): - reason = 'missing_or_unknown_support_at_public_feet' - else: - for y in range(feet, math.ceil(feet + height)): - shape = surface_shape(reader.state(x, y, z)) - if shape is None: - reason = 'unknown_shape_in_required_clearance' - break - if any(y + lo < feet + height and y + hi > feet for lo, hi in shape): - reason = 'occupied_required_clearance' - break - if reason: - failures.append({'x': x, 'z': z, 'standing_y': feet, 'reason': reason}) - else: - valid.add((x, z)) - reached, queue = set(), deque([source]) - while queue: - p = queue.popleft() - if p in reached or p not in valid: - continue - reached.add(p) - queue.extend((p[0] + dx, p[1] + dz) for dx, dz in N2) - unreachable = sorted(clear - reached) - reports.append({'id': name, 'standing_y': feet, 'required_headroom': height, - 'declared_columns': len(clear), 'physically_clear_columns': len(valid), - 'reachable_clear_columns': len(reached), 'failed_samples': len(failures), - 'sample_failure_examples': failures[:30], 'unreachable_columns': len(unreachable), - 'unreachable_examples': unreachable[:30], 'passed': not failures and not unreachable}) - return {'status': 'checked' if reports else 'not_provided', 'passed': bool(reports) and all(r['passed'] for r in reports), - 'floors': reports, 'method': 'Observed center supports and declared vertical headroom, then cardinal traversal across valid level-floor columns. No lift transitions are inferred.'} - - -def clear_regions(reader, regions): - reports = [] - for region in regions: - box, failures, checked = bounded_box(region), [], 0 - for at in positions(box): - checked += 1 - state = reader.state(*at) - if state not in survey.AIR: - failures.append({'at': list(at), 'actual': state}) - reports.append({'id': region['id'], 'bounds': box, 'checked_voxels': checked, - 'nonair_voxels': len(failures), 'examples': failures[:30], 'passed': not failures}) - return {'status': 'checked' if reports else 'not_provided', 'passed': bool(reports) and all(r['passed'] for r in reports), - 'regions': reports, 'method': 'Every declared doorway/cabin/aisle clearance voxel must be observed air.'} - - -def named_access(reader, metadata): - """Ensure furnishing targets are not silently filtered out of public topology.""" - interior = metadata.get('interior') - if interior is None: - return {'status': 'not_provided', 'passed': True} - failures, targets_by_floor, lift_reports = [], {}, [] - floors = {f['standing_y']: set(map(tuple, f['clear_columns'])) for f in metadata['public_floors']} - for raw_feet, navigation in interior['navigation'].items(): - feet = int(raw_feet) - targets = navigation['named_targets'] - targets_by_floor[raw_feet] = len(targets) - if feet not in floors or not targets: - failures.append({'reason': 'missing_floor_or_named_targets', 'standing_y': feet}) - for target in targets: - x, z = target['x'], target['z'] - if target['y'] != feet or (x, z) not in floors.get(feet, set()): - failures.append({'reason': 'named_target_missing_from_public_topology', 'target': target}) - if not full_cube(reader.state(x, feet - 1, z)) or any( - reader.state(x, y, z) not in survey.AIR for y in range(feet, feet + 4)): - failures.append({'reason': 'named_target_missing_full_support_or_four_air', 'target': target}) - for level, lift in interior['lift'].items(): - selector = tuple(lift['selector_block']) - landing = tuple(lift['landing_block']) - selector_state = reader.state(*selector) - x, feet, z = landing - passed = selector_state == 'minecraft:gold_block' and full_cube(reader.state(x, feet - 1, z)) and all( - reader.state(x, y, z) in survey.AIR for y in range(feet, feet + 4)) - lift_reports.append({'floor': level, 'selector': selector, 'observed_selector': selector_state, - 'landing': landing, 'passed': passed}) - if not passed: - failures.append({'reason': 'lift_selector_or_landing_obstructed', 'floor': level}) - return {'status': 'checked', 'passed': not failures, 'named_targets_per_floor': targets_by_floor, - 'lift_landings': lift_reports, 'failures': failures, - 'method': 'Named furnishing targets must remain in the independently verified public-floor topology with full support and four air blocks. Gold selector and landing voxels are checked; runtime lift operation is not inferred.'} - - -def fixture_stability(reader, desired): - fixtures = {p: s for p, s in desired.items() if plaza.parse(s)[0] in {'lantern', 'spruce_leaves'}} - report = plaza.check_fixtures(reader, fixtures, {'fixtures': []}) - report['checked'].pop('root_connected_logs', None) - logs = {p: s for p, s in desired.items() if plaza.parse(s)[0] == 'spruce_log'} - roots = [p for p in logs if (p[0], p[1] - 1, p[2]) not in logs] - for x, y, z in roots: - below = reader.state(x, y - 1, z) - if plaza.parse(below)[0] not in {'dirt', 'moss_block'}: - report['failures'].append({'reason': 'station_topiary_or_diorama_root_without_soil', - 'at': [x, y, z], 'below': below}) - report['checked'].update({'spruce_logs': len(logs), 'topiary_or_diorama_roots': len(roots)}) - report['passed'] = not report['failures'] - report['status'] = 'checked' - report['method'] = 'Planned lantern support, persistent leaf distances against the full observed neighborhood, and station topiary/diorama roots on stable dirt or moss.' - return report - - -def conservative_containment(reader, metadata): - if metadata is None: - return {'status': 'not_proven', 'passed': False, 'reason': 'No containment bounds, seeds and authorized entrance caps were supplied.'} - box = bounded_box(metadata['bounds']) - caps, cap_reports = set(), [] - for region in metadata.get('authorized_caps', []): - cap_box = bounded_box(region) - points = set(positions(cap_box)) - if not points or not all(inside(p, box) for p in points): - raise ValueError('Authorized virtual cap lies outside surveyed containment bounds') - caps |= points - cap_reports.append({'id': region['id'], 'bounds': cap_box, 'voxels': len(points)}) - blocked, passable, unknown = set(), set(), Counter() - for at in positions(box): - state = reader.state(*at) - if full_cube(state) or at in caps: - blocked.add(at) - else: - passable.add(at) - if state not in survey.AIR: - unknown[plaza.parse(state)[0]] += 1 - regions = [(r['id'], bounded_box(r)) for r in metadata.get('forbidden_regions', [])] - roof_y = metadata.get('forbidden_y_at_or_above', 126) - if type(roof_y) is not int: - raise ValueError('Forbidden roof threshold must be an integer Y') - seeds = metadata.get('seeds') - if not isinstance(seeds, list) or not seeds: - raise ValueError('Containment requires explicit public aisle/cabin seeds') - reports = [] - for seed in seeds: - coordinates = [seed[axis] for axis in ('x', 'y', 'z')] - if any(type(v) not in (int, float) or not math.isfinite(v) for v in coordinates): - raise ValueError('Seed coordinates must be finite') - source = tuple(math.floor(v) for v in coordinates) - if not inside(source, box) or source in caps: - raise ValueError('Containment source is outside its observed box or inside a virtual cap') - low, high = seed.get('min_y'), seed.get('max_y') - if any(v is not None and type(v) is not int for v in (low, high)): - raise ValueError('Per-floor minimum/maximum reachable Y must be integers') - reached, queue, violations, examples = set(), deque([source]), Counter(), [] - while queue: - p = queue.popleft() - if p in reached or p not in passable: - continue - reached.add(p) - reasons = [] - if any(value in (box['min'][axis], box['max'][axis]) for axis, value in zip(('x', 'y', 'z'), p)): - reasons.append('survey_boundary_reachable') - if p[1] >= roof_y: - reasons.append('roof_space_reachable') - if low is not None and p[1] < low: - reasons.append('below_public_floor_reachable') - if high is not None and p[1] > high: - reasons.append('above_public_room_reachable') - reasons.extend('forbidden_region:' + name for name, bounds in regions if inside(p, bounds)) - for reason in reasons: - violations[reason] += 1 - if len(examples) < 30: - examples.append({'at': list(p), 'reason': reason}) - queue.extend((p[0] + dx, p[1] + dy, p[2] + dz) for dx, dy, dz in N3) - source_air = reader.state(*source) in survey.AIR - reports.append({'id': seed['id'], 'source': list(source), 'source_observed_air': source_air, - 'reachable_voxels': len(reached), 'minimum_reached_y': min((p[1] for p in reached), default=None), - 'maximum_reached_y': max((p[1] for p in reached), default=None), - 'violation_counts': dict(violations), 'examples': examples, - 'passed': source_air and bool(reached) and not violations}) - return {'status': 'checked', 'passed': all(r['passed'] for r in reports), 'bounds': box, - 'observed_voxels': len(blocked) + len(passable), 'authorized_virtual_caps': cap_reports, - 'partial_or_unknown_materials_treated_as_passable': dict(unknown), 'seeds': reports, - 'method': 'Independent 6-neighbor floods from each public room/cabin seed. Known full cubes including glass block movement; partial/unknown shapes are treated as empty. Authorized entrance caps are virtual audit boundaries only.', - 'limits': 'This overestimates continuous player movement and does not simulate teleportation, spectator, block removal or lift operation. Failed floods through partial shapes require review; they are not automatically proven playable escape paths.'} - - -def audit(before, recipe, metadata, after=None): - scope = survey.scope_of(recipe['scope']) - if scope != before.scope or scope != metadata['scope'] or (after and after.scope != scope): - raise ValueError('Recipe, metadata and snapshots differ in project/world/epoch') - desired = base.coordinates(recipe) - for row in recipe['blocks']: - at = tuple(row[axis] for axis in ('x', 'y', 'z')) - if before.state(*at) != row['expected']: - raise ValueError(f'Recipe expected state differs from observed baseline at {at}') - reader = plaza.Reader(after or before, None if after else desired) - floor_report = public_floors(reader, metadata.get('public_floors', [])) - clearance = clear_regions(reader, metadata.get('clear_regions', [])) - access = named_access(reader, metadata) - fixtures = fixture_stability(reader, desired) - containment = conservative_containment(reader, metadata.get('containment')) - volume = base.compare_volume(before, after, desired) if after else None - unproven = [] - if floor_report['status'] == 'not_provided': - unproven.append('Public floor support/headroom/reachability: no clear-column topology supplied.') - if clearance['status'] == 'not_provided': - unproven.append('Doorway and lift cabin clearances: no explicit clearance regions supplied.') - if containment['status'] == 'not_proven': - unproven.append('Windows, floor separation and roof/private-space containment: no flood contract supplied.') - provided = [r for r in (floor_report, clearance, access, fixtures, containment) if r['status'] == 'checked'] - supported_passed = all(r['passed'] for r in provided) and (volume is None or volume['passed']) - return {'version': 1, 'scope': scope, 'world_edits': 0, - 'mode': 'actual_after_snapshot' if after else 'candidate_overlay', 'desired_blocks': len(desired), - 'expected_states_verified_against_baseline': len(desired), 'public_floors': floor_report, - 'clear_regions': clearance, 'named_access': access, 'fixtures': fixtures, - 'containment': containment, 'volume': volume, - 'supported_checks_passed': supported_passed, 'unproven_checks': unproven, - 'passed': supported_passed and not unproven, - 'limits': 'Only observed block states are verified. Block entity text, display entities, gameplay/sign destinations and operational lift transitions require separate runtime checks. Actual snapshots are sequential, not atomic.'} - - -class FakeReader: - def __init__(self, states): - self.states = states - - def state(self, x, y, z): - return self.states.get((x, y, z), 'minecraft:air') - - -class StationTests(unittest.TestCase): - def room(self): - box = {'min': {'x': -1, 'y': 98, 'z': -1}, 'max': {'x': 5, 'y': 127, 'z': 5}} - states = {(x, y, z): 'minecraft:glass' for x in range(5) for y in range(99, 104) for z in range(5) - if x in (0, 4) or z in (0, 4) or y in (99, 103)} - meta = {'bounds': box, 'seeds': [{'id': 'room', 'x': 2, 'y': 100, 'z': 2, 'min_y': 100, 'max_y': 102}]} - return FakeReader(states), meta - - def test_closed_glass_room_is_contained_and_window_hole_is_detected(self): - reader, metadata = self.room() - self.assertTrue(conservative_containment(reader, metadata)['passed']) - del reader.states[0, 101, 2] - result = conservative_containment(reader, metadata) - self.assertFalse(result['passed']) - self.assertIn('survey_boundary_reachable', result['seeds'][0]['violation_counts']) - - def test_authorized_entrance_can_be_virtually_capped_without_changing_world(self): - reader, metadata = self.room() - del reader.states[0, 101, 2] - metadata['authorized_caps'] = [{'id': 'entry', 'min': {'x': 0, 'y': 101, 'z': 2}, 'max': {'x': 0, 'y': 101, 'z': 2}}] - self.assertTrue(conservative_containment(reader, metadata)['passed']) - self.assertEqual(reader.state(0, 101, 2), 'minecraft:air') - - def test_partial_window_is_treated_as_open_and_upper_floor_hole_is_detected(self): - reader, metadata = self.room() - reader.states[0, 101, 2] = 'minecraft:iron_bars[east=false,north=true,south=true,waterlogged=false,west=false]' - self.assertFalse(conservative_containment(reader, metadata)['passed']) - reader, metadata = self.room() - del reader.states[2, 99, 2] - result = conservative_containment(reader, metadata) - self.assertIn('below_public_floor_reachable', result['seeds'][0]['violation_counts']) - - def test_public_headroom_is_four_blocks_and_obstacle_disconnects_floor(self): - reader = FakeReader({(x, 98, 0): 'minecraft:stone' for x in range(3)}) - floor = {'id': 'vestibule', 'standing_y': 99, 'source': {'x': 0, 'z': 0}, 'clear_columns': [[0, 0], [1, 0], [2, 0]]} - self.assertTrue(public_floors(reader, [floor])['passed']) - reader.states[1, 102, 0] = 'minecraft:lantern[hanging=true,waterlogged=false]' - result = public_floors(reader, [floor]) - self.assertFalse(result['passed']) - self.assertEqual(result['floors'][0]['unreachable_columns'], 2) - - def test_clearance_region_reports_real_door_obstruction(self): - reader = FakeReader({(0, 99, 0): 'minecraft:stone'}) - region = {'id': 'door', 'min': {'x': 0, 'y': 99, 'z': 0}, 'max': {'x': 0, 'y': 102, 'z': 0}} - self.assertEqual(clear_regions(reader, [region])['regions'][0]['nonair_voxels'], 1) - - def test_named_target_cannot_be_hidden_by_filtering_the_public_mask(self): - reader = FakeReader({(x, 98, 0): 'minecraft:stone' for x in range(2)}) - metadata = {'public_floors': [{'standing_y': 99, 'clear_columns': [[0, 0]]}], - 'interior': {'navigation': {'99': {'named_targets': [ - {'name': 'bench-front', 'x': 1, 'y': 99, 'z': 0}]}}, 'lift': {}}} - self.assertFalse(named_access(reader, metadata)['passed']) - metadata['public_floors'][0]['clear_columns'].append([1, 0]) - self.assertTrue(named_access(reader, metadata)['passed']) - - def test_fixture_leaf_distance_and_lantern_support_use_actual_states(self): - desired = {(0, 100, 0): 'minecraft:lantern[hanging=true,waterlogged=false]', - (2, 100, 0): 'minecraft:spruce_leaves[distance=1,persistent=true,waterlogged=false]'} - reader = FakeReader(desired | {(0, 101, 0): 'minecraft:stone', - (2, 99, 0): 'minecraft:spruce_log[axis=y]'}) - self.assertTrue(fixture_stability(reader, desired)['passed']) - del reader.states[0, 101, 0] - del reader.states[2, 99, 0] - self.assertEqual(len(fixture_stability(reader, desired)['failures']), 2) - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - for name in ('before', 'after', 'recipe', 'metadata', 'report'): - parser.add_argument('--' + name, type=Path) - parser.add_argument('--self-test', action='store_true') - args = parser.parse_args() - if args.self_test: - result = unittest.TextTestRunner().run(unittest.defaultTestLoader.loadTestsFromTestCase(StationTests)) - raise SystemExit(0 if result.wasSuccessful() else 1) - if any(getattr(args, name) is None for name in ('before', 'recipe', 'metadata', 'report')): - parser.error('--before, --recipe, --metadata and --report are required') - before = survey.load_snapshot(args.before) - after = survey.load_snapshot(args.after, before.scope) if args.after else None - report = audit(before, json.loads(args.recipe.read_text()), json.loads(args.metadata.read_text()), after) - paths = {name: getattr(args, name) for name in ('before', 'after', 'recipe', 'metadata') if getattr(args, name)} - report['input_sha256'] = {key: hashlib.sha256(path.read_bytes()).hexdigest() for key, path in paths.items()} - args.report.parent.mkdir(parents=True, exist_ok=True) - survey.terrain.save(args.report, report) - print(json.dumps(report, indent=2)) - raise SystemExit(0 if report['passed'] else 2 if report['supported_checks_passed'] and report['unproven_checks'] else 1) - - -if __name__ == '__main__': - main() diff --git a/scripts/verify-trapdoor-contact.py b/scripts/verify-trapdoor-contact.py new file mode 100644 index 0000000..06f7867 --- /dev/null +++ b/scripts/verify-trapdoor-contact.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""Read-only trapdoor panel/contact checks for actual or overlaid block readers. + +Pass a ``state_reader(x, y, z)`` returning canonical block data. Each fixture is +``{'pos': [x,y,z], 'support': [x,y,z]}`` for an adjacent support cell, or uses +``'edge': 'south'`` for an intended edge within the trapdoor's own cell. +Support materials and partial support shapes must be validated by the caller; +this check measures contact with that cell's face and rejects an empty cell. + +Shape source: pinned Paper 26.2 TrapDoorBlock.getShape / static initializer, +Block.boxZ(16,13,16), Shapes.rotateAll. An open NORTH trapdoor occupies the +SOUTH 3/16 strip. HALF affects closed panels only. No world/file/network writes. + +>>> world = {(0, 0, 0): 'minecraft:spruce_trapdoor[facing=east,half=bottom,open=true]', (1, 0, 0): 'minecraft:spruce_planks'} +>>> read = lambda x, y, z: world.get((x,y,z), 'minecraft:air') +>>> check_contact(read, (0,0,0), support=(1,0,0))['gap_blocks'] +0.8125 +>>> world[(0,0,0)] = 'minecraft:spruce_trapdoor[open=true,facing=west,half=top]' +>>> check_contact(read, (0,0,0), support=(1,0,0))['passed'] +True +>>> check_contact(read, (0,0,0), edge='north')['passed'] +False +>>> world[(0,0,0)] = 'minecraft:iron_trapdoor[open=false,facing=south,half=top]' +>>> check_contact(read, (0,0,0), edge='up')['passed'] +True +""" + +NORMALS = {'west': (-1, 0, 0), 'east': (1, 0, 0), + 'down': (0, -1, 0), 'up': (0, 1, 0), + 'north': (0, 0, -1), 'south': (0, 0, 1)} +THICKNESS = 3 / 16 +AIR = {'minecraft:air', 'minecraft:cave_air', 'minecraft:void_air'} + + +def panel_bounds(state): + """Return local AABB minimum/maximum and its thickness axis (X=0,Y=1,Z=2).""" + name, separator, raw = state.partition('[') + if not name.startswith('minecraft:') or not name.endswith('_trapdoor') or not separator or not raw.endswith(']'): + raise ValueError('Expected explicit trapdoor block data') + values = dict(part.split('=', 1) for part in raw[:-1].split(',')) + if values.get('open') == 'true': + # Bounds are in block units; the panel sits opposite its facing. + strips = {'north': (2, 1 - THICKNESS, 1), 'south': (2, 0, THICKNESS), + 'east': (0, 0, THICKNESS), 'west': (0, 1 - THICKNESS, 1)} + if values.get('facing') not in strips: + raise ValueError('Open trapdoor needs a horizontal facing') + axis, start, end = strips[values['facing']] + elif values.get('open') == 'false' and values.get('half') in {'top', 'bottom'}: + axis = 1 + start, end = (1 - THICKNESS, 1) if values['half'] == 'top' else (0, THICKNESS) + else: + raise ValueError('Trapdoor needs explicit open and valid half properties') + minimum, maximum = [0., 0., 0.], [1., 1., 1.] + minimum[axis], maximum[axis] = start, end + return minimum, maximum, axis + + +def _position(pos): + result = tuple(pos) + if len(result) != 3 or any(type(value) is not int for value in result): + raise ValueError('Position must contain three integer block coordinates') + return result + + +def check_contact(state_reader, pos, *, support=None, edge=None): + """Measure a panel's gap/alignment to a requested edge or adjacent cell face. + + ``passed`` requires the broad panel face to be parallel and flush. Merely + touching with a thin side edge does not pass. A support fixture also requires + a nonair observed support; this is not a full support collision-shape audit. + """ + pos = _position(pos) + if (support is None) == (edge is None): + raise ValueError('Specify exactly one adjacent support or intended edge') + support_state = None + if support is not None: + support = _position(support) + delta = tuple(target - origin for origin, target in zip(pos, support)) + edge = next((name for name, normal in NORMALS.items() if normal == delta), None) + if edge is None: + raise ValueError('Support must share one face with the trapdoor cell') + support_state = state_reader(*support) + if edge not in NORMALS: + raise ValueError('Unknown intended edge') + state = state_reader(*pos) + minimum, maximum, thickness_axis = panel_bounds(state) + normal = NORMALS[edge] + axis = next(i for i, value in enumerate(normal) if value) + gap = minimum[axis] if normal[axis] < 0 else 1 - maximum[axis] + parallel = axis == thickness_axis + support_present = support_state is None or support_state.partition('[')[0] not in AIR + result = {'pos': list(pos), 'state': state, 'edge': edge, + 'panel_bounds': {'min': minimum, 'max': maximum}, + 'panel_parallel_to_face': parallel, 'gap_blocks': gap, + 'passed': parallel and gap == 0 and support_present} + if support is not None: + result.update(support=list(support), support_state=support_state, + support_present=support_present) + return result + + +def audit_contacts(state_reader, fixtures): + """Return a compact summary and per-fixture geometric evidence.""" + checks = [] + for fixture in fixtures: + result = check_contact(state_reader, fixture['pos'], support=fixture.get('support'), edge=fixture.get('edge')) + if 'name' in fixture: + result['name'] = fixture['name'] + checks.append(result) + failures = [check for check in checks if not check['passed']] + return {'passed': not failures, 'checked': len(checks), 'failed': len(failures), + 'max_gap_blocks': max((check['gap_blocks'] for check in checks), default=0), + 'checks': checks} + + +if __name__ == '__main__': + import doctest + result = doctest.testmod() + raise SystemExit(bool(result.failed)) diff --git a/scripts/verify-zone-containment.py b/scripts/verify-zone-containment.py deleted file mode 100644 index 25515ef..0000000 --- a/scripts/verify-zone-containment.py +++ /dev/null @@ -1,384 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only proof of a closed, observed full-cube Minecraft zone enclosure. - -The membrane is derived independently from an extruded XZ mask minus explicit -excluded voxels: every six-neighbor exterior shell voxel must be a full cube. An -exterior flood treats every unknown/partial block as empty, overestimating escape -routes. Swept 0.6 x 1.8 player AABB probes additionally test cardinal/diagonal -crossings, half-block stair rises, floor drops and roof ascent. These sample -probes supplement the complete membrane proof; they are not the proof itself. - -This verifies static continuous movement, not spectator, commands, breaking, -plugin teleportation, or arbitrary discontinuous teleport/pearl behavior. -""" -import argparse -from collections import Counter, deque -import hashlib -import importlib.util -import itertools -import json -import math -from pathlib import Path -import unittest - -ROOT = Path(__file__).resolve().parents[1] -SPEC = importlib.util.spec_from_file_location('containment_base', ROOT / 'scripts/verify-balustrade.py') -base = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(base) -survey, plaza = base.survey, base.plaza -FULL = plaza.STATIC_CUBES | {'barrier'} -N2 = ((1, 0), (-1, 0), (0, 1), (0, -1)) -N3 = ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)) -WIDTH, HEIGHT = .6, 1.8 - - -def full_cube(state): - return plaza.parse(state)[0] in FULL - - -def mask_geometry(metadata): - raw = metadata['interior_columns'] - if (not isinstance(raw, list) or not raw or any(not isinstance(p, list) or len(p) != 2 - or any(type(v) is not int for v in p) for p in raw)): - raise ValueError('interior_columns must be nonempty integer [x,z] pairs') - interior = set(map(tuple, raw)) - if len(interior) != len(raw): - raise ValueError('Duplicate interior column') - floor, roof = metadata['floor_y'], metadata['roof_y'] - if type(floor) is not int or type(roof) is not int or roof - floor < 4: - raise ValueError('Integer floor_y and roof_y need at least three free interior rows') - shell = {(x + dx, z + dz) for x, z in interior for dx, dz in N2} - interior - footprint = interior | shell - reached, queue = set(), deque([next(iter(interior))]) - while queue: - p = queue.popleft() - if p in reached or p not in interior: - continue - reached.add(p) - queue.extend((p[0] + dx, p[1] + dz) for dx, dz in N2) - if reached != interior: - raise ValueError('Interior mask must be one cardinally connected component') - for key in ('wall_columns', 'shell_columns'): - if ('interior_excluded_voxels' not in metadata and key in metadata - and set(map(tuple, metadata[key])) != shell): - raise ValueError(f'Claimed {key} differ from the independently derived complete shell') - bounds = ((min(x for x, z in footprint) - 1, floor - 1, min(z for x, z in footprint) - 1), - (max(x for x, z in footprint) + 1, roof + 1, max(z for x, z in footprint) + 1)) - if math.prod(hi - lo + 1 for lo, hi in zip(*bounds)) > survey.MAX_VOXELS: - raise ValueError('Enclosure verification exceeds the bounded snapshot voxel limit') - raw_excluded = metadata.get('interior_excluded_voxels', []) - if (not isinstance(raw_excluded, list) or any(not isinstance(p, list) or len(p) != 3 - or any(type(v) is not int for v in p) for p in raw_excluded)): - raise ValueError('interior_excluded_voxels must contain integer [x,y,z] triples') - excluded = set(map(tuple, raw_excluded)) - if len(excluded) != len(raw_excluded): - raise ValueError('Duplicate excluded interior voxel') - volume = {(x, y, z) for x, z in interior for y in range(floor + 1, roof)} - if not excluded <= volume: - raise ValueError('Excluded voxel is outside the base extruded interior') - volume -= excluded - if not volume: - raise ValueError('Excluded voxels remove the whole enclosure interior') - membrane_voxels = {(x + dx, y + dy, z + dz) for x, y, z in volume for dx, dy, dz in N3} - volume - return interior, shell, footprint, floor, roof, bounds, volume, membrane_voxels, excluded - - -def membrane(reader, geometry): - interior, _, _, floor, roof, _ = geometry[:6] - failures, materials, counts = [], Counter(), Counter() - for x, y, z in sorted(geometry[7]): - kind = ('floor' if y == floor else 'roof' if y == roof else - 'wall' if (x, z) not in interior else 'folded_boundary') - counts[kind] += 1 - state = reader.state(x, y, z) - materials[plaza.parse(state)[0]] += 1 - if not full_cube(state): - failures.append({'at': [x, y, z], 'part': kind, 'actual': state}) - return {'passed': not failures, 'required_voxels': sum(counts.values()), 'parts': dict(counts), - 'observed_materials': dict(materials), 'nonfull_voxels': len(failures), 'examples': failures[:30], - 'method': 'Every voxel in N6(interior volume) minus interior volume must be an observed full collision cube.'} - - -def conservative_exterior_flood(reader, geometry, source): - lo, hi = geometry[5] - blocked, passable, unknown = set(), set(), Counter() - boundary = [] - for x in range(lo[0], hi[0] + 1): - for y in range(lo[1], hi[1] + 1): - for z in range(lo[2], hi[2] + 1): - p = x, y, z - state = reader.state(*p) - if full_cube(state): - blocked.add(p) - continue - passable.add(p) - if state not in survey.AIR: - unknown[plaza.parse(state)[0]] += 1 - if any(v in (low, high) for v, low, high in zip(p, lo, hi)): - boundary.append(p) - reached, queue = set(boundary), deque(boundary) - while queue: - x, y, z = queue.popleft() - for dx, dy, dz in N3: - p = x + dx, y + dy, z + dz - if p in passable and p not in reached: - reached.add(p) - queue.append(p) - leaks = sorted(reached & geometry[6]) - source_cell = tuple(math.floor(v) for v in source) - return {'passed': not leaks, 'observed_voxels': len(blocked) + len(passable), - 'exterior_reached_voxels': len(reached), 'interior_reached_from_exterior': len(leaks), - 'source_reached_from_exterior': source_cell in reached, - 'unknown_or_partial_blocks_treated_as_empty': dict(unknown), 'leak_examples': leaks[:30], - 'method': 'Six-neighbor free-voxel exterior flood; only known full collision cubes obstruct it.'} - - -def segment_box(start, finish, low, high): - """Slab intersection with the interior of an expanded block AABB. - - Face contact is legal: a player's feet resting exactly on paving must not - make every horizontal probe appear blocked before reaching the guard. - """ - enter, leave = 0., 1. - for a, b, lo, hi in zip(start, finish, low, high): - lo, hi = lo + 1e-9, hi - 1e-9 - delta = b - a - if abs(delta) < 1e-12: - if a < lo or a > hi: - return False - continue - first, last = sorted(((lo - a) / delta, (hi - a) / delta)) - enter, leave = max(enter, first), min(leave, last) - if enter > leave: - return False - return True - - -def swept_player_hits_full_cube(reader, start, finish): - radius = WIDTH / 2 - low = [math.floor(min(start[i], finish[i]) - (radius if i != 1 else 0)) for i in range(3)] - high = [math.floor(max(start[i], finish[i]) + (radius if i != 1 else HEIGHT)) for i in range(3)] - for x in range(low[0], high[0] + 1): - for y in range(low[1], high[1] + 1): - for z in range(low[2], high[2] + 1): - if full_cube(reader.state(x, y, z)) and segment_box( - start, finish, (x - radius, y - HEIGHT, z - radius), - (x + 1 + radius, y + 1, z + 1 + radius)): - return True - return False - - -def player_probes(reader, geometry, source): - interior, _, _, floor, roof, _ = geometry[:6] - counts, missed = Counter(), [] - source_clear = all(reader.state(x, y, z) in survey.AIR - for x in range(math.floor(source[0] - WIDTH / 2), math.floor(source[0] + WIDTH / 2) + 1) - for y in range(math.floor(source[1]), math.ceil(source[1] + HEIGHT)) - for z in range(math.floor(source[2] - WIDTH / 2), math.floor(source[2] + WIDTH / 2) + 1)) - # Cross each boundary in cardinal and diagonal directions, including half- - # block stair rises/drops. Fixed-height samples also cover free flight rows. - heights = sorted({float(floor + 1), float(source[1]), roof - HEIGHT - .05}) - for x, z in sorted(interior): - for dx, dz in itertools.product((-1, 0, 1), repeat=2): - if (dx == dz == 0) or (x + dx, z + dz) in interior: - continue - for y in heights: - for dy in (-.5, 0., .5): - start = (x + .5, y, z + .5) - finish = (x + dx + .5, y + dy, z + dz + .5) - kind = 'diagonal' if dx and dz else 'cardinal' - counts[kind] += 1 - if not swept_player_hits_full_cube(reader, start, finish): - missed.append({'kind': kind, 'from': start, 'to': finish}) - # Test the locally folded boundary as well as the original outer perimeter. - # The complete membrane check covers every height even when a sample starts - # in an already obstructed partial-height pocket. - for x, y, z in sorted(geometry[8] & geometry[7]): - for dx, dy, dz in N3: - p = x + dx, y + dy, z + dz - if p not in geometry[6]: - continue - start, finish = (p[0] + .5, float(p[1]), p[2] + .5), (x + .5, float(y), z + .5) - counts['folded_boundary'] += 1 - if not swept_player_hits_full_cube(reader, start, finish): - missed.append({'kind': 'folded_boundary', 'from': start, 'to': finish}) - for kind, start, finish in [('floor', (source[0], floor + 1.1, source[2]), - (source[0], floor - .5, source[2])), - ('roof', (source[0], roof - HEIGHT - .1, source[2]), - (source[0], roof - .5, source[2]))]: - counts[kind] += 1 - if not swept_player_hits_full_cube(reader, start, finish): - missed.append({'kind': kind, 'from': start, 'to': finish}) - return {'passed': source_clear and not missed, 'player_width': WIDTH, 'player_height': HEIGHT, - 'source_headroom_observed_air': source_clear, 'swept_crossing_probes': dict(counts), - 'unblocked_probes': len(missed), 'examples': missed[:30], - 'note': 'Supplementary swept-AABB boundary probes; the independently derived full membrane is the complete static containment criterion.'} - - -def volume_components(reader, geometry, source): - """Report geometric components; every one is still checked for containment.""" - pending, components = set(geometry[6]), [] - source_cell = tuple(math.floor(v) for v in source) - while pending: - queue, count, nonfull, contains_source = deque([next(iter(pending))]), 0, 0, False - while queue: - p = queue.popleft() - if p not in pending: - continue - pending.remove(p) - count += 1 - nonfull += not full_cube(reader.state(*p)) - contains_source |= p == source_cell - queue.extend((p[0] + dx, p[1] + dy, p[2] + dz) for dx, dy, dz in N3) - components.append({'voxels': count, 'nonfull_voxels': nonfull, 'contains_source': contains_source}) - return {'components': sorted(components, key=lambda c: c['voxels'], reverse=True), - 'source_in_volume': any(c['contains_source'] for c in components), - 'note': 'Disconnected sealed components are reported, not treated as escapes. The entire required membrane and exterior flood are checked.'} - - -def preserved_decor(before, reader, desired): - protected, failures = 0, [] - for at, state in base.captured_voxels(before): - name, _ = plaza.parse(state) - if (name not in plaza.FLOWERS | {'lantern', 'spruce_fence', 'spruce_stairs', 'iron_chain'} - and not any(suffix in name for suffix in ('_leaves', '_log', 'waxed_oxidized_cut_copper'))): - continue - protected += 1 - if reader.state(*at) != state: - failures.append({'at': list(at), 'before': state, 'after': reader.state(*at), 'in_plan': at in desired}) - return {'passed': not failures, 'protected_observed_states': protected, 'changed_states': len(failures), - 'examples': failures[:30]} - - -def audit(before, layout, metadata, after=None): - scope = survey.scope_of(layout['scope']) - if scope != before.scope or scope != metadata['scope'] or (after and after.scope != scope): - raise ValueError('Project/world/epoch differs between containment inputs') - desired = base.coordinates(layout) - for row in layout['blocks']: - p = tuple(row[a] for a in ('x', 'y', 'z')) - if before.state(*p) != row['expected']: - raise ValueError(f'Expected plan state differs from observed baseline at {p}') - source = tuple(metadata['source'][axis] for axis in ('x', 'y', 'z')) - if any(type(v) not in (int, float) or not math.isfinite(v) for v in source): - raise ValueError('Source must contain finite x/y/z player feet coordinates') - geometry = mask_geometry(metadata) - if tuple(math.floor(v) for v in source) not in geometry[6] or not geometry[3] < source[1] < geometry[4] - HEIGHT: - raise ValueError('Source is outside the enclosure interior') - reader = plaza.Reader(after or before, None if after else desired) - closed = membrane(reader, geometry) - flood = conservative_exterior_flood(reader, geometry, source) - probes = player_probes(reader, geometry, source) - components = volume_components(reader, geometry, source) - decor = preserved_decor(before, reader, desired) - volume = base.compare_volume(before, after, desired) if after else None - return {'version': 1, 'scope': scope, 'world_edits': 0, - 'mode': 'actual_after_snapshot' if after else 'candidate_overlay', - 'desired_blocks': len(desired), 'interior_columns': len(geometry[0]), - 'independently_derived_wall_columns': len(geometry[1]), - 'interior_voxels': len(geometry[6]), 'interior_excluded_voxels': len(geometry[8]), - 'volume_components': components, - 'membrane': closed, 'exterior_flood': flood, 'player_probes': probes, - 'preserved_decor': decor, 'volume': volume, - 'passed': all(r['passed'] for r in (closed, flood, probes, decor)) and (volume is None or volume['passed']), - 'limitations': 'Static continuous collision containment for normal nonspectator players only. No protection against spectator, breaking/removing blocks, operator commands, plugin teleports or arbitrary discontinuous teleport/pearl behavior. Actual snapshots are sequential, not atomic.'} - - -class ContainmentTests(unittest.TestCase): - def scene(self): - meta = {'interior_columns': [[0, 0], [1, 0], [0, 1], [1, 1]], 'floor_y': 0, 'roof_y': 5} - geo = mask_geometry(meta) - states = {} - for x, z in geo[2]: - for y in ([0, 5] if (x, z) in geo[0] else range(6)): - states[x, y, z] = 'minecraft:barrier' - reader = type('Reader', (), {'state': lambda self, x, y, z: states.get((x, y, z), 'minecraft:air')})() - return meta, geo, states, reader - - def test_complete_shell_blocks_flight_diagonals_stairs_and_drops(self): - _, geo, _, reader = self.scene() - self.assertTrue(membrane(reader, geo)['passed']) - self.assertTrue(conservative_exterior_flood(reader, geo, (.5, 1., .5))['passed']) - self.assertTrue(player_probes(reader, geo, (.5, 1., .5))['passed']) - - def test_one_missing_wall_or_roof_voxel_leaks(self): - for at in [(-1, 2, 0), (0, 5, 0), (0, 0, 0)]: - _, geo, states, reader = self.scene() - del states[at] - self.assertFalse(membrane(reader, geo)['passed']) - self.assertFalse(conservative_exterior_flood(reader, geo, (.5, 1., .5))['passed']) - - def test_floor_need_not_extend_under_exterior_wall(self): - _, geo, states, reader = self.scene() - for x, z in geo[1]: - states.pop((x, 0, z), None) - self.assertTrue(membrane(reader, geo)['passed']) - self.assertTrue(conservative_exterior_flood(reader, geo, (.5, 1., .5))['passed']) - - def test_inward_fold_preserves_partial_stair_outside_and_missing_fold_leaks(self): - meta, _, states, reader = self.scene() - meta['interior_excluded_voxels'] = [[0, 2, 0]] - geo = mask_geometry(meta) - states[-1, 2, 0] = 'minecraft:stone_brick_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]' - states[0, 2, 0] = 'minecraft:barrier' - self.assertNotIn((-1, 2, 0), geo[7]) - self.assertTrue(membrane(reader, geo)['passed']) - self.assertTrue(conservative_exterior_flood(reader, geo, (1.5, 1., 1.5))['passed']) - self.assertTrue(player_probes(reader, geo, (1.5, 1., 1.5))['passed']) - del states[0, 2, 0] - self.assertFalse(membrane(reader, geo)['passed']) - self.assertFalse(conservative_exterior_flood(reader, geo, (1.5, 1., 1.5))['passed']) - - def test_exclusions_must_be_unique_voxels_inside_original_volume(self): - meta, _, _, _ = self.scene() - for exclusions in [[[0, 2, 0], [0, 2, 0]], [[100, 2, 100]], [[0, 0, 0]]]: - meta['interior_excluded_voxels'] = exclusions - with self.assertRaises(ValueError): - mask_geometry(meta) - - def test_stair_or_slab_cannot_substitute_for_solid_membrane(self): - _, geo, states, reader = self.scene() - states[-1, 2, 0] = 'minecraft:stone_brick_slab[type=bottom,waterlogged=false]' - self.assertFalse(membrane(reader, geo)['passed']) - self.assertFalse(conservative_exterior_flood(reader, geo, (.5, 1., .5))['passed']) - states[-1, 2, 0] = 'minecraft:stone' - self.assertTrue(membrane(reader, geo)['passed']) - - def test_swept_aabb_detects_diagonal_corner_and_vertical_collision(self): - reader = type('Reader', (), {'state': lambda self, x, y, z: 'minecraft:barrier' if (x, y, z) == (1, 1, 1) else 'minecraft:air'})() - self.assertTrue(swept_player_hits_full_cube(reader, (.5, 1., .5), (2.5, 1., 2.5))) - self.assertFalse(swept_player_hits_full_cube(reader, (.2, 1., .2), (.2, 2., .2))) - - def test_standing_contact_with_floor_does_not_mask_an_open_horizontal_route(self): - reader = type('Reader', (), {'state': lambda self, x, y, z: 'minecraft:stone' if y == 0 else 'minecraft:air'})() - self.assertFalse(swept_player_hits_full_cube(reader, (.5, 1., .5), (2.5, 1., 2.5))) - self.assertTrue(swept_player_hits_full_cube(reader, (.5, 1., .5), (.5, .5, .5))) - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--before', type=Path) - parser.add_argument('--after', type=Path) - parser.add_argument('--layout', type=Path) - parser.add_argument('--metadata', type=Path) - parser.add_argument('--report', type=Path) - parser.add_argument('--self-test', action='store_true') - args = parser.parse_args() - if args.self_test: - result = unittest.TextTestRunner().run(unittest.defaultTestLoader.loadTestsFromTestCase(ContainmentTests)) - raise SystemExit(0 if result.wasSuccessful() else 1) - if any(getattr(args, key) is None for key in ('before', 'layout', 'metadata', 'report')): - parser.error('--before, --layout, --metadata and --report are required') - before = survey.load_snapshot(args.before) - after = survey.load_snapshot(args.after, before.scope) if args.after else None - report = audit(before, json.loads(args.layout.read_text()), json.loads(args.metadata.read_text()), after) - paths = {key: getattr(args, key) for key in ('before', 'after', 'layout', 'metadata') if getattr(args, key)} - report['input_sha256'] = {key: hashlib.sha256(path.read_bytes()).hexdigest() for key, path in paths.items()} - args.report.parent.mkdir(parents=True, exist_ok=True) - survey.terrain.save(args.report, report) - print(json.dumps(report, indent=2)) - raise SystemExit(0 if report['passed'] else 1) - - -if __name__ == '__main__': - main() diff --git a/scripts/voxel-island.py b/scripts/voxel-island.py new file mode 100644 index 0000000..be9895e --- /dev/null +++ b/scripts/voxel-island.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Deterministic floating-island columns, independent of any map or server. + +Large contour harmonics shape the silhouette; correlated low-frequency fields +shape the underside. Surface grading and gameplay layout belong to the caller. +This is an offline geometry helper, not an additional MCP tool or live edit API. +""" +import math + + +def smooth(value): + value = max(0., min(1., value)) + return value * value * (3 - 2 * value) + + +def hash_value(x, z, seed): + n = (x * 374761393 + z * 668265263 + seed * 1442695041) & 0xffffffff + n = ((n ^ (n >> 13)) * 1274126177) & 0xffffffff + return (n ^ (n >> 16)) / 0xffffffff + + +def noise(x, z, seed): + x0, z0 = math.floor(x), math.floor(z) + u, v = smooth(x - x0), smooth(z - z0) + a = hash_value(x0, z0, seed) * (1 - u) + hash_value(x0 + 1, z0, seed) * u + b = hash_value(x0, z0 + 1, seed) * (1 - u) + hash_value(x0 + 1, z0 + 1, seed) * u + return a * (1 - v) + b * v + + +def columns(cx, cz, radius_x, radius_z, surface_y, depth, seed, contour=.055): + """Return {(x,z): {bottom, top, radial}} with inclusive occupied Y ranges. + + ``radial`` is normalized distance from the warped boundary (0 centre,1 rim). + The terrain is a connected solid below each top, with no inferred live air. + Neighbouring columns have coherent variation; random per-block noise is left + out deliberately. Integers give reproducible inclusive world coordinates. + """ + values = (radius_x, radius_z, depth, contour) + if not all(math.isfinite(v) for v in values) or min(radius_x, radius_z, depth) <= 0 or not 0 <= contour <= .2: + raise ValueError('Use positive finite radii/depth and contour in0..0.2') + if any(type(v) is not int for v in (cx, cz, surface_y, seed)): + raise ValueError('Centre, surface and seed must be integers') + result = {} + for x in range(math.floor(cx - radius_x * 1.3), math.ceil(cx + radius_x * 1.3) + 1): + for z in range(math.floor(cz - radius_z * 1.3), math.ceil(cz + radius_z * 1.3) + 1): + dx, dz = (x - cx) / radius_x, (z - cz) / radius_z + angle = math.atan2(dz, dx) + boundary = 1 + contour * (math.sin(3 * angle + seed * .07) + + .45 * math.sin(7 * angle - seed * .11)) + radial = math.hypot(dx, dz) / boundary + if radial > 1: + continue + # A broad taper and coherent hanging buttresses give a recognisable + # mass even when viewed in silhouette, before material decoration. + taper = max(0., 1 - radial ** 1.55) ** .62 + buttress = .63 + .37 * noise(x / 7.5, z / 7.5, seed + 31) + ribs = 2.5 * smooth(noise(x / 3.8, z / 3.8, seed + 57)) + thickness = max(3, round(3 + depth * taper * buttress + ribs)) + result[x, z] = {'top': surface_y, 'bottom': surface_y - thickness + 1, + 'radial': radial} + return result diff --git a/terrain-world-plugin/README.md b/terrain-world-plugin/README.md deleted file mode 100644 index b8f00cd..0000000 --- a/terrain-world-plugin/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Initial terrain worlds - -## Natural Shacraft profile - -Configure `terrain-profile: shacraft-natural-v1`, copy `examples/terrain/shacraft-natural-world.json` as the recipe, and select a new world name such as `shacraft_lobby_v2`. The recipe supplies the seed, footprint and palette; its feature list must be empty because the versioned profile defines its own landforms. This is not a new MCP terrain recipe type. - -Independent OpenSimplex2S fBm, weighted ridged noise, moderate coordinate warping and authored mountain corridors shape the skyline around soft central hills and connected water courses. There are no rectangular platform features. Before integer flooring, all heights exactly match the approved study's full-field SHA-256. No hydraulic erosion, biome pass or automatic route grading is implied. - -Steep columns expose stone/andesite without a dirt layer. Gentle dry columns retain grass and soil; submerged columns have rock beds and continuous source water. Geometry, profile and material version are bound into the immutable generation identity. Live pre-generation checks every column's height, top material and water fill. - -FastNoiseLite is vendored under `noise/` from upstream commit `785f37a9ad76e283586a379675085f2063ae03f7`, with a package declaration added. Its MIT notice is present in the source and under `META-INF/LICENSE-FastNoiseLite.txt` in the jar. - -## Recipe-based initial worlds - -This optional Paper 26.2 plugin creates and reloads a separate world using the existing `TerrainRecipe` height field. It loads before MinecraftBuilderMCP. Installation is inert until `enabled: true` is explicitly set in `plugins/ShacraftTerrain/config.yml`. - -Build with `./mvnw package`, install `target/terrain-world-plugin-0.1.0-SNAPSHOT.jar`, and copy a sculpt recipe without preserve masks into the plugin data directory as `terrain.json`: - -```yaml -enabled: true -world: shacraft_lobby -recipe: terrain.json -water-level: 48 -border-size: 2048 -``` - -The Shacraft example is `../examples/terrain/shacraft-lobby-world.json`: a 768×768 layout translated 64 blocks upward from the original design recipe. Spawn plaza Y=106; station Y=118; airship harbor Y=124. The west lake has a rock bed at Y=38 and water through Y=48. The ravines share that water level. Omit `water-level` for dry terrain. - -Generation builds solid foundations down to world bedrock, follows the recipe's soil layers, replaces submerged surface blocks with rock and fills submerged columns with source water. There are no vanilla caves, structures, decorations or mobs. The recipe continues outside the footprint so neighboring chunks do not end at artificial vertical cuts. The optional exploration border can sit beyond the design footprint to keep its visible wall out of construction views; it does not expand the editor's authorized bounds. Day and weather are fixed for construction. - -This is **initial world generation**, not an editor apply operation. It has no block-by-block undo journal. Existing chunks are never regenerated. The persisted generation manifest binds the generator version, recipe and water level; changing them under the same world name fails closed. Existing world folders without that manifest are refused. Keep the generator plugin, its configuration and manifest with world backups so future chunks use the same terrain. - -Commands: - -- `/lobby`: teleport yourself to the central spawn. Requires operator permission. -- `/lobby status`: inspect the current pre-generation pass. -- Server console `lobby `: teleport an online player to the lobby. -- Server console `lobby generate`: asynchronously load/generate the recipe footprint one chunk at a time. Verify every column's visible height, water fill and non-air bed; save `generation-report.json` on completion. Do this before building: later edits legitimately produce mismatches. Repeating the command loads existing chunks, without overwriting them. -- Server console `lobby map `: export an actual top-down surface map of the configured footprint, with no player or camera client. For example, `lobby map layout-before` writes `plugins/ShacraftTerrain/maps/layout-before.json` and `layout-before.png`. Use `lobby status` to see progress. Names use 1–48 lowercase letters, digits, underscores or hyphens; an existing map is never overwritten. - -The map exporter reads one existing chunk per tick on the server thread and refuses to generate missing chunks. It changes no blocks. A detached worker renders the PNG and writes the JSON after all columns have been captured. Maps are bounded to 4,194,304 columns. The height and material describe the highest non-air block, including water and survey markers, so bridges and roofs hide the surfaces below. PNGs show actual surface materials with slope shading; colored concrete keeps its unshaded survey color. This is a server-derived orthographic map, not a screenshot or a perspective camera. - -The compact JSON format `minecraft-builder-surface-map-v1` includes the world UUID/key, inclusive X/Z bounds, capture timestamps, `palette`, `palette_rgb`, and flat `surface_y` / `material_index` arrays. Index a column with `(z - min_z) * width + (x - min_x)`. North is at the top (negative Z), east at the right (positive X), and each PNG pixel represents one block. These are live, sequential chunk observations rather than an atomic snapshot: pause edits while capturing a before/after verification map. The exporter does not certify a particular build revision. - -MinecraftBuilderMCP still supports one active project. Before selecting this world in its config, stop the server and archive the previous project's configuration, metadata and journal together. Use a new project ID and world epoch, a fresh journal directory, and explicit project bounds within the new world. Keep normal per-plan limits and owner authorization. The player-facing `/ai area` command retains its 2-million-block selection limit; an operator can configure the full lobby envelope directly for staged work. - -For the local Shacraft session, connect to `127.0.0.1:25575`, then `/lobby`. The old `world` remains loaded; console `execute in minecraft:overworld run tp 0 5 0` can return a player there. The active AI editor remains bound to Shacraft until the old project is restored offline. - -Unit checks cover solid foundations, lake source-water continuity, negative chunk coordinates, the complete design's height bounds and invalid initial-world modes. Live generation reports cover real Paper chunks; they are not screenshots or a visual approval of the composition. - -The first live Shacraft pass generated/loaded 2304 chunks in 144 seconds and verified all 589,824 columns, including 59,609 water columns, with zero mismatches. See [the saved report](../docs/references/shacraft-lobby-generation-report.json). Terrain reaches Y=203. The local server uses `--heap 4G` and `view-distance=32`; the client also needs an adequate render distance. - -Paper 26.2 stores this dimension under `world/dimensions/minecraft/shacraft_lobby`. Back up the complete primary `world` save (including `level.dat`) together with both plugins' data and the generator jar. Copying only a legacy top-level `shacraft_lobby` folder is not a valid backup for this version. - -The live natural v2 pass verified all 589,824 columns across 2304 chunks in 275 seconds: zero height/material/water mismatches, 42,399 water columns, highest surface Y=204. See [the report](../docs/references/shacraft-natural-v2-generation-report.json). The separately retained v1 report describes the earlier rectangular layout. diff --git a/terrain-world-plugin/pom.xml b/terrain-world-plugin/pom.xml deleted file mode 100644 index 0981df0..0000000 --- a/terrain-world-plugin/pom.xml +++ /dev/null @@ -1,15 +0,0 @@ - - 4.0.0 - io.github.minecraftbuilderminecraft-builder-mcp0.1.0-SNAPSHOT - terrain-world-plugin - papermchttps://repo.papermc.io/repository/maven-public/ - - io.github.minecraftbuilderworld-core${project.version} - io.papermc.paperpaper-api${paper.version}provided - com.google.code.gsongson2.14.0 - org.junit.jupiterjunit-jupiter5.13.4test - - - org.apache.maven.pluginsmaven-shade-plugin3.6.1packageshadefalse*:*META-INF/*.SFMETA-INF/*.RSAMETA-INF/*.DSA - - diff --git a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/LobbyRespawn.java b/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/LobbyRespawn.java deleted file mode 100644 index 73ba47f..0000000 --- a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/LobbyRespawn.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerRespawnEvent; - -/** A configured indoor spawn must survive vanilla's highest-surface respawn search. */ -final class LobbyRespawn implements Listener { - private final World world; - private final Location configuredSpawn; - - LobbyRespawn(World world, Location configuredSpawn) { - this.world = world; - this.configuredSpawn = configuredSpawn == null ? null : configuredSpawn.clone(); - } - - @EventHandler - public void onRespawn(PlayerRespawnEvent event) { - if (configuredSpawn != null && event.getRespawnReason() == PlayerRespawnEvent.RespawnReason.DEATH - && event.getPlayer().getWorld() == world) - event.setRespawnLocation(configuredSpawn); - } -} diff --git a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/LobbySpawn.java b/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/LobbySpawn.java deleted file mode 100644 index 858f8e3..0000000 --- a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/LobbySpawn.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; -import java.util.function.IntSupplier; - -/** Explicit player feet position; never derived from a roof or containment heightmap. */ -record LobbySpawn(double x, double y, double z, float yaw, float pitch) { - LobbySpawn { - if (!Double.isFinite(x) || !Double.isFinite(y) || !Double.isFinite(z) - || !Float.isFinite(yaw) || !Float.isFinite(pitch)) - throw new IllegalArgumentException("Spawn coordinates and angles must be finite"); - if (Math.abs(x) >= 30_000_000 || Math.abs(z) >= 30_000_000) - throw new IllegalArgumentException("Spawn coordinates must be inside Minecraft's world limits"); - if (pitch < -90 || pitch > 90) throw new IllegalArgumentException("Spawn pitch must be between -90 and 90"); - } - - static LobbySpawn fromConfig(ConfigurationSection config, IntSupplier fallbackY) { - if (!config.contains("spawn")) return new LobbySpawn(.5, fallbackY.getAsInt(), .5, 180, 0); - ConfigurationSection spawn = config.getConfigurationSection("spawn"); - if (spawn == null) throw new IllegalArgumentException("Spawn must be a section with numeric x, y and z"); - return new LobbySpawn(number(spawn, "x"), number(spawn, "y"), number(spawn, "z"), - (float) (spawn.contains("yaw") ? number(spawn, "yaw") : 180), - (float) (spawn.contains("pitch") ? number(spawn, "pitch") : 0)); - } - - private static double number(ConfigurationSection section, String key) { - if (!(section.get(key) instanceof Number value)) - throw new IllegalArgumentException("Spawn " + key + " must be numeric"); - return value.doubleValue(); - } - - void requireHeight(int minY, int maxY) { - if (y < minY || y >= maxY) throw new IllegalArgumentException("Spawn Y must be inside world height bounds"); - } - - Location location(World world) { - requireHeight(world.getMinHeight(), world.getMaxHeight()); - return new Location(world, x, y, z, yaw, pitch); - } -} diff --git a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/NaturalTerrain.java b/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/NaturalTerrain.java deleted file mode 100644 index e10ab29..0000000 --- a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/NaturalTerrain.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import io.github.minecraftbuilder.terrainworld.noise.FastNoiseLite; - -/** Frozen Shacraft natural-v1 profile, promoted from the approved offline study. - * Noise objects are private and never mutated after construction; sampling is thread safe. - */ -public final class NaturalTerrain { - private static FastNoiseLite noise(int seed, float scale, int octaves, boolean ridged) { - var n = new FastNoiseLite(seed); - n.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2S); - n.SetFrequency(1f / scale); - n.SetFractalType(ridged ? FastNoiseLite.FractalType.Ridged : FastNoiseLite.FractalType.FBm); - n.SetFractalOctaves(octaves); n.SetFractalGain(.48f); n.SetFractalLacunarity(2.07f); - n.SetFractalWeightedStrength(ridged ? .7f : .25f); - return n; - } - private final FastNoiseLite broad, ridges, warpX, warpZ, detail, bank; - public NaturalTerrain(int seed) { - broad=noise(seed,220,4,false); ridges=noise(seed+12,85,5,true); - warpX=noise(seed+8995,165,3,false); warpZ=noise(seed+108995,165,3,false); - detail=noise(seed+208995,17,3,false); bank=noise(seed+308995,52,3,false); - } - public double slope(int x,int z) { - return Math.hypot((height(x+2,z)-height(x-2,z))/4.0,(height(x,z+2)-height(x,z-2))/4.0); - } - public float rockVariation(int x,int z) { return bank.GetNoise(x*2f,z*2f); } - private static final double[][] north={{-370,-225},{-280,-295},{-180,-320},{-70,-270},{55,-295},{170,-330},{275,-270},{370,-235}}; - private static final double[][] west={{-355,-235},{-315,-135},{-360,-30},{-315,95},{-325,245},{-285,370}}; - private static final double[][] east={{345,-235},{320,-100},{355,45},{315,160},{340,300},{285,390}}; - private static final double[][] riverEast={{160,-384},{145,-280},{112,-190},{127,-100},{107,-25},{100,70},{85,145},{110,215},{55,280},{30,384}}; - private static final double[][] riverWest={{-220,65},{-190,125},{-140,175},{-110,235},{-50,285},{-20,345},{30,384}}; - private static double distance(double x,double z,double[][] path) { - double best=Double.POSITIVE_INFINITY; - for(int i=1;i 1.05 + .22 * variation; - if (wet || cliff) return variation > .1 ? Material.ANDESITE : Material.STONE; - return surface; - } - - @Override public void generateNoise(WorldInfo info, Random random, int cx, int cz, ChunkData data) { - for (int x = 0; x < 16; x++) for (int z = 0; z < 16; z++) { - int top = height(cx * 16 + x, cz * 16 + z); - if (waterLevel != null && (waterLevel <= data.getMinHeight() || waterLevel >= data.getMaxHeight() - 1)) - throw new IllegalArgumentException("Water level outside world height"); - if (top <= data.getMinHeight() || top >= data.getMaxHeight() - 1) - throw new IllegalArgumentException("Recipe surface outside world height"); - data.setBlock(x, data.getMinHeight(), z, Material.BEDROCK); - Material topMaterial = surfaceMaterial(cx*16+x,cz*16+z); - int soilDepth = natural != null && topMaterial != surface ? 0 : depth; - int dirtStart = Math.max(data.getMinHeight() + 1, top - soilDepth); - data.setRegion(x, data.getMinHeight() + 1, z, x + 1, dirtStart, z + 1, rock); - data.setRegion(x, dirtStart, z, x + 1, top, z + 1, soil); - data.setBlock(x, top, z, topMaterial); - if (waterLevel != null && top < waterLevel) - data.setRegion(x, top + 1, z, x + 1, waterLevel + 1, z + 1, Material.WATER); - } - } - - @Override public int getBaseHeight(WorldInfo world, Random random, int x, int z, HeightMap map) { - return (map == HeightMap.OCEAN_FLOOR || map == HeightMap.OCEAN_FLOOR_WG ? height(x, z) : visibleHeight(x, z)) + 1; - } - @Override public Location getFixedSpawnLocation(World world, Random random) { - return new Location(world, .5, height(0, 0) + 1, .5, 180, 0); - } - @Override public BiomeProvider getDefaultBiomeProvider(WorldInfo world) { - return new BiomeProvider() { - @Override public Biome getBiome(WorldInfo info, int x, int y, int z) { return Biome.PLAINS; } - @Override public List getBiomes(WorldInfo info) { return List.of(Biome.PLAINS); } - }; - } - @Override public boolean shouldGenerateNoise() { return false; } - @Override public boolean shouldGenerateSurface() { return false; } - @Override public boolean shouldGenerateCaves() { return false; } - @Override public boolean shouldGenerateDecorations() { return false; } - @Override public boolean shouldGenerateMobs() { return false; } - @Override public boolean shouldGenerateStructures() { return false; } -} diff --git a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/StationLabels.java b/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/StationLabels.java deleted file mode 100644 index c6555e6..0000000 --- a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/StationLabels.java +++ /dev/null @@ -1,188 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextColor; -import net.kyori.adventure.text.format.TextDecoration; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Color; -import org.bukkit.Location; -import org.bukkit.NamespacedKey; -import org.bukkit.World; -import org.bukkit.entity.Display; -import org.bukkit.entity.TextDisplay; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.util.Transformation; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -/** Optional, persistent signs for the completed station; no arena destinations are implied. */ -public final class StationLabels { - public static final String TAG = "shacraft_station_v1"; - private static final TextColor INK = TextColor.color(0xf3dfab); - private static final Map ACTIVE = new HashMap<>(); - - /** Position is the visual centre; yaw 0 faces south and yaw -90 faces east. */ - public record LabelSpec(String id, String text, double x, double y, double z, - float yaw, float scale, int lineWidth, boolean bold) {} - - private static final List LABELS = labels(); - - private StationLabels() {} - - /** Immutable positions/text for a build manifest or a review before installation. */ - public static List manifest() { return LABELS; } - - /** - * Schedule installation on the server thread after existing label chunks have - * loaded their saved entities. This prevents duplicate labels after a restart. - * No terrain is generated. Only this plugin's marked TextDisplays are replaced. - */ - public static void install(JavaPlugin plugin, World world) { - Objects.requireNonNull(plugin); Objects.requireNonNull(world); - if (!Bukkit.isPrimaryThread()) throw new IllegalStateException("Station labels must be scheduled on the server thread"); - Installation installation = new Installation(plugin, world); - Installation previous = ACTIVE.put(world.getUID(), installation); - if (previous != null) previous.close(); - installation.load(); - } - - private static List labels() { - List labels = new ArrayList<>(); - labels.add(new LabelSpec("exterior-shacraft", "SHACRAFT", -5.5, 111.2, -81.94, 0, 6.4f, 160, true)); - labels.add(new LabelSpec("smash-header", "S M A S H", -16.5, 122.2, -136.94, 0, 7f, 160, true)); - int[] bayCenters = {-42, -32, -22, -12, -2, 8}; - for (int i = 0; i < bayCenters.length; i++) { - String number = String.format(java.util.Locale.ROOT, "%02d", i + 1); - labels.add(new LabelSpec("arena-" + number, number, - bayCenters[i] + .5, 119.6, -134.95, 0, 1.2f, 120, false)); - labels.add(new LabelSpec("arena-" + number + "-sign", "АРЕНА " + number + "\nСКОРО", - bayCenters[i] + .5, 114.5, -134.95, 0, 1f, 120, false)); - } - for (int floor = 1; floor <= 2; floor++) { - int feet = floor == 1 ? 99 : 113; - labels.add(new LabelSpec("lift-" + floor + "-front", floor == 1 ? "1 · ВЕСТИБЮЛЬ" : "2 · SMASH", - -5.5, feet + 6.2, -114.93, 0, 2.4f, 160, true)); - labels.add(new LabelSpec("lift-" + floor + "-selector", floor == 1 ? "2 SMASH ↑" : "1 ВЕСТИБЮЛЬ ↓", - -5.5, feet + 3.3, -121.92, 0, 1.65f, 160, true)); - labels.add(new LabelSpec("lift-" + floor + "-instruction", "ПКМ по золотой\nпанели", - -5.5, feet + 1.3, -121.92, 0, 1.2f, 120, false)); - } - labels.add(new LabelSpec("vestibule-lift-heading", "SHACRAFT", -5.5, 108, -114.93, 0, 3f, 160, true)); - labels.add(new LabelSpec("smash-damage", "УРОН\nЧем выше урон,\nтем сильнее\nотбрасывание", - -70.96, 116.9, -121.5, -90, 1.1f, 120, false)); - labels.add(new LabelSpec("smash-knockback", "ОТБРАСЫВАНИЕ\nСтолкни соперников\nс острова", - -70.96, 116.9, -114.5, -90, 1.1f, 120, false)); - labels.add(new LabelSpec("smash-double-jump", "ДВОЙНОЙ ПРЫЖОК\nПрыгни ещё раз,\nчтобы вернуться\nна остров", - -70.96, 116.9, -107.5, -90, 1.1f, 120, false)); - return List.copyOf(labels); - } - - private static void configure(TextDisplay display, LabelSpec label, NamespacedKey owner, NamespacedKey id) { - display.text(Component.text(label.text(), INK).decoration(TextDecoration.BOLD, label.bold())); - display.setBillboard(Display.Billboard.FIXED); - display.setRotation(label.yaw(), 0); - display.setAlignment(TextDisplay.TextAlignment.CENTER); - display.setLineWidth(label.lineWidth()); - display.setDefaultBackground(false); - display.setBackgroundColor(Color.fromARGB(0, 0, 0, 0)); - display.setSeeThrough(false); - display.setShadowed(true); - display.setTextOpacity((byte) 255); - display.setBrightness(new Display.Brightness(15, 15)); - display.setViewRange(label.id().equals("exterior-shacraft") ? 2f : 1f); - display.setShadowRadius(0); display.setShadowStrength(0); - display.setInterpolationDuration(0); display.setTeleportDuration(0); - float lines = label.text().split("\n", -1).length; - // Minecraft text is bottom-centred and uses 0.025 blocks per font pixel. - display.setTransformation(new Transformation(new Vector3f(0, -.125f * label.scale() * lines, 0), - new Quaternionf(), new Vector3f(label.scale()), new Quaternionf())); - // Disable the optional display bounding-box cull; normal view distance still applies. - display.setDisplayWidth(0); display.setDisplayHeight(0); - display.setPersistent(true); - display.addScoreboardTag(TAG); - display.getPersistentDataContainer().set(owner, PersistentDataType.STRING, TAG); - display.getPersistentDataContainer().set(id, PersistentDataType.STRING, label.id()); - } - - private static final class Installation { - private final JavaPlugin plugin; - private final World world; - private final List ownedTickets = new ArrayList<>(); - private List chunks = List.of(); - private boolean closed; - private int attempts; - - Installation(JavaPlugin plugin, World world) { this.plugin = plugin; this.world = world; } - - void load() { - record Column(int x, int z) {} - var columns = new LinkedHashSet(); - for (LabelSpec label : LABELS) - columns.add(new Column(Math.floorDiv((int) Math.floor(label.x()), 16), - Math.floorDiv((int) Math.floor(label.z()), 16))); - for (Column column : columns) { - if (!world.isChunkGenerated(column.x(), column.z())) { - fail("Station label chunk is not generated: " + column.x() + "," + column.z()); return; - } - } - List> loads = columns.stream() - .map(c -> world.getChunkAtAsync(c.x(), c.z(), false)).toList(); - CompletableFuture.allOf(loads.toArray(CompletableFuture[]::new)).whenComplete((unused, error) -> { - if (!plugin.isEnabled() || closed) return; - Bukkit.getScheduler().runTask(plugin, () -> { - if (closed) return; - if (error != null) { fail("Cannot load station label chunks: " + error.getMessage()); return; } - chunks = loads.stream().map(CompletableFuture::join).toList(); - if (chunks.stream().anyMatch(Objects::isNull)) { fail("An existing station chunk is unavailable"); return; } - for (Chunk chunk : chunks) if (chunk.addPluginChunkTicket(plugin)) ownedTickets.add(chunk); - waitForEntities(); - }); - }); - } - - void waitForEntities() { - if (closed || !plugin.isEnabled()) { close(); return; } - if (chunks.stream().anyMatch(c -> !c.isEntitiesLoaded())) { - if (++attempts >= 100) { fail("Station entity data did not load; existing labels retained"); return; } - Bukkit.getScheduler().runTaskLater(plugin, this::waitForEntities, 1L); return; - } - NamespacedKey owner = new NamespacedKey(plugin, "station_label_set"); - NamespacedKey id = new NamespacedKey(plugin, "station_label_id"); - List previous = world.getEntitiesByClass(TextDisplay.class).stream() - .filter(e -> TAG.equals(e.getPersistentDataContainer().get(owner, PersistentDataType.STRING))).toList(); - List created = new ArrayList<>(); - try { - for (LabelSpec label : LABELS) { - Location location = new Location(world, label.x(), label.y(), label.z(), label.yaw(), 0); - created.add(world.spawn(location, TextDisplay.class, display -> configure(display, label, owner, id))); - } - previous.forEach(TextDisplay::remove); - plugin.getLogger().info("Station labels installed: " + created.size() + "; replaced=" + previous.size()); - } catch (Exception error) { - created.forEach(TextDisplay::remove); - plugin.getLogger().warning("Station labels not installed; previous labels retained: " + error.getMessage()); - } finally { close(); } - } - - void fail(String message) { plugin.getLogger().warning(message); close(); } - - void close() { - if (closed) return; - closed = true; - for (Chunk chunk : ownedTickets) chunk.removePluginChunkTicket(plugin); - ownedTickets.clear(); - ACTIVE.remove(world.getUID(), this); - } - } -} diff --git a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/StationLift.java b/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/StationLift.java deleted file mode 100644 index f2a081f..0000000 --- a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/StationLift.java +++ /dev/null @@ -1,108 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.command.*; -import org.bukkit.entity.Player; -import org.bukkit.event.*; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.plugin.java.JavaPlugin; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** Explicitly enabled, two-stop personal lift for the completed station layout. */ -final class StationLift implements Listener, CommandExecutor { - private final JavaPlugin plugin; - private final World world; - private final Set pending = ConcurrentHashMap.newKeySet(); - private static final Set LANDING_FLOORS = EnumSet.of(Material.SMOOTH_SANDSTONE, - Material.SMOOTH_QUARTZ, Material.QUARTZ_BLOCK, Material.CUT_SANDSTONE, - Material.STONE_BRICKS, Material.SMOOTH_STONE, Material.POLISHED_ANDESITE, - Material.GOLD_BLOCK, Material.WAXED_OXIDIZED_CUT_COPPER, Material.GREEN_CONCRETE, - Material.SPRUCE_PLANKS, Material.POLISHED_DEEPSLATE, Material.WHITE_CONCRETE); - - StationLift(JavaPlugin plugin, World world) { this.plugin = plugin; this.world = world; } - - static int selectorFloor(int x, int y, int z) { - if (x != -6 || z != -123) return 0; - return y == 101 ? 1 : y == 115 ? 2 : 0; - } - - static int feetY(int floor) { - if (floor != 1 && floor != 2) throw new IllegalArgumentException("Only completed floors 1 and 2 are available"); - return floor == 1 ? 99 : 113; - } - - static boolean supportsLanding(Material material) { return LANDING_FLOORS.contains(material); } - - static boolean inCabin(double x, double y, double z, int floor) { - return x >= -8 && x < -3 && z >= -122 && z < -117 && y >= feetY(floor) && y < feetY(floor) + 2; - } - - @EventHandler(ignoreCancelled = true) - public void onInteract(PlayerInteractEvent event) { - if (event.getHand() != EquipmentSlot.HAND || event.getAction() != Action.RIGHT_CLICK_BLOCK) return; - Player player = event.getPlayer(); - Block block = event.getClickedBlock(); - if (player.getWorld() != world || block == null || block.getType() != Material.GOLD_BLOCK) return; - int floor = selectorFloor(block.getX(), block.getY(), block.getZ()); - if (floor == 0) return; - event.setCancelled(true); - Location at = player.getLocation(); - if (!inCabin(at.getX(), at.getY(), at.getZ(), floor)) { - player.sendMessage(Component.text("Войди в кабину лифта.", NamedTextColor.GOLD)); return; - } - travel(player, floor == 1 ? 2 : 1, false); - } - - private void travel(Player player, int floor, boolean entrance) { - if (!pending.add(player.getUniqueId())) return; - Location target = new Location(world, -5.5, feetY(floor), entrance ? -92.5 : -119.5, entrance ? 180 : 0, 0); - world.getChunkAtAsync(target).whenComplete((chunk, error) -> { - if (!plugin.isEnabled()) { pending.remove(player.getUniqueId()); return; } - Bukkit.getScheduler().runTask(plugin, () -> { - if (error != null || !player.isOnline() || player.getWorld() != world || !safeLanding(target)) { - pending.remove(player.getUniqueId()); - if (player.isOnline()) player.sendMessage(Component.text("Лифт временно недоступен: проверь площадку назначения.", NamedTextColor.RED)); - return; - } - // Loaded, checked destination. No shared cabin moves other passengers. - boolean moved = player.teleport(target); - pending.remove(player.getUniqueId()); - if (moved) { - player.playSound(target, Sound.BLOCK_NOTE_BLOCK_CHIME, .6f, floor == 2 ? 1.2f : .8f); - player.sendActionBar(Component.text(floor == 1 ? "1 · ВЕСТИБЮЛЬ" : "2 · SMASH", NamedTextColor.GOLD)); - } - }); - }); - } - - private boolean safeLanding(Location target) { - int x=target.getBlockX(),y=target.getBlockY(),z=target.getBlockZ(); - return supportsLanding(world.getBlockAt(x,y-1,z).getType()) - && world.getBlockAt(x,y,z).getType().isAir() && world.getBlockAt(x,y+1,z).getType().isAir(); - } - - @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - Player player; - String selected; - boolean entrance; - if (sender instanceof Player p) { - if (args.length > 1) return usage(sender); - player=p; selected=args.length==0 ? "1" : args[0]; entrance=args.length==0; - } else if (sender instanceof ConsoleCommandSender && args.length>=1 && args.length<=2) { - player=Bukkit.getPlayerExact(args[0]); selected=args.length==2 ? args[1] : "1"; entrance=args.length==1; - } else return usage(sender); - if (!selected.equals("1") && !selected.equals("2")) return usage(sender); - if (player==null || player.getWorld()!=world) { - sender.sendMessage("Station is available in the configured lobby world."); return true; - } - travel(player,Integer.parseInt(selected),entrance); return true; - } - - private boolean usage(CommandSender sender) { sender.sendMessage("/station [1|2] — vestibule or SMASH"); return true; } -} diff --git a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/SurfaceMap.java b/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/SurfaceMap.java deleted file mode 100644 index 16146ae..0000000 --- a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/SurfaceMap.java +++ /dev/null @@ -1,173 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import com.google.gson.stream.JsonWriter; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.nio.file.*; -import java.time.Instant; -import java.util.*; - -/** Detached world surface data. Rendering and file I/O never call the Bukkit API. */ -final class SurfaceMap { - static final int MAX_COLUMNS = 4_194_304; - final int minX, minZ, maxX, maxZ, width, length; - private final int[] heights, materials; - private final BitSet captured; - private final Map materialIds = new LinkedHashMap<>(); - private int columns, minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE; - - SurfaceMap(int minX, int minZ, int maxX, int maxZ) { - long w = (long) maxX - minX + 1, l = (long) maxZ - minZ + 1; - if (w <= 0 || l <= 0 || w > MAX_COLUMNS || l > MAX_COLUMNS || w * l > MAX_COLUMNS) - throw new IllegalArgumentException("Map bounds must contain 1 to " + MAX_COLUMNS + " columns"); - this.minX = minX; this.minZ = minZ; this.maxX = maxX; this.maxZ = maxZ; - width = (int) w; length = (int) l; - heights = new int[width * length]; materials = new int[heights.length]; - captured = new BitSet(heights.length); - } - - void setColumn(int x, int z, int y, String material) { - if (x < minX || x > maxX || z < minZ || z > maxZ) throw new IllegalArgumentException("Column outside map"); - if (material == null || !material.matches("minecraft:[a-z0-9_]+")) throw new IllegalArgumentException("Invalid material key"); - int index = (z - minZ) * width + x - minX; - if (captured.get(index)) throw new IllegalStateException("Column captured twice"); - heights[index] = y; - materials[index] = materialIds.computeIfAbsent(material, ignored -> materialIds.size()); - captured.set(index); columns++; minY = Math.min(minY, y); maxY = Math.max(maxY, y); - } - - int columns() { return columns; } - - static void validateName(String name) { - if (name == null || !name.matches("[a-z0-9][a-z0-9_-]{0,47}")) - throw new IllegalArgumentException("Map name must use 1–48 lowercase letters, digits, underscores or hyphens"); - } - - static void requireUnusedName(Path directory, String name) throws IOException { - validateName(name); - for (String extension : List.of(".json", ".png")) - if (Files.exists(directory.resolve(name + extension), LinkOption.NOFOLLOW_LINKS)) - throw new FileAlreadyExistsException("Map already exists; choose a new name: " + name); - } - - void write(Path directory, String name, String world, String worldKey, String worldUuid, - Instant startedAt, Instant finishedAt) throws IOException { - if (columns != heights.length) throw new IllegalStateException("Cannot export an incomplete map"); - requireUnusedName(directory, name); - Files.createDirectories(directory); - Path jsonTemp = Files.createTempFile(directory, ".surface-", ".json.tmp"); - Path pngTemp = Files.createTempFile(directory, ".surface-", ".png.tmp"); - try { - try (JsonWriter out = new JsonWriter(Files.newBufferedWriter(jsonTemp))) { - out.beginObject(); - out.name("format").value("minecraft-builder-surface-map-v1"); - out.name("source").value("paper_world_surface"); - out.name("ignored_materials").beginArray().value("minecraft:barrier").endArray(); - out.name("surface_policy").value("Highest block excluding all air variants and ignored_materials; wholly transparent columns use minecraft:air at world minimum Y."); - out.name("world").value(world); out.name("world_key").value(worldKey); out.name("world_uuid").value(worldUuid); - out.name("capture_started_at").value(startedAt.toString()); - out.name("capture_finished_at").value(finishedAt.toString()); - out.name("atomic_snapshot").value(false); - out.name("capture_note").value("Existing chunks read sequentially on the server thread; edits during capture can appear in different chunks at different times."); - out.name("orientation").value("north_up; x increases right; z increases down"); - out.name("index").value("(z - min_z) * width + (x - min_x)"); - out.name("min_x").value(minX); out.name("max_x").value(maxX); - out.name("min_z").value(minZ); out.name("max_z").value(maxZ); - out.name("width").value(width); out.name("length").value(length); - out.name("columns").value(columns); out.name("min_surface_y").value(minY); out.name("max_surface_y").value(maxY); - out.name("palette").beginArray(); - for (String material : materialIds.keySet()) out.value(material); - out.endArray(); out.name("palette_rgb").beginArray(); - for (String material : materialIds.keySet()) out.value(String.format(Locale.ROOT, "#%06x", color(material))); - out.endArray(); out.name("surface_y").beginArray(); - for (int height : heights) out.value(height); - out.endArray(); out.name("material_index").beginArray(); - for (int material : materials) out.value(material); - out.endArray(); out.endObject(); - } - if (!ImageIO.write(render(), "png", pngTemp.toFile())) throw new IOException("PNG writer unavailable"); - // Neither final filename is replaced, even if another process creates it during capture. - Files.move(jsonTemp, directory.resolve(name + ".json")); - Files.move(pngTemp, directory.resolve(name + ".png")); - } finally { - Files.deleteIfExists(jsonTemp); Files.deleteIfExists(pngTemp); - } - } - - BufferedImage render() { - if (columns != heights.length) throw new IllegalStateException("Cannot render an incomplete map"); - List palette = new ArrayList<>(materialIds.keySet()); - BufferedImage image = new BufferedImage(width, length, BufferedImage.TYPE_INT_RGB); - for (int z = 0; z < length; z++) for (int x = 0; x < width; x++) { - int index = z * width + x; - String material = palette.get(materials[index]); - int base = color(material); - double dx = (height(x + 1, z) - height(x - 1, z)) / 2.0; - double dz = (height(x, z + 1) - height(x, z - 1)) / 2.0; - double norm = Math.sqrt(dx * dx + dz * dz + 1); - double light = (-dx * -.55 - dz * -.55 + .63) / norm; - double shade = Math.clamp(.76 + .36 * light, .44, 1.13); - // Survey blocks retain their categorical color; water has no false land relief. - if (material.endsWith("_concrete") || material.endsWith("_wool") || material.endsWith("_terracotta")) shade = 1; - if (material.equals("minecraft:water")) shade = .96; - image.setRGB(x, z, shaded(base, shade)); - } - return image; - } - - private double height(int x, int z) { - return heights[Math.clamp(z, 0, length - 1) * width + Math.clamp(x, 0, width - 1)]; - } - - private static int shaded(int color, double shade) { - int r = (int) Math.clamp((color >> 16 & 255) * shade, 0, 255); - int g = (int) Math.clamp((color >> 8 & 255) * shade, 0, 255); - int b = (int) Math.clamp((color & 255) * shade, 0, 255); - return r << 16 | g << 8 | b; - } - - static int color(String key) { - String material = key.replaceFirst("^minecraft:", ""); - String pigment = material.replaceFirst("_(concrete|wool|terracotta)$", ""); - if (!pigment.equals(material)) { - Integer color = switch (pigment) { - case "white" -> 0xf0f0e6; case "orange" -> 0xf78d27; case "magenta" -> 0xdc52c7; - case "light_blue" -> 0x68c8ec; case "yellow" -> 0xf6d34a; case "lime" -> 0x98d84d; - case "pink" -> 0xf394b5; case "gray" -> 0x545c61; case "light_gray" -> 0xa4aaa6; - case "cyan" -> 0x23b6b6; case "purple" -> 0x9460ce; case "blue" -> 0x4a69d8; - case "brown" -> 0x916044; case "green" -> 0x527c31; case "red" -> 0xe3544b; - case "black" -> 0x26282d; default -> null; - }; - if (color != null) return color; - } - if (material.contains("leaves")) return 0x397548; - if (material.contains("spruce") || material.contains("dark_oak")) return 0x75583c; - if (material.endsWith("_planks") || material.endsWith("_log")) return 0xa48458; - if (material.contains("copper")) return 0x639f8d; - if (material.contains("quartz")) return 0xe9e4d5; - if (material.contains("sandstone")) return 0xc8bb83; - if (material.contains("deepslate") || material.equals("obsidian")) return 0x555a64; - if (material.contains("stone_brick")) return 0x9ca49e; - return switch (material) { - case "water", "bubble_column" -> 0x367dba; - case "grass_block", "short_grass", "tall_grass", "moss_block" -> 0x79a957; - case "dirt", "coarse_dirt", "rooted_dirt", "dirt_path", "farmland" -> 0x937454; - case "podzol", "mud" -> 0x675746; - case "stone", "cobblestone", "smooth_stone" -> 0xa2a8a3; - case "andesite", "polished_andesite" -> 0x929e98; - case "diorite", "polished_diorite" -> 0xb8bebb; - case "granite", "polished_granite" -> 0xae8980; - case "sand" -> 0xd7cc9a; - case "gravel" -> 0xa49f97; - case "snow", "snow_block", "powder_snow" -> 0xe8eff1; - case "ice", "packed_ice", "blue_ice" -> 0x93c7dc; - case "gold_block", "glowstone", "lantern" -> 0xf4cf58; - case "sea_lantern" -> 0xb4ece2; - case "terracotta", "bricks" -> 0xb6775d; - case "glass", "tinted_glass" -> 0xacced3; - case "air", "cave_air", "void_air" -> 0x18232e; - default -> 0x8a8988; - }; - } -} diff --git a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/SurfaceMapCapture.java b/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/SurfaceMapCapture.java deleted file mode 100644 index edd0704..0000000 --- a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/SurfaceMapCapture.java +++ /dev/null @@ -1,115 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import org.bukkit.*; -import org.bukkit.plugin.java.JavaPlugin; -import java.io.IOException; -import java.nio.file.Path; -import java.time.Instant; -import java.util.function.IntFunction; - -/** One existing chunk per server tick, followed by detached asynchronous rendering. */ -final class SurfaceMapCapture { - private final JavaPlugin plugin; - private final World world; - private boolean active; - private int completed, total, chunksWide; - private String name = "none", state = "idle"; - private SurfaceMap map; - private Instant startedAt; - - SurfaceMapCapture(JavaPlugin plugin, World world) { this.plugin = plugin; this.world = world; } - boolean active() { return active; } - String status() { return "map=" + name + "; map_state=" + state + "; map_chunks=" + completed + "/" + total; } - private Path directory() { return plugin.getDataFolder().toPath().resolve("maps"); } - - record Column(int y, Material material) {} - - /** Barriers contain players, but must not hide the visible world in a surface map. */ - static Column visibleColumn(int minY, int highestY, IntFunction materialAt) { - for (int y = Math.max(minY, highestY); y >= minY; y--) { - Material material = materialAt.apply(y); - if (material != Material.AIR && material != Material.CAVE_AIR - && material != Material.VOID_AIR && material != Material.BARRIER) return new Column(y, material); - if (y == minY) break; - } - // A void column has no visible surface; retain a bounded, explicit air sentinel. - return new Column(minY, Material.AIR); - } - - private Column visibleColumn(int x, int z) { - return visibleColumn(world.getMinHeight(), world.getHighestBlockYAt(x, z, HeightMap.WORLD_SURFACE), - y -> world.getBlockAt(x, y, z).getType()); - } - - void start(String name, int minX, int minZ, int maxX, int maxZ) throws IOException { - if (active) throw new IllegalStateException("Map capture already active"); - SurfaceMap.requireUnusedName(directory(), name); - map = new SurfaceMap(minX, minZ, maxX, maxZ); - this.name = name; completed = 0; - chunksWide = Math.floorDiv(maxX, 16) - Math.floorDiv(minX, 16) + 1; - total = chunksWide * (Math.floorDiv(maxZ, 16) - Math.floorDiv(minZ, 16) + 1); - startedAt = Instant.now(); active = true; state = "reading"; - plugin.getLogger().info("Surface map " + name + " started: " + total + " existing chunks; no terrain generation or block changes"); - next(); - } - - private void next() { - if (!active || !plugin.isEnabled()) return; - if (completed == total) { write(); return; } - int cx = Math.floorDiv(map.minX, 16) + completed % chunksWide; - int cz = Math.floorDiv(map.minZ, 16) + completed / chunksWide; - if (!world.isChunkGenerated(cx, cz)) { fail("Chunk " + cx + "," + cz + " has not been generated"); return; } - // generate=false: exporting a map must not extend the world. - world.getChunkAtAsync(cx, cz, false).whenComplete((chunk, error) -> { - if (!plugin.isEnabled()) return; - Bukkit.getScheduler().runTask(plugin, () -> { - if (error != null) { fail(error.getMessage()); return; } - if (chunk == null) { fail("Existing chunk unavailable: " + cx + "," + cz); return; } - boolean ticket = false; - try { - ticket = chunk.addPluginChunkTicket(plugin); - for (int z = Math.max(map.minZ, cz * 16); z <= Math.min(map.maxZ, cz * 16 + 15); z++) - for (int x = Math.max(map.minX, cx * 16); x <= Math.min(map.maxX, cx * 16 + 15); x++) { - Column column = visibleColumn(x, z); - map.setColumn(x, z, column.y(), column.material().getKey().toString()); - } - completed++; - if (completed % 512 == 0) plugin.getLogger().info("Surface map " + name + ": " + completed + "/" + total + " chunks"); - } catch (Exception e) { fail(e.getMessage()); return; } - finally { if (ticket) chunk.removePluginChunkTicket(plugin); } - // Zero-delay tasks can run again in the same scheduler heartbeat - // when the chunk future is already complete. Require a later tick. - Bukkit.getScheduler().runTaskLater(plugin, this::next, 1L); - }); - }); - } - - private void write() { - state = "writing"; - Instant finishedAt = Instant.now(); - String worldName = world.getName(), worldKey = world.getKey().toString(), worldUuid = world.getUID().toString(); - // This worker only sees the completed primitive grid, strings and output directory. - SurfaceMap capturedMap = map; - Path destination = directory(); - String capturedName = name; - Instant capturedStart = startedAt; - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - try { - capturedMap.write(destination, capturedName, worldName, worldKey, worldUuid, capturedStart, finishedAt); - if (!plugin.isEnabled()) return; - Bukkit.getScheduler().runTask(plugin, () -> { - active = false; state = "complete"; map = null; - plugin.getLogger().info("Surface map complete: maps/" + capturedName + ".json and .png; " - + capturedMap.columns() + " observed columns; north up; chunk-sequential capture"); - }); - } catch (Exception e) { - if (plugin.isEnabled()) Bukkit.getScheduler().runTask(plugin, () -> fail(e.getMessage())); - } - }); - } - - private void fail(String reason) { - active = false; state = "failed"; map = null; - plugin.getLogger().severe("Surface map " + name + " stopped: " + reason); - } -} diff --git a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/TerrainWorldPlugin.java b/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/TerrainWorldPlugin.java deleted file mode 100644 index 2844573..0000000 --- a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/TerrainWorldPlugin.java +++ /dev/null @@ -1,154 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import com.google.gson.*; -import org.bukkit.*; -import org.bukkit.command.*; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import java.nio.file.*; -import java.util.*; - -/** Optional bootstrap for a separate, operator-selected world. */ -public final class TerrainWorldPlugin extends JavaPlugin { - private RecipeGenerator generator; - private World world; - private Location lobbySpawn; - private SurfaceMapCapture surfaceMaps; - private boolean generating; - private int completed, total, mismatches, minHeight, maxHeight; - private long columns, wetColumns, started; - - @Override public void onEnable() { - try { - saveDefaultConfig(); - if (!getConfig().getBoolean("enabled", false)) return; - String name = getConfig().getString("world", "shacraft_lobby"); - if (!name.matches("[a-z0-9_]{1,48}")) throw new IllegalArgumentException("Invalid world name"); - // Paper 26.2 stores dimensions beneath the primary save, not server/name. - World overworld = Objects.requireNonNull(Bukkit.getWorld(NamespacedKey.minecraft("overworld"))); - Path folder = overworld.getWorldPath().resolveSibling(name); - Path recipeFile = getDataFolder().toPath().resolve(getConfig().getString("recipe", "terrain.json")).normalize(); - if (!recipeFile.startsWith(getDataFolder().toPath())) throw new IllegalArgumentException("Recipe must be inside plugin data folder"); - JsonObject source = JsonParser.parseString(Files.readString(recipeFile)).getAsJsonObject(); - Integer waterLevel = getConfig().isInt("water-level") ? getConfig().getInt("water-level") : null; - String profile = getConfig().getString("terrain-profile", "recipe-v1"); - generator = new RecipeGenerator(source, waterLevel, profile); - LobbySpawn spawn = LobbySpawn.fromConfig(getConfig(), () -> generator.height(0, 0) + 1); - Path manifest = getDataFolder().toPath().resolve(name + ".generation.json"); - String identity = generator.identity(); - if (Files.exists(manifest)) { - var saved = JsonParser.parseString(Files.readString(manifest)).getAsJsonObject(); - if (!saved.get("identity").getAsString().equals(identity)) - throw new IllegalStateException("Recipe changed: use a new world name to avoid seams"); - } else { - if (Files.exists(folder)) throw new IllegalStateException("Refusing to adopt an existing world without its generation manifest"); - JsonObject saved = new JsonObject(); saved.addProperty("identity", identity); saved.add("recipe", source); - if (waterLevel != null) saved.addProperty("water_level", waterLevel); - saved.addProperty("terrain_profile", profile); - Files.writeString(manifest, new GsonBuilder().setPrettyPrinting().create().toJson(saved), StandardOpenOption.CREATE_NEW); - } - if (Bukkit.getWorld(name) != null) throw new IllegalStateException("World already loaded by another provider"); - world = new WorldCreator(NamespacedKey.minecraft(name)).seed(source.get("seed").getAsLong()) - .generator(generator).generateStructures(false).createWorld(); - if (world == null) throw new IllegalStateException("World creation failed"); - surfaceMaps = new SurfaceMapCapture(this, world); - var bounds = generator.recipe.bounds(); - world.getWorldBorder().setCenter((bounds.min().x() + bounds.max().x() + 1) / 2.0, - (bounds.min().z() + bounds.max().z() + 1) / 2.0); - int footprint = Math.max(generator.recipe.width(), generator.recipe.length()); - world.getWorldBorder().setSize(Math.max(footprint, getConfig().getInt("border-size", footprint))); - lobbySpawn = spawn.location(world); - if (getConfig().contains("spawn")) world.setSpawnLocation(lobbySpawn); - else world.setSpawnLocation(0, lobbySpawn.getBlockY(), 0); - getServer().getPluginManager().registerEvents(new LobbyRespawn(world, - getConfig().contains("spawn") ? lobbySpawn : null), this); - world.setTime(6000); world.setStorm(false); world.setThundering(false); - world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); - world.setGameRule(GameRule.DO_WEATHER_CYCLE, false); - world.setGameRule(GameRule.DO_MOB_SPAWNING, false); - Objects.requireNonNull(getCommand("lobby")).setExecutor(this::command); - if (getConfig().getBoolean("station.enabled", false)) { - StationLift lift = new StationLift(this, world); - getServer().getPluginManager().registerEvents(lift, this); - Objects.requireNonNull(getCommand("station")).setExecutor(lift); - StationLabels.install(this, world); - } - getLogger().info("Terrain world ready: " + name + "; recipe=" + generator.recipe.id()); - } catch (Exception e) { - getLogger().severe("Terrain world disabled: " + e.getMessage()); - getServer().getPluginManager().disablePlugin(this); - } - } - - private boolean command(CommandSender sender, Command command, String label, String[] args) { - if (world == null) { sender.sendMessage("Terrain world unavailable"); return true; } - if (args.length == 1 && args[0].equals("status")) { - sender.sendMessage("Shacraft: " + completed + "/" + total + " chunks; active=" + generating + "; mismatched columns=" + mismatches + "; " + surfaceMaps.status()); - } else if (args.length == 2 && args[0].equals("map")) { - if (!(sender instanceof ConsoleCommandSender)) { sender.sendMessage("Surface map export is a server-console command"); return true; } - if (generating) { sender.sendMessage("Wait for terrain generation before capturing a map"); return true; } - try { - var b = generator.recipe.bounds(); - surfaceMaps.start(args[1], b.min().x(), b.min().z(), b.max().x(), b.max().z()); - sender.sendMessage("Surface map started; inspect lobby status. Output: plugins/ShacraftTerrain/maps/" + args[1] + ".{json,png}"); - } catch (Exception e) { sender.sendMessage("Cannot capture map: " + e.getMessage()); } - } else if (args.length == 1 && args[0].equals("generate") && sender instanceof ConsoleCommandSender) { - if (generating) { sender.sendMessage("Generation already active"); return true; } - if (surfaceMaps.active()) { sender.sendMessage("Wait for the active surface map capture"); return true; } - completed = 0; columns = 0; wetColumns = 0; mismatches = 0; - minHeight = Integer.MAX_VALUE; maxHeight = Integer.MIN_VALUE; - var b = generator.recipe.bounds(); - total = (Math.floorDiv(b.max().x(), 16) - Math.floorDiv(b.min().x(), 16) + 1) - * (Math.floorDiv(b.max().z(), 16) - Math.floorDiv(b.min().z(), 16) + 1); - started = System.currentTimeMillis(); generating = true; next(); - } else { - Player player = sender instanceof Player p ? p : args.length == 1 ? Bukkit.getPlayerExact(args[0]) : null; - if (player == null) { sender.sendMessage("/lobby [status] or console: lobby | generate | map "); return true; } - player.teleportAsync(lobbySpawn.clone()); - } - return true; - } - - private void next() { - if (!isEnabled() || !generating) return; - if (completed == total) { - generating = false; world.save(); - var report = Map.of("world", world.getName(), "recipe_id", generator.recipe.id(), "chunks", total, - "verified_columns", columns, "water_columns", wetColumns, "mismatches", mismatches, "min_surface_y", minHeight, - "max_surface_y", maxHeight, "elapsed_ms", System.currentTimeMillis() - started); - try { Files.writeString(getDataFolder().toPath().resolve("generation-report.json"), new GsonBuilder().setPrettyPrinting().create().toJson(report)); } - catch (Exception e) { getLogger().severe("Cannot save generation report: " + e.getMessage()); } - getLogger().info("Terrain generation complete: " + report); return; - } - var b = generator.recipe.bounds(); - int nx = Math.floorDiv(b.max().x(), 16) - Math.floorDiv(b.min().x(), 16) + 1; - int cx = Math.floorDiv(b.min().x(), 16) + completed % nx; - int cz = Math.floorDiv(b.min().z(), 16) + completed / nx; - world.getChunkAtAsync(cx, cz, true).whenComplete((chunk, error) -> { - if (!isEnabled()) return; - Bukkit.getScheduler().runTask(this, () -> { - if (error != null) { generating = false; getLogger().severe("Generation stopped: " + error.getMessage()); return; } - for (int x = 0; x < 16; x++) for (int z = 0; z < 16; z++) { - int wx = cx * 16 + x, wz = cz * 16 + z; - if (wx < b.min().x() || wx > b.max().x() || wz < b.min().z() || wz > b.max().z()) continue; - int expected = generator.visibleHeight(wx, wz); - int actual = world.getHighestBlockYAt(wx, wz, HeightMap.WORLD_SURFACE); - int ground = generator.height(wx, wz); - boolean wet = expected > ground; - boolean mismatch = actual != expected || world.getBlockAt(wx, ground, wz).getType() != generator.surfaceMaterial(wx,wz); - if (wet) { - wetColumns++; - for (int y = ground + 1; y <= expected; y++) - if (world.getBlockAt(wx, y, wz).getType() != Material.WATER) { mismatch = true; break; } - if (world.getBlockAt(wx, ground, wz).getType().isAir()) mismatch = true; - } - if (mismatch) mismatches++; - minHeight = Math.min(minHeight, actual); maxHeight = Math.max(maxHeight, actual); columns++; - } - completed++; - if (completed % 128 == 0) getLogger().info("Terrain progress " + completed + "/" + total); - Bukkit.getScheduler().runTaskLater(this, this::next, 1L); - }); - }); - } -} diff --git a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/noise/FastNoiseLite.java b/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/noise/FastNoiseLite.java deleted file mode 100644 index 7a993de..0000000 --- a/terrain-world-plugin/src/main/java/io/github/minecraftbuilder/terrainworld/noise/FastNoiseLite.java +++ /dev/null @@ -1,2611 +0,0 @@ -package io.github.minecraftbuilder.terrainworld.noise; - -// MIT License -// -// Copyright(c) 2023 Jordan Peck (jordan.me2@gmail.com) -// Copyright(c) 2023 Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// .'',;:cldxkO00KKXXNNWWWNNXKOkxdollcc::::::;:::ccllloooolllllllllooollc:,'... ...........',;cldxkO000Okxdlc::;;;,,;;;::cclllllll -// ..',;:ldxO0KXXNNNNNNNNXXK0kxdolcc::::::;;;,,,,,,;;;;;;;;;;:::cclllllc:;'.... ...........',;:ldxO0KXXXK0Okxdolc::;;;;::cllodddddo -// ...',:loxO0KXNNNNNXXKK0Okxdolc::;::::::::;;;,,'''''.....''',;:clllllc:;,'............''''''''',;:loxO0KXNNNNNXK0Okxdollccccllodxxxxxxd -// ....';:ldkO0KXXXKK00Okxdolcc:;;;;;::cclllcc:;;,''..... ....',;clooddolcc:;;;;,,;;;;;::::;;;;;;:cloxk0KXNWWWWWWNXKK0Okxddoooddxxkkkkkxx -// .....';:ldxkOOOOOkxxdolcc:;;;,,,;;:cllooooolcc:;'... ..,:codxkkkxddooollloooooooollcc:::::clodkO0KXNWWWWWWNNXK00Okxxxxxxxxkkkkxxx -// . ....';:cloddddo___________,,,,;;:clooddddoolc:,... ..,:ldx__00OOOkkk___kkkkkkxxdollc::::cclodkO0KXXNNNNNNXXK0OOkxxxxxxxxxxxxddd -// .......',;:cccc:| |,,,;;:cclooddddoll:;'.. ..';cox| \KKK000| |KK00OOkxdocc___;::clldxxkO0KKKKK00Okkxdddddddddddddddoo -// .......'',,,,,''| ________|',,;;::cclloooooolc:;'......___:ldk| \KK000| |XKKK0Okxolc| |;;::cclodxxkkkkxxdoolllcclllooodddooooo -// ''......''''....| | ....'',,,,;;;::cclloooollc:;,''.'| |oxk| \OOO0| |KKK00Oxdoll|___|;;;;;::ccllllllcc::;;,,;;;:cclloooooooo -// ;;,''.......... | |_____',,;;;____:___cllo________.___| |___| \xkk| |KK_______ool___:::;________;;;_______...'',;;:ccclllloo -// c:;,''......... | |:::/ ' |lo/ | | \dx| |0/ \d| |cc/ |'/ \......',,;;:ccllo -// ol:;,'..........| _____|ll/ __ |o/ ______|____ ___| | \o| |/ ___ \| |o/ ______|/ ___ \ .......'',;:clo -// dlc;,...........| |::clooo| / | |x\___ \KXKKK0| |dol| |\ \| | | | | |d\___ \..| | / / ....',:cl -// xoc;'... .....'| |llodddd| \__| |_____\ \KKK0O| |lc:| |'\ | |___| | |_____\ \.| |_/___/... ...',;:c -// dlc;'... ....',;| |oddddddo\ | |Okkx| |::;| |..\ |\ /| | | \ |... ....',;:c -// ol:,'.......',:c|___|xxxddollc\_____,___|_________/ddoll|___|,,,|___|...\_____|:\ ______/l|___|_________/...\________|'........',;::cc -// c:;'.......';:codxxkkkkxxolc::;::clodxkOO0OOkkxdollc::;;,,''''',,,,''''''''''',,'''''',;:loxkkOOkxol:;,'''',,;:ccllcc:;,'''''',;::ccll -// ;,'.......',:codxkOO0OOkxdlc:;,,;;:cldxxkkxxdolc:;;,,''.....'',;;:::;;,,,'''''........,;cldkO0KK0Okdoc::;;::cloodddoolc:;;;;;::ccllooo -// .........',;:lodxOO0000Okdoc:,,',,;:clloddoolc:;,''.......'',;:clooollc:;;,,''.......',:ldkOKXNNXX0Oxdolllloddxxxxxxdolccccccllooodddd -// . .....';:cldxkO0000Okxol:;,''',,;::cccc:;,,'.......'',;:cldxxkkxxdolc:;;,'.......';coxOKXNWWWNXKOkxddddxxkkkkkkxdoollllooddxxxxkkk -// ....',;:codxkO000OOxdoc:;,''',,,;;;;,''.......',,;:clodkO00000Okxolc::;,,''..',;:ldxOKXNWWWNNK0OkkkkkkkkkkkxxddooooodxxkOOOOO000 -// ....',;;clodxkkOOOkkdolc:;,,,,,,,,'..........,;:clodxkO0KKXKK0Okxdolcc::;;,,,;;:codkO0XXNNNNXKK0OOOOOkkkkxxdoollloodxkO0KKKXXXXX -// -// VERSION: 1.1.1 -// https://github.com/Auburn/FastNoiseLite - -// To switch between using floats or doubles for input position, -// perform a file-wide replace on the following strings (including /*FNLfloat*/) -// /*FNLfloat*/ float -// /*FNLfloat*/ double - -public class FastNoiseLite -{ - public enum NoiseType - { - OpenSimplex2, - OpenSimplex2S, - Cellular, - Perlin, - ValueCubic, - Value - }; - - public enum RotationType3D - { - None, - ImproveXYPlanes, - ImproveXZPlanes - }; - - public enum FractalType - { - None, - FBm, - Ridged, - PingPong, - DomainWarpProgressive, - DomainWarpIndependent - }; - - public enum CellularDistanceFunction - { - Euclidean, - EuclideanSq, - Manhattan, - Hybrid - }; - - public enum CellularReturnType - { - CellValue, - Distance, - Distance2, - Distance2Add, - Distance2Sub, - Distance2Mul, - Distance2Div - }; - - public enum DomainWarpType - { - OpenSimplex2, - OpenSimplex2Reduced, - BasicGrid - }; - - private enum TransformType3D - { - None, - ImproveXYPlanes, - ImproveXZPlanes, - DefaultOpenSimplex2 - }; - - private int mSeed = 1337; - private float mFrequency = 0.01f; - private NoiseType mNoiseType = NoiseType.OpenSimplex2; - private RotationType3D mRotationType3D = RotationType3D.None; - private TransformType3D mTransformType3D = TransformType3D.DefaultOpenSimplex2; - - private FractalType mFractalType = FractalType.None; - private int mOctaves = 3; - private float mLacunarity = 2.0f; - private float mGain = 0.5f; - private float mWeightedStrength = 0.0f; - private float mPingPongStrength = 2.0f; - - private float mFractalBounding = 1 / 1.75f; - - private CellularDistanceFunction mCellularDistanceFunction = CellularDistanceFunction.EuclideanSq; - private CellularReturnType mCellularReturnType = CellularReturnType.Distance; - private float mCellularJitterModifier = 1.0f; - - private DomainWarpType mDomainWarpType = DomainWarpType.OpenSimplex2; - private TransformType3D mWarpTransformType3D = TransformType3D.DefaultOpenSimplex2; - private float mDomainWarpAmp = 1.0f; - - /// - /// Create new FastNoise object with default seed - /// - public FastNoiseLite() { } - - /// - /// Create new FastNoise object with specified seed - /// - public FastNoiseLite(int seed) - { - SetSeed(seed); - } - - /// - /// Sets seed used for all noise types - /// - /// - /// Default: 1337 - /// - public void SetSeed(int seed) { mSeed = seed; } - - /// - /// Sets frequency for all noise types - /// - /// - /// Default: 0.01 - /// - public void SetFrequency(float frequency) { mFrequency = frequency; } - - /// - /// Sets noise algorithm used for GetNoise(...) - /// - /// - /// Default: OpenSimplex2 - /// - public void SetNoiseType(NoiseType noiseType) - { - mNoiseType = noiseType; - UpdateTransformType3D(); - } - - /// - /// Sets domain rotation type for 3D Noise and 3D DomainWarp. - /// Can aid in reducing directional artifacts when sampling a 2D plane in 3D - /// - /// - /// Default: None - /// - public void SetRotationType3D(RotationType3D rotationType3D) - { - mRotationType3D = rotationType3D; - UpdateTransformType3D(); - UpdateWarpTransformType3D(); - } - - /// - /// Sets method for combining octaves in all fractal noise types - /// - /// - /// Default: None - /// Note: FractalType.DomainWarp... only affects DomainWarp(...) - /// - public void SetFractalType(FractalType fractalType) { mFractalType = fractalType; } - - /// - /// Sets octave count for all fractal noise types - /// - /// - /// Default: 3 - /// - public void SetFractalOctaves(int octaves) - { - mOctaves = octaves; - CalculateFractalBounding(); - } - - /// - /// Sets octave lacunarity for all fractal noise types - /// - /// - /// Default: 2.0 - /// - public void SetFractalLacunarity(float lacunarity) { mLacunarity = lacunarity; } - - /// - /// Sets octave gain for all fractal noise types - /// - /// - /// Default: 0.5 - /// - public void SetFractalGain(float gain) - { - mGain = gain; - CalculateFractalBounding(); - } - - /// - /// Sets octave weighting for all none DomainWarp fratal types - /// - /// - /// Default: 0.0 - /// Note: Keep between 0...1 to maintain -1...1 output bounding - /// - public void SetFractalWeightedStrength(float weightedStrength) { mWeightedStrength = weightedStrength; } - - /// - /// Sets strength of the fractal ping pong effect - /// - /// - /// Default: 2.0 - /// - public void SetFractalPingPongStrength(float pingPongStrength) { mPingPongStrength = pingPongStrength; } - - - /// - /// Sets distance function used in cellular noise calculations - /// - /// - /// Default: Distance - /// - public void SetCellularDistanceFunction(CellularDistanceFunction cellularDistanceFunction) { mCellularDistanceFunction = cellularDistanceFunction; } - - /// - /// Sets return type from cellular noise calculations - /// - /// - /// Default: EuclideanSq - /// - public void SetCellularReturnType(CellularReturnType cellularReturnType) { mCellularReturnType = cellularReturnType; } - - /// - /// Sets the maximum distance a cellular point can move from it's grid position - /// - /// - /// Default: 1.0 - /// Note: Setting this higher than 1 will cause artifacts - /// - public void SetCellularJitter(float cellularJitter) { mCellularJitterModifier = cellularJitter; } - - - /// - /// Sets the warp algorithm when using DomainWarp(...) - /// - /// - /// Default: OpenSimplex2 - /// - public void SetDomainWarpType(DomainWarpType domainWarpType) - { - mDomainWarpType = domainWarpType; - UpdateWarpTransformType3D(); - } - - - /// - /// Sets the maximum warp distance from original position when using DomainWarp(...) - /// - /// - /// Default: 1.0 - /// - public void SetDomainWarpAmp(float domainWarpAmp) { mDomainWarpAmp = domainWarpAmp; } - - - /// - /// 2D noise at given position using current settings - /// - /// - /// Noise output bounded between -1...1 - /// - public float GetNoise(/*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - x *= mFrequency; - y *= mFrequency; - - switch (mNoiseType) - { - case OpenSimplex2: - case OpenSimplex2S: - { - final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float)1.7320508075688772935274463415059; - final /*FNLfloat*/ float F2 = 0.5f * (SQRT3 - 1); - /*FNLfloat*/ float t = (x + y) * F2; - x += t; - y += t; - } - break; - default: - break; - } - - switch (mFractalType) - { - default: - return GenNoiseSingle(mSeed, x, y); - case FBm: - return GenFractalFBm(x, y); - case Ridged: - return GenFractalRidged(x, y); - case PingPong: - return GenFractalPingPong(x, y); - } - } - - /// - /// 3D noise at given position using current settings - /// - /// - /// Noise output bounded between -1...1 - /// - public float GetNoise(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - x *= mFrequency; - y *= mFrequency; - z *= mFrequency; - - switch (mTransformType3D) - { - case ImproveXYPlanes: - { - /*FNLfloat*/ float xy = x + y; - /*FNLfloat*/ float s2 = xy * -(/*FNLfloat*/ float)0.211324865405187; - z *= (/*FNLfloat*/ float)0.577350269189626; - x += s2 - z; - y = y + s2 - z; - z += xy * (/*FNLfloat*/ float)0.577350269189626; - } - break; - case ImproveXZPlanes: - { - /*FNLfloat*/ float xz = x + z; - /*FNLfloat*/ float s2 = xz * -(/*FNLfloat*/ float)0.211324865405187; - y *= (/*FNLfloat*/ float)0.577350269189626; - x += s2 - y; - z += s2 - y; - y += xz * (/*FNLfloat*/ float)0.577350269189626; - } - break; - case DefaultOpenSimplex2: - { - final /*FNLfloat*/ float R3 = (/*FNLfloat*/ float)(2.0 / 3.0); - /*FNLfloat*/ float r = (x + y + z) * R3; // Rotation, not skew - x = r - x; - y = r - y; - z = r - z; - } - break; - default: - break; - } - - switch (mFractalType) - { - default: - return GenNoiseSingle(mSeed, x, y, z); - case FBm: - return GenFractalFBm(x, y, z); - case Ridged: - return GenFractalRidged(x, y, z); - case PingPong: - return GenFractalPingPong(x, y, z); - } - } - - - /// - /// 2D warps the input position using current domain warp settings - /// - /// - /// Example usage with GetNoise - /// DomainWarp(coord) - /// noise = GetNoise(x, y) - /// - public void DomainWarp(Vector2 coord) - { - switch (mFractalType) - { - default: - DomainWarpSingle(coord); - break; - case DomainWarpProgressive: - DomainWarpFractalProgressive(coord); - break; - case DomainWarpIndependent: - DomainWarpFractalIndependent(coord); - break; - } - } - - /// - /// 3D warps the input position using current domain warp settings - /// - /// - /// Example usage with GetNoise - /// DomainWarp(coord) - /// noise = GetNoise(x, y, z) - /// - public void DomainWarp(Vector3 coord) - { - switch (mFractalType) - { - default: - DomainWarpSingle(coord); - break; - case DomainWarpProgressive: - DomainWarpFractalProgressive(coord); - break; - case DomainWarpIndependent: - DomainWarpFractalIndependent(coord); - break; - } - } - - - private static final float[] Gradients2D = { - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f, - 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f, - 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f, - -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f, - -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f, - -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f, - 0.38268343236509f, 0.923879532511287f, 0.923879532511287f, 0.38268343236509f, 0.923879532511287f, -0.38268343236509f, 0.38268343236509f, -0.923879532511287f, - -0.38268343236509f, -0.923879532511287f, -0.923879532511287f, -0.38268343236509f, -0.923879532511287f, 0.38268343236509f, -0.38268343236509f, 0.923879532511287f, - }; - - private static final float[] RandVecs2D = { - -0.2700222198f, -0.9628540911f, 0.3863092627f, -0.9223693152f, 0.04444859006f, -0.999011673f, -0.5992523158f, -0.8005602176f, -0.7819280288f, 0.6233687174f, 0.9464672271f, 0.3227999196f, -0.6514146797f, -0.7587218957f, 0.9378472289f, 0.347048376f, - -0.8497875957f, -0.5271252623f, -0.879042592f, 0.4767432447f, -0.892300288f, -0.4514423508f, -0.379844434f, -0.9250503802f, -0.9951650832f, 0.0982163789f, 0.7724397808f, -0.6350880136f, 0.7573283322f, -0.6530343002f, -0.9928004525f, -0.119780055f, - -0.0532665713f, 0.9985803285f, 0.9754253726f, -0.2203300762f, -0.7665018163f, 0.6422421394f, 0.991636706f, 0.1290606184f, -0.994696838f, 0.1028503788f, -0.5379205513f, -0.84299554f, 0.5022815471f, -0.8647041387f, 0.4559821461f, -0.8899889226f, - -0.8659131224f, -0.5001944266f, 0.0879458407f, -0.9961252577f, -0.5051684983f, 0.8630207346f, 0.7753185226f, -0.6315704146f, -0.6921944612f, 0.7217110418f, -0.5191659449f, -0.8546734591f, 0.8978622882f, -0.4402764035f, -0.1706774107f, 0.9853269617f, - -0.9353430106f, -0.3537420705f, -0.9992404798f, 0.03896746794f, -0.2882064021f, -0.9575683108f, -0.9663811329f, 0.2571137995f, -0.8759714238f, -0.4823630009f, -0.8303123018f, -0.5572983775f, 0.05110133755f, -0.9986934731f, -0.8558373281f, -0.5172450752f, - 0.09887025282f, 0.9951003332f, 0.9189016087f, 0.3944867976f, -0.2439375892f, -0.9697909324f, -0.8121409387f, -0.5834613061f, -0.9910431363f, 0.1335421355f, 0.8492423985f, -0.5280031709f, -0.9717838994f, -0.2358729591f, 0.9949457207f, 0.1004142068f, - 0.6241065508f, -0.7813392434f, 0.662910307f, 0.7486988212f, -0.7197418176f, 0.6942418282f, -0.8143370775f, -0.5803922158f, 0.104521054f, -0.9945226741f, -0.1065926113f, -0.9943027784f, 0.445799684f, -0.8951327509f, 0.105547406f, 0.9944142724f, - -0.992790267f, 0.1198644477f, -0.8334366408f, 0.552615025f, 0.9115561563f, -0.4111755999f, 0.8285544909f, -0.5599084351f, 0.7217097654f, -0.6921957921f, 0.4940492677f, -0.8694339084f, -0.3652321272f, -0.9309164803f, -0.9696606758f, 0.2444548501f, - 0.08925509731f, -0.996008799f, 0.5354071276f, -0.8445941083f, -0.1053576186f, 0.9944343981f, -0.9890284586f, 0.1477251101f, 0.004856104961f, 0.9999882091f, 0.9885598478f, 0.1508291331f, 0.9286129562f, -0.3710498316f, -0.5832393863f, -0.8123003252f, - 0.3015207509f, 0.9534596146f, -0.9575110528f, 0.2883965738f, 0.9715802154f, -0.2367105511f, 0.229981792f, 0.9731949318f, 0.955763816f, -0.2941352207f, 0.740956116f, 0.6715534485f, -0.9971513787f, -0.07542630764f, 0.6905710663f, -0.7232645452f, - -0.290713703f, -0.9568100872f, 0.5912777791f, -0.8064679708f, -0.9454592212f, -0.325740481f, 0.6664455681f, 0.74555369f, 0.6236134912f, 0.7817328275f, 0.9126993851f, -0.4086316587f, -0.8191762011f, 0.5735419353f, -0.8812745759f, -0.4726046147f, - 0.9953313627f, 0.09651672651f, 0.9855650846f, -0.1692969699f, -0.8495980887f, 0.5274306472f, 0.6174853946f, -0.7865823463f, 0.8508156371f, 0.52546432f, 0.9985032451f, -0.05469249926f, 0.1971371563f, -0.9803759185f, 0.6607855748f, -0.7505747292f, - -0.03097494063f, 0.9995201614f, -0.6731660801f, 0.739491331f, -0.7195018362f, -0.6944905383f, 0.9727511689f, 0.2318515979f, 0.9997059088f, -0.0242506907f, 0.4421787429f, -0.8969269532f, 0.9981350961f, -0.061043673f, -0.9173660799f, -0.3980445648f, - -0.8150056635f, -0.5794529907f, -0.8789331304f, 0.4769450202f, 0.0158605829f, 0.999874213f, -0.8095464474f, 0.5870558317f, -0.9165898907f, -0.3998286786f, -0.8023542565f, 0.5968480938f, -0.5176737917f, 0.8555780767f, -0.8154407307f, -0.5788405779f, - 0.4022010347f, -0.9155513791f, -0.9052556868f, -0.4248672045f, 0.7317445619f, 0.6815789728f, -0.5647632201f, -0.8252529947f, -0.8403276335f, -0.5420788397f, -0.9314281527f, 0.363925262f, 0.5238198472f, 0.8518290719f, 0.7432803869f, -0.6689800195f, - -0.985371561f, -0.1704197369f, 0.4601468731f, 0.88784281f, 0.825855404f, 0.5638819483f, 0.6182366099f, 0.7859920446f, 0.8331502863f, -0.553046653f, 0.1500307506f, 0.9886813308f, -0.662330369f, -0.7492119075f, -0.668598664f, 0.743623444f, - 0.7025606278f, 0.7116238924f, -0.5419389763f, -0.8404178401f, -0.3388616456f, 0.9408362159f, 0.8331530315f, 0.5530425174f, -0.2989720662f, -0.9542618632f, 0.2638522993f, 0.9645630949f, 0.124108739f, -0.9922686234f, -0.7282649308f, -0.6852956957f, - 0.6962500149f, 0.7177993569f, -0.9183535368f, 0.3957610156f, -0.6326102274f, -0.7744703352f, -0.9331891859f, -0.359385508f, -0.1153779357f, -0.9933216659f, 0.9514974788f, -0.3076565421f, -0.08987977445f, -0.9959526224f, 0.6678496916f, 0.7442961705f, - 0.7952400393f, -0.6062947138f, -0.6462007402f, -0.7631674805f, -0.2733598753f, 0.9619118351f, 0.9669590226f, -0.254931851f, -0.9792894595f, 0.2024651934f, -0.5369502995f, -0.8436138784f, -0.270036471f, -0.9628500944f, -0.6400277131f, 0.7683518247f, - -0.7854537493f, -0.6189203566f, 0.06005905383f, -0.9981948257f, -0.02455770378f, 0.9996984141f, -0.65983623f, 0.751409442f, -0.6253894466f, -0.7803127835f, -0.6210408851f, -0.7837781695f, 0.8348888491f, 0.5504185768f, -0.1592275245f, 0.9872419133f, - 0.8367622488f, 0.5475663786f, -0.8675753916f, -0.4973056806f, -0.2022662628f, -0.9793305667f, 0.9399189937f, 0.3413975472f, 0.9877404807f, -0.1561049093f, -0.9034455656f, 0.4287028224f, 0.1269804218f, -0.9919052235f, -0.3819600854f, 0.924178821f, - 0.9754625894f, 0.2201652486f, -0.3204015856f, -0.9472818081f, -0.9874760884f, 0.1577687387f, 0.02535348474f, -0.9996785487f, 0.4835130794f, -0.8753371362f, -0.2850799925f, -0.9585037287f, -0.06805516006f, -0.99768156f, -0.7885244045f, -0.6150034663f, - 0.3185392127f, -0.9479096845f, 0.8880043089f, 0.4598351306f, 0.6476921488f, -0.7619021462f, 0.9820241299f, 0.1887554194f, 0.9357275128f, -0.3527237187f, -0.8894895414f, 0.4569555293f, 0.7922791302f, 0.6101588153f, 0.7483818261f, 0.6632681526f, - -0.7288929755f, -0.6846276581f, 0.8729032783f, -0.4878932944f, 0.8288345784f, 0.5594937369f, 0.08074567077f, 0.9967347374f, 0.9799148216f, -0.1994165048f, -0.580730673f, -0.8140957471f, -0.4700049791f, -0.8826637636f, 0.2409492979f, 0.9705377045f, - 0.9437816757f, -0.3305694308f, -0.8927998638f, -0.4504535528f, -0.8069622304f, 0.5906030467f, 0.06258973166f, 0.9980393407f, -0.9312597469f, 0.3643559849f, 0.5777449785f, 0.8162173362f, -0.3360095855f, -0.941858566f, 0.697932075f, -0.7161639607f, - -0.002008157227f, -0.9999979837f, -0.1827294312f, -0.9831632392f, -0.6523911722f, 0.7578824173f, -0.4302626911f, -0.9027037258f, -0.9985126289f, -0.05452091251f, -0.01028102172f, -0.9999471489f, -0.4946071129f, 0.8691166802f, -0.2999350194f, 0.9539596344f, - 0.8165471961f, 0.5772786819f, 0.2697460475f, 0.962931498f, -0.7306287391f, -0.6827749597f, -0.7590952064f, -0.6509796216f, -0.907053853f, 0.4210146171f, -0.5104861064f, -0.8598860013f, 0.8613350597f, 0.5080373165f, 0.5007881595f, -0.8655698812f, - -0.654158152f, 0.7563577938f, -0.8382755311f, -0.545246856f, 0.6940070834f, 0.7199681717f, 0.06950936031f, 0.9975812994f, 0.1702942185f, -0.9853932612f, 0.2695973274f, 0.9629731466f, 0.5519612192f, -0.8338697815f, 0.225657487f, -0.9742067022f, - 0.4215262855f, -0.9068161835f, 0.4881873305f, -0.8727388672f, -0.3683854996f, -0.9296731273f, -0.9825390578f, 0.1860564427f, 0.81256471f, 0.5828709909f, 0.3196460933f, -0.9475370046f, 0.9570913859f, 0.2897862643f, -0.6876655497f, -0.7260276109f, - -0.9988770922f, -0.047376731f, -0.1250179027f, 0.992154486f, -0.8280133617f, 0.560708367f, 0.9324863769f, -0.3612051451f, 0.6394653183f, 0.7688199442f, -0.01623847064f, -0.9998681473f, -0.9955014666f, -0.09474613458f, -0.81453315f, 0.580117012f, - 0.4037327978f, -0.9148769469f, 0.9944263371f, 0.1054336766f, -0.1624711654f, 0.9867132919f, -0.9949487814f, -0.100383875f, -0.6995302564f, 0.7146029809f, 0.5263414922f, -0.85027327f, -0.5395221479f, 0.841971408f, 0.6579370318f, 0.7530729462f, - 0.01426758847f, -0.9998982128f, -0.6734383991f, 0.7392433447f, 0.639412098f, -0.7688642071f, 0.9211571421f, 0.3891908523f, -0.146637214f, -0.9891903394f, -0.782318098f, 0.6228791163f, -0.5039610839f, -0.8637263605f, -0.7743120191f, -0.6328039957f, - }; - - private static final float[] Gradients3D = { - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0, - 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0, - 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0, - 1, 1, 0, 0, 0,-1, 1, 0, -1, 1, 0, 0, 0,-1,-1, 0 - }; - - private static final float[] RandVecs3D = { - -0.7292736885f, -0.6618439697f, 0.1735581948f, 0, 0.790292081f, -0.5480887466f, -0.2739291014f, 0, 0.7217578935f, 0.6226212466f, -0.3023380997f, 0, 0.565683137f, -0.8208298145f, -0.0790000257f, 0, 0.760049034f, -0.5555979497f, -0.3370999617f, 0, 0.3713945616f, 0.5011264475f, 0.7816254623f, 0, -0.1277062463f, -0.4254438999f, -0.8959289049f, 0, -0.2881560924f, -0.5815838982f, 0.7607405838f, 0, - 0.5849561111f, -0.662820239f, -0.4674352136f, 0, 0.3307171178f, 0.0391653737f, 0.94291689f, 0, 0.8712121778f, -0.4113374369f, -0.2679381538f, 0, 0.580981015f, 0.7021915846f, 0.4115677815f, 0, 0.503756873f, 0.6330056931f, -0.5878203852f, 0, 0.4493712205f, 0.601390195f, 0.6606022552f, 0, -0.6878403724f, 0.09018890807f, -0.7202371714f, 0, -0.5958956522f, -0.6469350577f, 0.475797649f, 0, - -0.5127052122f, 0.1946921978f, -0.8361987284f, 0, -0.9911507142f, -0.05410276466f, -0.1212153153f, 0, -0.2149721042f, 0.9720882117f, -0.09397607749f, 0, -0.7518650936f, -0.5428057603f, 0.3742469607f, 0, 0.5237068895f, 0.8516377189f, -0.02107817834f, 0, 0.6333504779f, 0.1926167129f, -0.7495104896f, 0, -0.06788241606f, 0.3998305789f, 0.9140719259f, 0, -0.5538628599f, -0.4729896695f, -0.6852128902f, 0, - -0.7261455366f, -0.5911990757f, 0.3509933228f, 0, -0.9229274737f, -0.1782808786f, 0.3412049336f, 0, -0.6968815002f, 0.6511274338f, 0.3006480328f, 0, 0.9608044783f, -0.2098363234f, -0.1811724921f, 0, 0.06817146062f, -0.9743405129f, 0.2145069156f, 0, -0.3577285196f, -0.6697087264f, -0.6507845481f, 0, -0.1868621131f, 0.7648617052f, -0.6164974636f, 0, -0.6541697588f, 0.3967914832f, 0.6439087246f, 0, - 0.6993340405f, -0.6164538506f, 0.3618239211f, 0, -0.1546665739f, 0.6291283928f, 0.7617583057f, 0, -0.6841612949f, -0.2580482182f, -0.6821542638f, 0, 0.5383980957f, 0.4258654885f, 0.7271630328f, 0, -0.5026987823f, -0.7939832935f, -0.3418836993f, 0, 0.3202971715f, 0.2834415347f, 0.9039195862f, 0, 0.8683227101f, -0.0003762656404f, -0.4959995258f, 0, 0.791120031f, -0.08511045745f, 0.6057105799f, 0, - -0.04011016052f, -0.4397248749f, 0.8972364289f, 0, 0.9145119872f, 0.3579346169f, -0.1885487608f, 0, -0.9612039066f, -0.2756484276f, 0.01024666929f, 0, 0.6510361721f, -0.2877799159f, -0.7023778346f, 0, -0.2041786351f, 0.7365237271f, 0.644859585f, 0, -0.7718263711f, 0.3790626912f, 0.5104855816f, 0, -0.3060082741f, -0.7692987727f, 0.5608371729f, 0, 0.454007341f, -0.5024843065f, 0.7357899537f, 0, - 0.4816795475f, 0.6021208291f, -0.6367380315f, 0, 0.6961980369f, -0.3222197429f, 0.641469197f, 0, -0.6532160499f, -0.6781148932f, 0.3368515753f, 0, 0.5089301236f, -0.6154662304f, -0.6018234363f, 0, -0.1635919754f, -0.9133604627f, -0.372840892f, 0, 0.52408019f, -0.8437664109f, 0.1157505864f, 0, 0.5902587356f, 0.4983817807f, -0.6349883666f, 0, 0.5863227872f, 0.494764745f, 0.6414307729f, 0, - 0.6779335087f, 0.2341345225f, 0.6968408593f, 0, 0.7177054546f, -0.6858979348f, 0.120178631f, 0, -0.5328819713f, -0.5205125012f, 0.6671608058f, 0, -0.8654874251f, -0.0700727088f, -0.4960053754f, 0, -0.2861810166f, 0.7952089234f, 0.5345495242f, 0, -0.04849529634f, 0.9810836427f, -0.1874115585f, 0, -0.6358521667f, 0.6058348682f, 0.4781800233f, 0, 0.6254794696f, -0.2861619734f, 0.7258696564f, 0, - -0.2585259868f, 0.5061949264f, -0.8227581726f, 0, 0.02136306781f, 0.5064016808f, -0.8620330371f, 0, 0.200111773f, 0.8599263484f, 0.4695550591f, 0, 0.4743561372f, 0.6014985084f, -0.6427953014f, 0, 0.6622993731f, -0.5202474575f, -0.5391679918f, 0, 0.08084972818f, -0.6532720452f, 0.7527940996f, 0, -0.6893687501f, 0.0592860349f, 0.7219805347f, 0, -0.1121887082f, -0.9673185067f, 0.2273952515f, 0, - 0.7344116094f, 0.5979668656f, -0.3210532909f, 0, 0.5789393465f, -0.2488849713f, 0.7764570201f, 0, 0.6988182827f, 0.3557169806f, -0.6205791146f, 0, -0.8636845529f, -0.2748771249f, -0.4224826141f, 0, -0.4247027957f, -0.4640880967f, 0.777335046f, 0, 0.5257722489f, -0.8427017621f, 0.1158329937f, 0, 0.9343830603f, 0.316302472f, -0.1639543925f, 0, -0.1016836419f, -0.8057303073f, -0.5834887393f, 0, - -0.6529238969f, 0.50602126f, -0.5635892736f, 0, -0.2465286165f, -0.9668205684f, -0.06694497494f, 0, -0.9776897119f, -0.2099250524f, -0.007368825344f, 0, 0.7736893337f, 0.5734244712f, 0.2694238123f, 0, -0.6095087895f, 0.4995678998f, 0.6155736747f, 0, 0.5794535482f, 0.7434546771f, 0.3339292269f, 0, -0.8226211154f, 0.08142581855f, 0.5627293636f, 0, -0.510385483f, 0.4703667658f, 0.7199039967f, 0, - -0.5764971849f, -0.07231656274f, -0.8138926898f, 0, 0.7250628871f, 0.3949971505f, -0.5641463116f, 0, -0.1525424005f, 0.4860840828f, -0.8604958341f, 0, -0.5550976208f, -0.4957820792f, 0.667882296f, 0, -0.1883614327f, 0.9145869398f, 0.357841725f, 0, 0.7625556724f, -0.5414408243f, -0.3540489801f, 0, -0.5870231946f, -0.3226498013f, -0.7424963803f, 0, 0.3051124198f, 0.2262544068f, -0.9250488391f, 0, - 0.6379576059f, 0.577242424f, -0.5097070502f, 0, -0.5966775796f, 0.1454852398f, -0.7891830656f, 0, -0.658330573f, 0.6555487542f, -0.3699414651f, 0, 0.7434892426f, 0.2351084581f, 0.6260573129f, 0, 0.5562114096f, 0.8264360377f, -0.0873632843f, 0, -0.3028940016f, -0.8251527185f, 0.4768419182f, 0, 0.1129343818f, -0.985888439f, -0.1235710781f, 0, 0.5937652891f, -0.5896813806f, 0.5474656618f, 0, - 0.6757964092f, -0.5835758614f, -0.4502648413f, 0, 0.7242302609f, -0.1152719764f, 0.6798550586f, 0, -0.9511914166f, 0.0753623979f, -0.2992580792f, 0, 0.2539470961f, -0.1886339355f, 0.9486454084f, 0, 0.571433621f, -0.1679450851f, -0.8032795685f, 0, -0.06778234979f, 0.3978269256f, 0.9149531629f, 0, 0.6074972649f, 0.733060024f, -0.3058922593f, 0, -0.5435478392f, 0.1675822484f, 0.8224791405f, 0, - -0.5876678086f, -0.3380045064f, -0.7351186982f, 0, -0.7967562402f, 0.04097822706f, -0.6029098428f, 0, -0.1996350917f, 0.8706294745f, 0.4496111079f, 0, -0.02787660336f, -0.9106232682f, -0.4122962022f, 0, -0.7797625996f, -0.6257634692f, 0.01975775581f, 0, -0.5211232846f, 0.7401644346f, -0.4249554471f, 0, 0.8575424857f, 0.4053272873f, -0.3167501783f, 0, 0.1045223322f, 0.8390195772f, -0.5339674439f, 0, - 0.3501822831f, 0.9242524096f, -0.1520850155f, 0, 0.1987849858f, 0.07647613266f, 0.9770547224f, 0, 0.7845996363f, 0.6066256811f, -0.1280964233f, 0, 0.09006737436f, -0.9750989929f, -0.2026569073f, 0, -0.8274343547f, -0.542299559f, 0.1458203587f, 0, -0.3485797732f, -0.415802277f, 0.840000362f, 0, -0.2471778936f, -0.7304819962f, -0.6366310879f, 0, -0.3700154943f, 0.8577948156f, 0.3567584454f, 0, - 0.5913394901f, -0.548311967f, -0.5913303597f, 0, 0.1204873514f, -0.7626472379f, -0.6354935001f, 0, 0.616959265f, 0.03079647928f, 0.7863922953f, 0, 0.1258156836f, -0.6640829889f, -0.7369967419f, 0, -0.6477565124f, -0.1740147258f, -0.7417077429f, 0, 0.6217889313f, -0.7804430448f, -0.06547655076f, 0, 0.6589943422f, -0.6096987708f, 0.4404473475f, 0, -0.2689837504f, -0.6732403169f, -0.6887635427f, 0, - -0.3849775103f, 0.5676542638f, 0.7277093879f, 0, 0.5754444408f, 0.8110471154f, -0.1051963504f, 0, 0.9141593684f, 0.3832947817f, 0.131900567f, 0, -0.107925319f, 0.9245493968f, 0.3654593525f, 0, 0.377977089f, 0.3043148782f, 0.8743716458f, 0, -0.2142885215f, -0.8259286236f, 0.5214617324f, 0, 0.5802544474f, 0.4148098596f, -0.7008834116f, 0, -0.1982660881f, 0.8567161266f, -0.4761596756f, 0, - -0.03381553704f, 0.3773180787f, -0.9254661404f, 0, -0.6867922841f, -0.6656597827f, 0.2919133642f, 0, 0.7731742607f, -0.2875793547f, -0.5652430251f, 0, -0.09655941928f, 0.9193708367f, -0.3813575004f, 0, 0.2715702457f, -0.9577909544f, -0.09426605581f, 0, 0.2451015704f, -0.6917998565f, -0.6792188003f, 0, 0.977700782f, -0.1753855374f, 0.1155036542f, 0, -0.5224739938f, 0.8521606816f, 0.02903615945f, 0, - -0.7734880599f, -0.5261292347f, 0.3534179531f, 0, -0.7134492443f, -0.269547243f, 0.6467878011f, 0, 0.1644037271f, 0.5105846203f, -0.8439637196f, 0, 0.6494635788f, 0.05585611296f, 0.7583384168f, 0, -0.4711970882f, 0.5017280509f, -0.7254255765f, 0, -0.6335764307f, -0.2381686273f, -0.7361091029f, 0, -0.9021533097f, -0.270947803f, -0.3357181763f, 0, -0.3793711033f, 0.872258117f, 0.3086152025f, 0, - -0.6855598966f, -0.3250143309f, 0.6514394162f, 0, 0.2900942212f, -0.7799057743f, -0.5546100667f, 0, -0.2098319339f, 0.85037073f, 0.4825351604f, 0, -0.4592603758f, 0.6598504336f, -0.5947077538f, 0, 0.8715945488f, 0.09616365406f, -0.4807031248f, 0, -0.6776666319f, 0.7118504878f, -0.1844907016f, 0, 0.7044377633f, 0.312427597f, 0.637304036f, 0, -0.7052318886f, -0.2401093292f, -0.6670798253f, 0, - 0.081921007f, -0.7207336136f, -0.6883545647f, 0, -0.6993680906f, -0.5875763221f, -0.4069869034f, 0, -0.1281454481f, 0.6419895885f, 0.7559286424f, 0, -0.6337388239f, -0.6785471501f, -0.3714146849f, 0, 0.5565051903f, -0.2168887573f, -0.8020356851f, 0, -0.5791554484f, 0.7244372011f, -0.3738578718f, 0, 0.1175779076f, -0.7096451073f, 0.6946792478f, 0, -0.6134619607f, 0.1323631078f, 0.7785527795f, 0, - 0.6984635305f, -0.02980516237f, -0.715024719f, 0, 0.8318082963f, -0.3930171956f, 0.3919597455f, 0, 0.1469576422f, 0.05541651717f, -0.9875892167f, 0, 0.708868575f, -0.2690503865f, 0.6520101478f, 0, 0.2726053183f, 0.67369766f, -0.68688995f, 0, -0.6591295371f, 0.3035458599f, -0.6880466294f, 0, 0.4815131379f, -0.7528270071f, 0.4487723203f, 0, 0.9430009463f, 0.1675647412f, -0.2875261255f, 0, - 0.434802957f, 0.7695304522f, -0.4677277752f, 0, 0.3931996188f, 0.594473625f, 0.7014236729f, 0, 0.7254336655f, -0.603925654f, 0.3301814672f, 0, 0.7590235227f, -0.6506083235f, 0.02433313207f, 0, -0.8552768592f, -0.3430042733f, 0.3883935666f, 0, -0.6139746835f, 0.6981725247f, 0.3682257648f, 0, -0.7465905486f, -0.5752009504f, 0.3342849376f, 0, 0.5730065677f, 0.810555537f, -0.1210916791f, 0, - -0.9225877367f, -0.3475211012f, -0.167514036f, 0, -0.7105816789f, -0.4719692027f, -0.5218416899f, 0, -0.08564609717f, 0.3583001386f, 0.929669703f, 0, -0.8279697606f, -0.2043157126f, 0.5222271202f, 0, 0.427944023f, 0.278165994f, 0.8599346446f, 0, 0.5399079671f, -0.7857120652f, -0.3019204161f, 0, 0.5678404253f, -0.5495413974f, -0.6128307303f, 0, -0.9896071041f, 0.1365639107f, -0.04503418428f, 0, - -0.6154342638f, -0.6440875597f, 0.4543037336f, 0, 0.1074204368f, -0.7946340692f, 0.5975094525f, 0, -0.3595449969f, -0.8885529948f, 0.28495784f, 0, -0.2180405296f, 0.1529888965f, 0.9638738118f, 0, -0.7277432317f, -0.6164050508f, -0.3007234646f, 0, 0.7249729114f, -0.00669719484f, 0.6887448187f, 0, -0.5553659455f, -0.5336586252f, 0.6377908264f, 0, 0.5137558015f, 0.7976208196f, -0.3160000073f, 0, - -0.3794024848f, 0.9245608561f, -0.03522751494f, 0, 0.8229248658f, 0.2745365933f, -0.4974176556f, 0, -0.5404114394f, 0.6091141441f, 0.5804613989f, 0, 0.8036581901f, -0.2703029469f, 0.5301601931f, 0, 0.6044318879f, 0.6832968393f, 0.4095943388f, 0, 0.06389988817f, 0.9658208605f, -0.2512108074f, 0, 0.1087113286f, 0.7402471173f, -0.6634877936f, 0, -0.713427712f, -0.6926784018f, 0.1059128479f, 0, - 0.6458897819f, -0.5724548511f, -0.5050958653f, 0, -0.6553931414f, 0.7381471625f, 0.159995615f, 0, 0.3910961323f, 0.9188871375f, -0.05186755998f, 0, -0.4879022471f, -0.5904376907f, 0.6429111375f, 0, 0.6014790094f, 0.7707441366f, -0.2101820095f, 0, -0.5677173047f, 0.7511360995f, 0.3368851762f, 0, 0.7858573506f, 0.226674665f, 0.5753666838f, 0, -0.4520345543f, -0.604222686f, -0.6561857263f, 0, - 0.002272116345f, 0.4132844051f, -0.9105991643f, 0, -0.5815751419f, -0.5162925989f, 0.6286591339f, 0, -0.03703704785f, 0.8273785755f, 0.5604221175f, 0, -0.5119692504f, 0.7953543429f, -0.3244980058f, 0, -0.2682417366f, -0.9572290247f, -0.1084387619f, 0, -0.2322482736f, -0.9679131102f, -0.09594243324f, 0, 0.3554328906f, -0.8881505545f, 0.2913006227f, 0, 0.7346520519f, -0.4371373164f, 0.5188422971f, 0, - 0.9985120116f, 0.04659011161f, -0.02833944577f, 0, -0.3727687496f, -0.9082481361f, 0.1900757285f, 0, 0.91737377f, -0.3483642108f, 0.1925298489f, 0, 0.2714911074f, 0.4147529736f, -0.8684886582f, 0, 0.5131763485f, -0.7116334161f, 0.4798207128f, 0, -0.8737353606f, 0.18886992f, -0.4482350644f, 0, 0.8460043821f, -0.3725217914f, 0.3814499973f, 0, 0.8978727456f, -0.1780209141f, -0.4026575304f, 0, - 0.2178065647f, -0.9698322841f, -0.1094789531f, 0, -0.1518031304f, -0.7788918132f, -0.6085091231f, 0, -0.2600384876f, -0.4755398075f, -0.8403819825f, 0, 0.572313509f, -0.7474340931f, -0.3373418503f, 0, -0.7174141009f, 0.1699017182f, -0.6756111411f, 0, -0.684180784f, 0.02145707593f, -0.7289967412f, 0, -0.2007447902f, 0.06555605789f, -0.9774476623f, 0, -0.1148803697f, -0.8044887315f, 0.5827524187f, 0, - -0.7870349638f, 0.03447489231f, 0.6159443543f, 0, -0.2015596421f, 0.6859872284f, 0.6991389226f, 0, -0.08581082512f, -0.10920836f, -0.9903080513f, 0, 0.5532693395f, 0.7325250401f, -0.396610771f, 0, -0.1842489331f, -0.9777375055f, -0.1004076743f, 0, 0.0775473789f, -0.9111505856f, 0.4047110257f, 0, 0.1399838409f, 0.7601631212f, -0.6344734459f, 0, 0.4484419361f, -0.845289248f, 0.2904925424f, 0 - }; - - - private static float FastMin(float a, float b) { return a < b ? a : b; } - - private static float FastMax(float a, float b) { return a > b ? a : b; } - - private static float FastAbs(float f) { return f < 0 ? -f : f; } - - private static float FastSqrt(float f) { return (float)Math.sqrt(f); } - - private static int FastFloor(/*FNLfloat*/ float f) { return f >= 0 ? (int)f : (int)f - 1; } - - private static int FastRound(/*FNLfloat*/ float f) { return f >= 0 ? (int)(f + 0.5f) : (int)(f - 0.5f); } - - private static float Lerp(float a, float b, float t) { return a + t * (b - a); } - - private static float InterpHermite(float t) { return t * t * (3 - 2 * t); } - - private static float InterpQuintic(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } - - private static float CubicLerp(float a, float b, float c, float d, float t) - { - float p = (d - c) - (a - b); - return t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b; - } - - private static float PingPong(float t) - { - t -= (int)(t * 0.5f) * 2; - return t < 1 ? t : 2 - t; - } - - private void CalculateFractalBounding() - { - float gain = FastAbs(mGain); - float amp = gain; - float ampFractal = 1.0f; - for (int i = 1; i < mOctaves; i++) - { - ampFractal += amp; - amp *= gain; - } - mFractalBounding = 1 / ampFractal; - } - - // Hashing - private static final int PrimeX = 501125321; - private static final int PrimeY = 1136930381; - private static final int PrimeZ = 1720413743; - - private static int Hash(int seed, int xPrimed, int yPrimed) - { - int hash = seed ^ xPrimed ^ yPrimed; - - hash *= 0x27d4eb2d; - return hash; - } - - private static int Hash(int seed, int xPrimed, int yPrimed, int zPrimed) - { - int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed; - - hash *= 0x27d4eb2d; - return hash; - } - - private static float ValCoord(int seed, int xPrimed, int yPrimed) - { - int hash = Hash(seed, xPrimed, yPrimed); - - hash *= hash; - hash ^= hash << 19; - return hash * (1 / 2147483648.0f); - } - - private static float ValCoord(int seed, int xPrimed, int yPrimed, int zPrimed) - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - - hash *= hash; - hash ^= hash << 19; - return hash * (1 / 2147483648.0f); - } - - private static float GradCoord(int seed, int xPrimed, int yPrimed, float xd, float yd) - { - int hash = Hash(seed, xPrimed, yPrimed); - hash ^= hash >> 15; - hash &= 127 << 1; - - float xg = Gradients2D[hash]; - float yg = Gradients2D[hash | 1]; - - return xd * xg + yd * yg; - } - - private static float GradCoord(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd) - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - hash ^= hash >> 15; - hash &= 63 << 2; - - float xg = Gradients3D[hash]; - float yg = Gradients3D[hash | 1]; - float zg = Gradients3D[hash | 2]; - - return xd * xg + yd * yg + zd * zg; - } - - - // Generic noise gen - - private float GenNoiseSingle(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - switch (mNoiseType) - { - case OpenSimplex2: - return SingleSimplex(seed, x, y); - case OpenSimplex2S: - return SingleOpenSimplex2S(seed, x, y); - case Cellular: - return SingleCellular(seed, x, y); - case Perlin: - return SinglePerlin(seed, x, y); - case ValueCubic: - return SingleValueCubic(seed, x, y); - case Value: - return SingleValue(seed, x, y); - default: - return 0; - } - } - - private float GenNoiseSingle(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - switch (mNoiseType) - { - case OpenSimplex2: - return SingleOpenSimplex2(seed, x, y, z); - case OpenSimplex2S: - return SingleOpenSimplex2S(seed, x, y, z); - case Cellular: - return SingleCellular(seed, x, y, z); - case Perlin: - return SinglePerlin(seed, x, y, z); - case ValueCubic: - return SingleValueCubic(seed, x, y, z); - case Value: - return SingleValue(seed, x, y, z); - default: - return 0; - } - } - - - // Noise Coordinate Transforms (frequency, and possible skew or rotation) - - private void UpdateTransformType3D() - { - switch (mRotationType3D) - { - case ImproveXYPlanes: - mTransformType3D = TransformType3D.ImproveXYPlanes; - break; - case ImproveXZPlanes: - mTransformType3D = TransformType3D.ImproveXZPlanes; - break; - default: - switch (mNoiseType) - { - case OpenSimplex2: - case OpenSimplex2S: - mTransformType3D = TransformType3D.DefaultOpenSimplex2; - break; - default: - mTransformType3D = TransformType3D.None; - break; - } - break; - } - } - - private void UpdateWarpTransformType3D() - { - switch (mRotationType3D) - { - case ImproveXYPlanes: - mWarpTransformType3D = TransformType3D.ImproveXYPlanes; - break; - case ImproveXZPlanes: - mWarpTransformType3D = TransformType3D.ImproveXZPlanes; - break; - default: - switch (mDomainWarpType) - { - case OpenSimplex2: - case OpenSimplex2Reduced: - mWarpTransformType3D = TransformType3D.DefaultOpenSimplex2; - break; - default: - mWarpTransformType3D = TransformType3D.None; - break; - } - break; - } - } - - - // Fractal FBm - - private float GenFractalFBm(/*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = GenNoiseSingle(seed++, x, y); - sum += noise * amp; - amp *= Lerp(1.0f, FastMin(noise + 1, 2) * 0.5f, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - private float GenFractalFBm(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = GenNoiseSingle(seed++, x, y, z); - sum += noise * amp; - amp *= Lerp(1.0f, (noise + 1) * 0.5f, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - z *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - - // Fractal Ridged - - private float GenFractalRidged(/*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = FastAbs(GenNoiseSingle(seed++, x, y)); - sum += (noise * -2 + 1) * amp; - amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - private float GenFractalRidged(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = FastAbs(GenNoiseSingle(seed++, x, y, z)); - sum += (noise * -2 + 1) * amp; - amp *= Lerp(1.0f, 1 - noise, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - z *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - - // Fractal PingPong - - private float GenFractalPingPong(/*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = PingPong((GenNoiseSingle(seed++, x, y) + 1) * mPingPongStrength); - sum += (noise - 0.5f) * 2 * amp; - amp *= Lerp(1.0f, noise, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - private float GenFractalPingPong(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - int seed = mSeed; - float sum = 0; - float amp = mFractalBounding; - - for (int i = 0; i < mOctaves; i++) - { - float noise = PingPong((GenNoiseSingle(seed++, x, y, z) + 1) * mPingPongStrength); - sum += (noise - 0.5f) * 2 * amp; - amp *= Lerp(1.0f, noise, mWeightedStrength); - - x *= mLacunarity; - y *= mLacunarity; - z *= mLacunarity; - amp *= mGain; - } - - return sum; - } - - - // Simplex/OpenSimplex2 Noise - - private float SingleSimplex(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - // 2D OpenSimplex2 case uses the same algorithm as ordinary Simplex. - - final float SQRT3 = 1.7320508075688772935274463415059f; - final float G2 = (3 - SQRT3) / 6; - - /* - * --- Skew moved to switch statements before fractal evaluation --- - * final FNLfloat F2 = 0.5f * (SQRT3 - 1); - * FNLfloat s = (x + y) * F2; - * x += s; y += s; - */ - - int i = FastFloor(x); - int j = FastFloor(y); - float xi = (float)(x - i); - float yi = (float)(y - j); - - float t = (xi + yi) * G2; - float x0 = (float)(xi - t); - float y0 = (float)(yi - t); - - i *= PrimeX; - j *= PrimeY; - - float n0, n1, n2; - - float a = 0.5f - x0 * x0 - y0 * y0; - if (a <= 0) n0 = 0; - else - { - n0 = (a * a) * (a * a) * GradCoord(seed, i, j, x0, y0); - } - - float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); - if (c <= 0) n2 = 0; - else - { - float x2 = x0 + (2 * (float)G2 - 1); - float y2 = y0 + (2 * (float)G2 - 1); - n2 = (c * c) * (c * c) * GradCoord(seed, i + PrimeX, j + PrimeY, x2, y2); - } - - if (y0 > x0) - { - float x1 = x0 + (float)G2; - float y1 = y0 + ((float)G2 - 1); - float b = 0.5f - x1 * x1 - y1 * y1; - if (b <= 0) n1 = 0; - else - { - n1 = (b * b) * (b * b) * GradCoord(seed, i, j + PrimeY, x1, y1); - } - } - else - { - float x1 = x0 + ((float)G2 - 1); - float y1 = y0 + (float)G2; - float b = 0.5f - x1 * x1 - y1 * y1; - if (b <= 0) n1 = 0; - else - { - n1 = (b * b) * (b * b) * GradCoord(seed, i + PrimeX, j, x1, y1); - } - } - - return (n0 + n1 + n2) * 99.83685446303647f; - } - - private float SingleOpenSimplex2(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - // 3D OpenSimplex2 case uses two offset rotated cube grids. - - /* - * --- Rotation moved to switch statements before fractal evaluation --- - * final FNLfloat R3 = (FNLfloat)(2.0 / 3.0); - * FNLfloat r = (x + y + z) * R3; // Rotation, not skew - * x = r - x; y = r - y; z = r - z; - */ - - int i = FastRound(x); - int j = FastRound(y); - int k = FastRound(z); - float x0 = (float)(x - i); - float y0 = (float)(y - j); - float z0 = (float)(z - k); - - int xNSign = (int)(-1.0f - x0) | 1; - int yNSign = (int)(-1.0f - y0) | 1; - int zNSign = (int)(-1.0f - z0) | 1; - - float ax0 = xNSign * -x0; - float ay0 = yNSign * -y0; - float az0 = zNSign * -z0; - - i *= PrimeX; - j *= PrimeY; - k *= PrimeZ; - - float value = 0; - float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); - - for (int l = 0; ; l++) - { - if (a > 0) - { - value += (a * a) * (a * a) * GradCoord(seed, i, j, k, x0, y0, z0); - } - - if (ax0 >= ay0 && ax0 >= az0) - { - float b = a + ax0 + ax0; - if (b > 1) - { - b -= 1; - value += (b * b) * (b * b) * GradCoord(seed, i - xNSign * PrimeX, j, k, x0 + xNSign, y0, z0); - } - } - else if (ay0 > ax0 && ay0 >= az0) - { - float b = a + ay0 + ay0; - if (b > 1) - { - b -= 1; - value += (b * b) * (b * b) * GradCoord(seed, i, j - yNSign * PrimeY, k, x0, y0 + yNSign, z0); - } - } - else - { - float b = a + az0 + az0; - if (b > 1) - { - b -= 1; - value += (b * b) * (b * b) * GradCoord(seed, i, j, k - zNSign * PrimeZ, x0, y0, z0 + zNSign); - } - } - - if (l == 1) break; - - ax0 = 0.5f - ax0; - ay0 = 0.5f - ay0; - az0 = 0.5f - az0; - - x0 = xNSign * ax0; - y0 = yNSign * ay0; - z0 = zNSign * az0; - - a += (0.75f - ax0) - (ay0 + az0); - - i += (xNSign >> 1) & PrimeX; - j += (yNSign >> 1) & PrimeY; - k += (zNSign >> 1) & PrimeZ; - - xNSign = -xNSign; - yNSign = -yNSign; - zNSign = -zNSign; - - seed = ~seed; - } - - return value * 32.69428253173828125f; - } - - - // OpenSimplex2S Noise - - private float SingleOpenSimplex2S(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - // 2D OpenSimplex2S case is a modified 2D simplex noise. - - final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float)1.7320508075688772935274463415059; - final /*FNLfloat*/ float G2 = (3 - SQRT3) / 6; - - /* - * --- Skew moved to TransformNoiseCoordinate method --- - * final FNLfloat F2 = 0.5f * (SQRT3 - 1); - * FNLfloat s = (x + y) * F2; - * x += s; y += s; - */ - - int i = FastFloor(x); - int j = FastFloor(y); - float xi = (float)(x - i); - float yi = (float)(y - j); - - i *= PrimeX; - j *= PrimeY; - int i1 = i + PrimeX; - int j1 = j + PrimeY; - - float t = (xi + yi) * (float)G2; - float x0 = xi - t; - float y0 = yi - t; - - float a0 = (2.0f / 3.0f) - x0 * x0 - y0 * y0; - float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, i, j, x0, y0); - - float a1 = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a0); - float x1 = x0 - (float)(1 - 2 * G2); - float y1 = y0 - (float)(1 - 2 * G2); - value += (a1 * a1) * (a1 * a1) * GradCoord(seed, i1, j1, x1, y1); - - // Nested conditionals were faster than compact bit logic/arithmetic. - float xmyi = xi - yi; - if (t > G2) - { - if (xi + xmyi > 1) - { - float x2 = x0 + (float)(3 * G2 - 2); - float y2 = y0 + (float)(3 * G2 - 1); - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + (PrimeX << 1), j + PrimeY, x2, y2); - } - } - else - { - float x2 = x0 + (float)G2; - float y2 = y0 + (float)(G2 - 1); - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); - } - } - - if (yi - xmyi > 1) - { - float x3 = x0 + (float)(3 * G2 - 1); - float y3 = y0 + (float)(3 * G2 - 2); - float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; - if (a3 > 0) - { - value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j + (PrimeY << 1), x3, y3); - } - } - else - { - float x3 = x0 + (float)(G2 - 1); - float y3 = y0 + (float)G2; - float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3; - if (a3 > 0) - { - value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j, x3, y3); - } - } - } - else - { - if (xi + xmyi < 0) - { - float x2 = x0 + (float)(1 - G2); - float y2 = y0 - (float)G2; - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i - PrimeX, j, x2, y2); - } - } - else - { - float x2 = x0 + (float)(G2 - 1); - float y2 = y0 + (float)G2; - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + PrimeX, j, x2, y2); - } - } - - if (yi < xmyi) - { - float x2 = x0 - (float)G2; - float y2 = y0 - (float)(G2 - 1); - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j - PrimeY, x2, y2); - } - } - else - { - float x2 = x0 + (float)G2; - float y2 = y0 + (float)(G2 - 1); - float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2; - if (a2 > 0) - { - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2); - } - } - } - - return value * 18.24196194486065f; - } - - private float SingleOpenSimplex2S(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - // 3D OpenSimplex2S case uses two offset rotated cube grids. - - /* - * --- Rotation moved to TransformNoiseCoordinate method --- - * final FNLfloat R3 = (FNLfloat)(2.0 / 3.0); - * FNLfloat r = (x + y + z) * R3; // Rotation, not skew - * x = r - x; y = r - y; z = r - z; - */ - - int i = FastFloor(x); - int j = FastFloor(y); - int k = FastFloor(z); - float xi = (float)(x - i); - float yi = (float)(y - j); - float zi = (float)(z - k); - - i *= PrimeX; - j *= PrimeY; - k *= PrimeZ; - int seed2 = seed + 1293373; - - int xNMask = (int)(-0.5f - xi); - int yNMask = (int)(-0.5f - yi); - int zNMask = (int)(-0.5f - zi); - - float x0 = xi + xNMask; - float y0 = yi + yNMask; - float z0 = zi + zNMask; - float a0 = 0.75f - x0 * x0 - y0 * y0 - z0 * z0; - float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, - i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x0, y0, z0); - - float x1 = xi - 0.5f; - float y1 = yi - 0.5f; - float z1 = zi - 0.5f; - float a1 = 0.75f - x1 * x1 - y1 * y1 - z1 * z1; - value += (a1 * a1) * (a1 * a1) * GradCoord(seed2, - i + PrimeX, j + PrimeY, k + PrimeZ, x1, y1, z1); - - float xAFlipMask0 = ((xNMask | 1) << 1) * x1; - float yAFlipMask0 = ((yNMask | 1) << 1) * y1; - float zAFlipMask0 = ((zNMask | 1) << 1) * z1; - float xAFlipMask1 = (-2 - (xNMask << 2)) * x1 - 1.0f; - float yAFlipMask1 = (-2 - (yNMask << 2)) * y1 - 1.0f; - float zAFlipMask1 = (-2 - (zNMask << 2)) * z1 - 1.0f; - - boolean skip5 = false; - float a2 = xAFlipMask0 + a0; - if (a2 > 0) - { - float x2 = x0 - (xNMask | 1); - float y2 = y0; - float z2 = z0; - value += (a2 * a2) * (a2 * a2) * GradCoord(seed, - i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x2, y2, z2); - } - else - { - float a3 = yAFlipMask0 + zAFlipMask0 + a0; - if (a3 > 0) - { - float x3 = x0; - float y3 = y0 - (yNMask | 1); - float z3 = z0 - (zNMask | 1); - value += (a3 * a3) * (a3 * a3) * GradCoord(seed, - i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (~zNMask & PrimeZ), x3, y3, z3); - } - - float a4 = xAFlipMask1 + a1; - if (a4 > 0) - { - float x4 = (xNMask | 1) + x1; - float y4 = y1; - float z4 = z1; - value += (a4 * a4) * (a4 * a4) * GradCoord(seed2, - i + (xNMask & (PrimeX * 2)), j + PrimeY, k + PrimeZ, x4, y4, z4); - skip5 = true; - } - } - - boolean skip9 = false; - float a6 = yAFlipMask0 + a0; - if (a6 > 0) - { - float x6 = x0; - float y6 = y0 - (yNMask | 1); - float z6 = z0; - value += (a6 * a6) * (a6 * a6) * GradCoord(seed, - i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), x6, y6, z6); - } - else - { - float a7 = xAFlipMask0 + zAFlipMask0 + a0; - if (a7 > 0) - { - float x7 = x0 - (xNMask | 1); - float y7 = y0; - float z7 = z0 - (zNMask | 1); - value += (a7 * a7) * (a7 * a7) * GradCoord(seed, - i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), x7, y7, z7); - } - - float a8 = yAFlipMask1 + a1; - if (a8 > 0) - { - float x8 = x1; - float y8 = (yNMask | 1) + y1; - float z8 = z1; - value += (a8 * a8) * (a8 * a8) * GradCoord(seed2, - i + PrimeX, j + (yNMask & (PrimeY << 1)), k + PrimeZ, x8, y8, z8); - skip9 = true; - } - } - - boolean skipD = false; - float aA = zAFlipMask0 + a0; - if (aA > 0) - { - float xA = x0; - float yA = y0; - float zA = z0 - (zNMask | 1); - value += (aA * aA) * (aA * aA) * GradCoord(seed, - i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), xA, yA, zA); - } - else - { - float aB = xAFlipMask0 + yAFlipMask0 + a0; - if (aB > 0) - { - float xB = x0 - (xNMask | 1); - float yB = y0 - (yNMask | 1); - float zB = z0; - value += (aB * aB) * (aB * aB) * GradCoord(seed, - i + (~xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), xB, yB, zB); - } - - float aC = zAFlipMask1 + a1; - if (aC > 0) - { - float xC = x1; - float yC = y1; - float zC = (zNMask | 1) + z1; - value += (aC * aC) * (aC * aC) * GradCoord(seed2, - i + PrimeX, j + PrimeY, k + (zNMask & (PrimeZ << 1)), xC, yC, zC); - skipD = true; - } - } - - if (!skip5) - { - float a5 = yAFlipMask1 + zAFlipMask1 + a1; - if (a5 > 0) - { - float x5 = x1; - float y5 = (yNMask | 1) + y1; - float z5 = (zNMask | 1) + z1; - value += (a5 * a5) * (a5 * a5) * GradCoord(seed2, - i + PrimeX, j + (yNMask & (PrimeY << 1)), k + (zNMask & (PrimeZ << 1)), x5, y5, z5); - } - } - - if (!skip9) - { - float a9 = xAFlipMask1 + zAFlipMask1 + a1; - if (a9 > 0) - { - float x9 = (xNMask | 1) + x1; - float y9 = y1; - float z9 = (zNMask | 1) + z1; - value += (a9 * a9) * (a9 * a9) * GradCoord(seed2, - i + (xNMask & (PrimeX * 2)), j + PrimeY, k + (zNMask & (PrimeZ << 1)), x9, y9, z9); - } - } - - if (!skipD) - { - float aD = xAFlipMask1 + yAFlipMask1 + a1; - if (aD > 0) - { - float xD = (xNMask | 1) + x1; - float yD = (yNMask | 1) + y1; - float zD = z1; - value += (aD * aD) * (aD * aD) * GradCoord(seed2, - i + (xNMask & (PrimeX << 1)), j + (yNMask & (PrimeY << 1)), k + PrimeZ, xD, yD, zD); - } - } - - return value * 9.046026385208288f; - } - - - // Cellular Noise - - private float SingleCellular(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - int xr = FastRound(x); - int yr = FastRound(y); - - float distance0 = Float.MAX_VALUE; - float distance1 = Float.MAX_VALUE; - int closestHash = 0; - - float cellularJitter = 0.43701595f * mCellularJitterModifier; - - int xPrimed = (xr - 1) * PrimeX; - int yPrimedBase = (yr - 1) * PrimeY; - - switch (mCellularDistanceFunction) - { - default: - case Euclidean: - case EuclideanSq: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int hash = Hash(seed, xPrimed, yPrimed); - int idx = hash & (255 << 1); - - float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter; - float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter; - - float newDistance = vecX * vecX + vecY * vecY; - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - case Manhattan: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int hash = Hash(seed, xPrimed, yPrimed); - int idx = hash & (255 << 1); - - float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter; - float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter; - - float newDistance = FastAbs(vecX) + FastAbs(vecY); - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - case Hybrid: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int hash = Hash(seed, xPrimed, yPrimed); - int idx = hash & (255 << 1); - - float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter; - float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter; - - float newDistance = (FastAbs(vecX) + FastAbs(vecY)) + (vecX * vecX + vecY * vecY); - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - } - - if (mCellularDistanceFunction == CellularDistanceFunction.Euclidean && mCellularReturnType != CellularReturnType.CellValue) - { - distance0 = FastSqrt(distance0); - - if (mCellularReturnType != CellularReturnType.Distance) - { - distance1 = FastSqrt(distance1); - } - } - - switch (mCellularReturnType) - { - case CellValue: - return closestHash * (1 / 2147483648.0f); - case Distance: - return distance0 - 1; - case Distance2: - return distance1 - 1; - case Distance2Add: - return (distance1 + distance0) * 0.5f - 1; - case Distance2Sub: - return distance1 - distance0 - 1; - case Distance2Mul: - return distance1 * distance0 * 0.5f - 1; - case Distance2Div: - return distance0 / distance1 - 1; - default: - return 0; - } - } - - private float SingleCellular(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - int xr = FastRound(x); - int yr = FastRound(y); - int zr = FastRound(z); - - float distance0 = Float.MAX_VALUE; - float distance1 = Float.MAX_VALUE; - int closestHash = 0; - - float cellularJitter = 0.39614353f * mCellularJitterModifier; - - int xPrimed = (xr - 1) * PrimeX; - int yPrimedBase = (yr - 1) * PrimeY; - int zPrimedBase = (zr - 1) * PrimeZ; - - switch (mCellularDistanceFunction) - { - case Euclidean: - case EuclideanSq: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int zPrimed = zPrimedBase; - - for (int zi = zr - 1; zi <= zr + 1; zi++) - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - int idx = hash & (255 << 2); - - float vecX = (float)(xi - x) + RandVecs3D[idx] * cellularJitter; - float vecY = (float)(yi - y) + RandVecs3D[idx | 1] * cellularJitter; - float vecZ = (float)(zi - z) + RandVecs3D[idx | 2] * cellularJitter; - - float newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - zPrimed += PrimeZ; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - case Manhattan: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int zPrimed = zPrimedBase; - - for (int zi = zr - 1; zi <= zr + 1; zi++) - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - int idx = hash & (255 << 2); - - float vecX = (float)(xi - x) + RandVecs3D[idx] * cellularJitter; - float vecY = (float)(yi - y) + RandVecs3D[idx | 1] * cellularJitter; - float vecZ = (float)(zi - z) + RandVecs3D[idx | 2] * cellularJitter; - - float newDistance = FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ); - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - zPrimed += PrimeZ; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - case Hybrid: - for (int xi = xr - 1; xi <= xr + 1; xi++) - { - int yPrimed = yPrimedBase; - - for (int yi = yr - 1; yi <= yr + 1; yi++) - { - int zPrimed = zPrimedBase; - - for (int zi = zr - 1; zi <= zr + 1; zi++) - { - int hash = Hash(seed, xPrimed, yPrimed, zPrimed); - int idx = hash & (255 << 2); - - float vecX = (float)(xi - x) + RandVecs3D[idx] * cellularJitter; - float vecY = (float)(yi - y) + RandVecs3D[idx | 1] * cellularJitter; - float vecZ = (float)(zi - z) + RandVecs3D[idx | 2] * cellularJitter; - - float newDistance = (FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ); - - distance1 = FastMax(FastMin(distance1, newDistance), distance0); - if (newDistance < distance0) - { - distance0 = newDistance; - closestHash = hash; - } - zPrimed += PrimeZ; - } - yPrimed += PrimeY; - } - xPrimed += PrimeX; - } - break; - default: - break; - } - - if (mCellularDistanceFunction == CellularDistanceFunction.Euclidean && mCellularReturnType != CellularReturnType.CellValue) - { - distance0 = FastSqrt(distance0); - - if (mCellularReturnType != CellularReturnType.Distance) - { - distance1 = FastSqrt(distance1); - } - } - - switch (mCellularReturnType) - { - case CellValue: - return closestHash * (1 / 2147483648.0f); - case Distance: - return distance0 - 1; - case Distance2: - return distance1 - 1; - case Distance2Add: - return (distance1 + distance0) * 0.5f - 1; - case Distance2Sub: - return distance1 - distance0 - 1; - case Distance2Mul: - return distance1 * distance0 * 0.5f - 1; - case Distance2Div: - return distance0 / distance1 - 1; - default: - return 0; - } - } - - - // Perlin Noise - - private float SinglePerlin(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - int x0 = FastFloor(x); - int y0 = FastFloor(y); - - float xd0 = (float)(x - x0); - float yd0 = (float)(y - y0); - float xd1 = xd0 - 1; - float yd1 = yd0 - 1; - - float xs = InterpQuintic(xd0); - float ys = InterpQuintic(yd0); - - x0 *= PrimeX; - y0 *= PrimeY; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - - float xf0 = Lerp(GradCoord(seed, x0, y0, xd0, yd0), GradCoord(seed, x1, y0, xd1, yd0), xs); - float xf1 = Lerp(GradCoord(seed, x0, y1, xd0, yd1), GradCoord(seed, x1, y1, xd1, yd1), xs); - - return Lerp(xf0, xf1, ys) * 1.4247691104677813f; - } - - private float SinglePerlin(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - int x0 = FastFloor(x); - int y0 = FastFloor(y); - int z0 = FastFloor(z); - - float xd0 = (float)(x - x0); - float yd0 = (float)(y - y0); - float zd0 = (float)(z - z0); - float xd1 = xd0 - 1; - float yd1 = yd0 - 1; - float zd1 = zd0 - 1; - - float xs = InterpQuintic(xd0); - float ys = InterpQuintic(yd0); - float zs = InterpQuintic(zd0); - - x0 *= PrimeX; - y0 *= PrimeY; - z0 *= PrimeZ; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - int z1 = z0 + PrimeZ; - - float xf00 = Lerp(GradCoord(seed, x0, y0, z0, xd0, yd0, zd0), GradCoord(seed, x1, y0, z0, xd1, yd0, zd0), xs); - float xf10 = Lerp(GradCoord(seed, x0, y1, z0, xd0, yd1, zd0), GradCoord(seed, x1, y1, z0, xd1, yd1, zd0), xs); - float xf01 = Lerp(GradCoord(seed, x0, y0, z1, xd0, yd0, zd1), GradCoord(seed, x1, y0, z1, xd1, yd0, zd1), xs); - float xf11 = Lerp(GradCoord(seed, x0, y1, z1, xd0, yd1, zd1), GradCoord(seed, x1, y1, z1, xd1, yd1, zd1), xs); - - float yf0 = Lerp(xf00, xf10, ys); - float yf1 = Lerp(xf01, xf11, ys); - - return Lerp(yf0, yf1, zs) * 0.964921414852142333984375f; - } - - - // Value Cubic Noise - - private float SingleValueCubic(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - int x1 = FastFloor(x); - int y1 = FastFloor(y); - - float xs = (float)(x - x1); - float ys = (float)(y - y1); - - x1 *= PrimeX; - y1 *= PrimeY; - int x0 = x1 - PrimeX; - int y0 = y1 - PrimeY; - int x2 = x1 + PrimeX; - int y2 = y1 + PrimeY; - int x3 = x1 + (PrimeX << 1); - int y3 = y1 + (PrimeY << 1); - - return CubicLerp( - CubicLerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), ValCoord(seed, x2, y0), ValCoord(seed, x3, y0), - xs), - CubicLerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), ValCoord(seed, x2, y1), ValCoord(seed, x3, y1), - xs), - CubicLerp(ValCoord(seed, x0, y2), ValCoord(seed, x1, y2), ValCoord(seed, x2, y2), ValCoord(seed, x3, y2), - xs), - CubicLerp(ValCoord(seed, x0, y3), ValCoord(seed, x1, y3), ValCoord(seed, x2, y3), ValCoord(seed, x3, y3), - xs), - ys) * (1 / (1.5f * 1.5f)); - } - - private float SingleValueCubic(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - int x1 = FastFloor(x); - int y1 = FastFloor(y); - int z1 = FastFloor(z); - - float xs = (float)(x - x1); - float ys = (float)(y - y1); - float zs = (float)(z - z1); - - x1 *= PrimeX; - y1 *= PrimeY; - z1 *= PrimeZ; - - int x0 = x1 - PrimeX; - int y0 = y1 - PrimeY; - int z0 = z1 - PrimeZ; - int x2 = x1 + PrimeX; - int y2 = y1 + PrimeY; - int z2 = z1 + PrimeZ; - int x3 = x1 + (PrimeX << 1); - int y3 = y1 + (PrimeY << 1); - int z3 = z1 + (PrimeZ << 1); - - - return CubicLerp( - CubicLerp( - CubicLerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), ValCoord(seed, x2, y0, z0), ValCoord(seed, x3, y0, z0), xs), - CubicLerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), ValCoord(seed, x2, y1, z0), ValCoord(seed, x3, y1, z0), xs), - CubicLerp(ValCoord(seed, x0, y2, z0), ValCoord(seed, x1, y2, z0), ValCoord(seed, x2, y2, z0), ValCoord(seed, x3, y2, z0), xs), - CubicLerp(ValCoord(seed, x0, y3, z0), ValCoord(seed, x1, y3, z0), ValCoord(seed, x2, y3, z0), ValCoord(seed, x3, y3, z0), xs), - ys), - CubicLerp( - CubicLerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), ValCoord(seed, x2, y0, z1), ValCoord(seed, x3, y0, z1), xs), - CubicLerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), ValCoord(seed, x2, y1, z1), ValCoord(seed, x3, y1, z1), xs), - CubicLerp(ValCoord(seed, x0, y2, z1), ValCoord(seed, x1, y2, z1), ValCoord(seed, x2, y2, z1), ValCoord(seed, x3, y2, z1), xs), - CubicLerp(ValCoord(seed, x0, y3, z1), ValCoord(seed, x1, y3, z1), ValCoord(seed, x2, y3, z1), ValCoord(seed, x3, y3, z1), xs), - ys), - CubicLerp( - CubicLerp(ValCoord(seed, x0, y0, z2), ValCoord(seed, x1, y0, z2), ValCoord(seed, x2, y0, z2), ValCoord(seed, x3, y0, z2), xs), - CubicLerp(ValCoord(seed, x0, y1, z2), ValCoord(seed, x1, y1, z2), ValCoord(seed, x2, y1, z2), ValCoord(seed, x3, y1, z2), xs), - CubicLerp(ValCoord(seed, x0, y2, z2), ValCoord(seed, x1, y2, z2), ValCoord(seed, x2, y2, z2), ValCoord(seed, x3, y2, z2), xs), - CubicLerp(ValCoord(seed, x0, y3, z2), ValCoord(seed, x1, y3, z2), ValCoord(seed, x2, y3, z2), ValCoord(seed, x3, y3, z2), xs), - ys), - CubicLerp( - CubicLerp(ValCoord(seed, x0, y0, z3), ValCoord(seed, x1, y0, z3), ValCoord(seed, x2, y0, z3), ValCoord(seed, x3, y0, z3), xs), - CubicLerp(ValCoord(seed, x0, y1, z3), ValCoord(seed, x1, y1, z3), ValCoord(seed, x2, y1, z3), ValCoord(seed, x3, y1, z3), xs), - CubicLerp(ValCoord(seed, x0, y2, z3), ValCoord(seed, x1, y2, z3), ValCoord(seed, x2, y2, z3), ValCoord(seed, x3, y2, z3), xs), - CubicLerp(ValCoord(seed, x0, y3, z3), ValCoord(seed, x1, y3, z3), ValCoord(seed, x2, y3, z3), ValCoord(seed, x3, y3, z3), xs), - ys), - zs) * (1 / (1.5f * 1.5f * 1.5f)); - } - - - // Value Noise - - private float SingleValue(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - int x0 = FastFloor(x); - int y0 = FastFloor(y); - - float xs = InterpHermite((float)(x - x0)); - float ys = InterpHermite((float)(y - y0)); - - x0 *= PrimeX; - y0 *= PrimeY; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - - float xf0 = Lerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), xs); - float xf1 = Lerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), xs); - - return Lerp(xf0, xf1, ys); - } - - private float SingleValue(int seed, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - int x0 = FastFloor(x); - int y0 = FastFloor(y); - int z0 = FastFloor(z); - - float xs = InterpHermite((float)(x - x0)); - float ys = InterpHermite((float)(y - y0)); - float zs = InterpHermite((float)(z - z0)); - - x0 *= PrimeX; - y0 *= PrimeY; - z0 *= PrimeZ; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - int z1 = z0 + PrimeZ; - - float xf00 = Lerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), xs); - float xf10 = Lerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), xs); - float xf01 = Lerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), xs); - float xf11 = Lerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), xs); - - float yf0 = Lerp(xf00, xf10, ys); - float yf1 = Lerp(xf01, xf11, ys); - - return Lerp(yf0, yf1, zs); - } - - - // Domain Warp - - private void DoSingleDomainWarp(int seed, float amp, float freq, /*FNLfloat*/ float x, /*FNLfloat*/ float y, Vector2 coord) - { - switch (mDomainWarpType) - { - case OpenSimplex2: - SingleDomainWarpSimplexGradient(seed, amp * 38.283687591552734375f, freq, x, y, coord, false); - break; - case OpenSimplex2Reduced: - SingleDomainWarpSimplexGradient(seed, amp * 16.0f, freq, x, y, coord, true); - break; - case BasicGrid: - SingleDomainWarpBasicGrid(seed, amp, freq, x, y, coord); - break; - } - } - - private void DoSingleDomainWarp(int seed, float amp, float freq, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z, Vector3 coord) - { - switch (mDomainWarpType) - { - case OpenSimplex2: - SingleDomainWarpOpenSimplex2Gradient(seed, amp * 32.69428253173828125f, freq, x, y, z, coord, false); - break; - case OpenSimplex2Reduced: - SingleDomainWarpOpenSimplex2Gradient(seed, amp * 7.71604938271605f, freq, x, y, z, coord, true); - break; - case BasicGrid: - SingleDomainWarpBasicGrid(seed, amp, freq, x, y, z, coord); - break; - } - } - - - // Domain Warp Single Wrapper - - private void DomainWarpSingle(Vector2 coord) - { - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - /*FNLfloat*/ float xs = coord.x; - /*FNLfloat*/ float ys = coord.y; - switch (mDomainWarpType) - { - case OpenSimplex2: - case OpenSimplex2Reduced: - { - final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float)1.7320508075688772935274463415059; - final /*FNLfloat*/ float F2 = 0.5f * (SQRT3 - 1); - /*FNLfloat*/ float t = (xs + ys) * F2; - xs += t; ys += t; - } - break; - default: - break; - } - - DoSingleDomainWarp(seed, amp, freq, xs, ys, coord); - } - - private void DomainWarpSingle(Vector3 coord) - { - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - /*FNLfloat*/ float xs = coord.x; - /*FNLfloat*/ float ys = coord.y; - /*FNLfloat*/ float zs = coord.z; - switch (mWarpTransformType3D) - { - case ImproveXYPlanes: - { - /*FNLfloat*/ float xy = xs + ys; - /*FNLfloat*/ float s2 = xy * -(/*FNLfloat*/ float)0.211324865405187; - zs *= (/*FNLfloat*/ float)0.577350269189626; - xs += s2 - zs; - ys = ys + s2 - zs; - zs += xy * (/*FNLfloat*/ float)0.577350269189626; - } - break; - case ImproveXZPlanes: - { - /*FNLfloat*/ float xz = xs + zs; - /*FNLfloat*/ float s2 = xz * -(/*FNLfloat*/ float)0.211324865405187; - ys *= (/*FNLfloat*/ float)0.577350269189626; - xs += s2 - ys; zs += s2 - ys; - ys += xz * (/*FNLfloat*/ float)0.577350269189626; - } - break; - case DefaultOpenSimplex2: - { - final /*FNLfloat*/ float R3 = (/*FNLfloat*/ float)(2.0 / 3.0); - /*FNLfloat*/ float r = (xs + ys + zs) * R3; // Rotation, not skew - xs = r - xs; - ys = r - ys; - zs = r - zs; - } - break; - default: - break; - } - - DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); - } - - - // Domain Warp Fractal Progressive - - private void DomainWarpFractalProgressive(Vector2 coord) - { - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - for (int i = 0; i < mOctaves; i++) - { - /*FNLfloat*/ float xs = coord.x; - /*FNLfloat*/ float ys = coord.y; - switch (mDomainWarpType) - { - case OpenSimplex2: - case OpenSimplex2Reduced: - { - final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float)1.7320508075688772935274463415059; - final /*FNLfloat*/ float F2 = 0.5f * (SQRT3 - 1); - /*FNLfloat*/ float t = (xs + ys) * F2; - xs += t; ys += t; - } - break; - default: - break; - } - - DoSingleDomainWarp(seed, amp, freq, xs, ys, coord); - - seed++; - amp *= mGain; - freq *= mLacunarity; - } - } - - private void DomainWarpFractalProgressive(Vector3 coord) - { - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - for (int i = 0; i < mOctaves; i++) - { - /*FNLfloat*/ float xs = coord.x; - /*FNLfloat*/ float ys = coord.y; - /*FNLfloat*/ float zs = coord.z; - switch (mWarpTransformType3D) - { - case ImproveXYPlanes: - { - /*FNLfloat*/ float xy = xs + ys; - /*FNLfloat*/ float s2 = xy * -(/*FNLfloat*/ float)0.211324865405187; - zs *= (/*FNLfloat*/ float)0.577350269189626; - xs += s2 - zs; - ys = ys + s2 - zs; - zs += xy * (/*FNLfloat*/ float)0.577350269189626; - } - break; - case ImproveXZPlanes: - { - /*FNLfloat*/ float xz = xs + zs; - /*FNLfloat*/ float s2 = xz * -(/*FNLfloat*/ float)0.211324865405187; - ys *= (/*FNLfloat*/ float)0.577350269189626; - xs += s2 - ys; zs += s2 - ys; - ys += xz * (/*FNLfloat*/ float)0.577350269189626; - } - break; - case DefaultOpenSimplex2: - { - final /*FNLfloat*/ float R3 = (/*FNLfloat*/ float)(2.0 / 3.0); - /*FNLfloat*/ float r = (xs + ys + zs) * R3; // Rotation, not skew - xs = r - xs; - ys = r - ys; - zs = r - zs; - } - break; - default: - break; - } - - DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); - - seed++; - amp *= mGain; - freq *= mLacunarity; - } - } - - - // Domain Warp Fractal Independant - private void DomainWarpFractalIndependent(Vector2 coord) - { - /*FNLfloat*/ float xs = coord.x; - /*FNLfloat*/ float ys = coord.y; - switch (mDomainWarpType) - { - case OpenSimplex2: - case OpenSimplex2Reduced: - { - final /*FNLfloat*/ float SQRT3 = (/*FNLfloat*/ float)1.7320508075688772935274463415059; - final /*FNLfloat*/ float F2 = 0.5f * (SQRT3 - 1); - /*FNLfloat*/ float t = (xs + ys) * F2; - xs += t; ys += t; - } - break; - default: - break; - } - - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - for (int i = 0; i < mOctaves; i++) - { - DoSingleDomainWarp(seed, amp, freq, xs, ys, coord); - - seed++; - amp *= mGain; - freq *= mLacunarity; - } - } - - private void DomainWarpFractalIndependent(Vector3 coord) - { - /*FNLfloat*/ float xs = coord.x; - /*FNLfloat*/ float ys = coord.y; - /*FNLfloat*/ float zs = coord.z; - switch (mWarpTransformType3D) - { - case ImproveXYPlanes: - { - /*FNLfloat*/ float xy = xs + ys; - /*FNLfloat*/ float s2 = xy * -(/*FNLfloat*/ float)0.211324865405187; - zs *= (/*FNLfloat*/ float)0.577350269189626; - xs += s2 - zs; - ys = ys + s2 - zs; - zs += xy * (/*FNLfloat*/ float)0.577350269189626; - } - break; - case ImproveXZPlanes: - { - /*FNLfloat*/ float xz = xs + zs; - /*FNLfloat*/ float s2 = xz * -(/*FNLfloat*/ float)0.211324865405187; - ys *= (/*FNLfloat*/ float)0.577350269189626; - xs += s2 - ys; zs += s2 - ys; - ys += xz * (/*FNLfloat*/ float)0.577350269189626; - } - break; - case DefaultOpenSimplex2: - { - final /*FNLfloat*/ float R3 = (/*FNLfloat*/ float)(2.0 / 3.0); - /*FNLfloat*/ float r = (xs + ys + zs) * R3; // Rotation, not skew - xs = r - xs; - ys = r - ys; - zs = r - zs; - } - break; - default: - break; - } - - int seed = mSeed; - float amp = mDomainWarpAmp * mFractalBounding; - float freq = mFrequency; - - for (int i = 0; i < mOctaves; i++) - { - DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, coord); - - seed++; - amp *= mGain; - freq *= mLacunarity; - } - } - - - // Domain Warp Basic Grid - - private void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, /*FNLfloat*/ float x, /*FNLfloat*/ float y, Vector2 coord) - { - /*FNLfloat*/ float xf = x * frequency; - /*FNLfloat*/ float yf = y * frequency; - - int x0 = FastFloor(xf); - int y0 = FastFloor(yf); - - float xs = InterpHermite((float)(xf - x0)); - float ys = InterpHermite((float)(yf - y0)); - - x0 *= PrimeX; - y0 *= PrimeY; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - - int hash0 = Hash(seed, x0, y0) & (255 << 1); - int hash1 = Hash(seed, x1, y0) & (255 << 1); - - float lx0x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs); - float ly0x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs); - - hash0 = Hash(seed, x0, y1) & (255 << 1); - hash1 = Hash(seed, x1, y1) & (255 << 1); - - float lx1x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs); - float ly1x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs); - - coord.x += Lerp(lx0x, lx1x, ys) * warpAmp; - coord.y += Lerp(ly0x, ly1x, ys) * warpAmp; - } - - private void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z, Vector3 coord) - { - /*FNLfloat*/ float xf = x * frequency; - /*FNLfloat*/ float yf = y * frequency; - /*FNLfloat*/ float zf = z * frequency; - - int x0 = FastFloor(xf); - int y0 = FastFloor(yf); - int z0 = FastFloor(zf); - - float xs = InterpHermite((float)(xf - x0)); - float ys = InterpHermite((float)(yf - y0)); - float zs = InterpHermite((float)(zf - z0)); - - x0 *= PrimeX; - y0 *= PrimeY; - z0 *= PrimeZ; - int x1 = x0 + PrimeX; - int y1 = y0 + PrimeY; - int z1 = z0 + PrimeZ; - - int hash0 = Hash(seed, x0, y0, z0) & (255 << 2); - int hash1 = Hash(seed, x1, y0, z0) & (255 << 2); - - float lx0x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); - float ly0x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); - float lz0x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); - - hash0 = Hash(seed, x0, y1, z0) & (255 << 2); - hash1 = Hash(seed, x1, y1, z0) & (255 << 2); - - float lx1x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); - float ly1x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); - float lz1x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); - - float lx0y = Lerp(lx0x, lx1x, ys); - float ly0y = Lerp(ly0x, ly1x, ys); - float lz0y = Lerp(lz0x, lz1x, ys); - - hash0 = Hash(seed, x0, y0, z1) & (255 << 2); - hash1 = Hash(seed, x1, y0, z1) & (255 << 2); - - lx0x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); - ly0x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); - lz0x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); - - hash0 = Hash(seed, x0, y1, z1) & (255 << 2); - hash1 = Hash(seed, x1, y1, z1) & (255 << 2); - - lx1x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs); - ly1x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs); - lz1x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs); - - coord.x += Lerp(lx0y, Lerp(lx0x, lx1x, ys), zs) * warpAmp; - coord.y += Lerp(ly0y, Lerp(ly0x, ly1x, ys), zs) * warpAmp; - coord.z += Lerp(lz0y, Lerp(lz0x, lz1x, ys), zs) * warpAmp; - } - - - // Domain Warp Simplex/OpenSimplex2 - private void SingleDomainWarpSimplexGradient(int seed, float warpAmp, float frequency, /*FNLfloat*/ float x, /*FNLfloat*/ float y, Vector2 coord, boolean outGradOnly) - { - final float SQRT3 = 1.7320508075688772935274463415059f; - final float G2 = (3 - SQRT3) / 6; - - x *= frequency; - y *= frequency; - - /* - * --- Skew moved to switch statements before fractal evaluation --- - * final FNLfloat F2 = 0.5f * (SQRT3 - 1); - * FNLfloat s = (x + y) * F2; - * x += s; y += s; - */ - - int i = FastFloor(x); - int j = FastFloor(y); - float xi = (float)(x - i); - float yi = (float)(y - j); - - float t = (xi + yi) * G2; - float x0 = (float)(xi - t); - float y0 = (float)(yi - t); - - i *= PrimeX; - j *= PrimeY; - - float vx, vy; - vx = vy = 0; - - float a = 0.5f - x0 * x0 - y0 * y0; - if (a > 0) - { - float aaaa = (a * a) * (a * a); - float xo, yo; - if (outGradOnly) - { - int hash = Hash(seed, i, j) & (255 << 1); - xo = RandVecs2D[hash]; - yo = RandVecs2D[hash | 1]; - } - else - { - int hash = Hash(seed, i, j); - int index1 = hash & (127 << 1); - int index2 = (hash >> 7) & (255 << 1); - float xg = Gradients2D[index1]; - float yg = Gradients2D[index1 | 1]; - float value = x0 * xg + y0 * yg; - float xgo = RandVecs2D[index2]; - float ygo = RandVecs2D[index2 | 1]; - xo = value * xgo; - yo = value * ygo; - } - vx += aaaa * xo; - vy += aaaa * yo; - } - - float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a); - if (c > 0) - { - float x2 = x0 + (2 * (float)G2 - 1); - float y2 = y0 + (2 * (float)G2 - 1); - float cccc = (c * c) * (c * c); - float xo, yo; - if (outGradOnly) - { - int hash = Hash(seed, i + PrimeX, j + PrimeY) & (255 << 1); - xo = RandVecs2D[hash]; - yo = RandVecs2D[hash | 1]; - } - else - { - int hash = Hash(seed, i + PrimeX, j + PrimeY); - int index1 = hash & (127 << 1); - int index2 = (hash >> 7) & (255 << 1); - float xg = Gradients2D[index1]; - float yg = Gradients2D[index1 | 1]; - float value = x2 * xg + y2 * yg; - float xgo = RandVecs2D[index2]; - float ygo = RandVecs2D[index2 | 1]; - xo = value * xgo; - yo = value * ygo; - } - vx += cccc * xo; - vy += cccc * yo; - } - - if (y0 > x0) - { - float x1 = x0 + (float)G2; - float y1 = y0 + ((float)G2 - 1); - float b = 0.5f - x1 * x1 - y1 * y1; - if (b > 0) - { - float bbbb = (b * b) * (b * b); - float xo, yo; - if (outGradOnly) - { - int hash = Hash(seed, i, j + PrimeY) & (255 << 1); - xo = RandVecs2D[hash]; - yo = RandVecs2D[hash | 1]; - } - else - { - int hash = Hash(seed, i, j + PrimeY); - int index1 = hash & (127 << 1); - int index2 = (hash >> 7) & (255 << 1); - float xg = Gradients2D[index1]; - float yg = Gradients2D[index1 | 1]; - float value = x1 * xg + y1 * yg; - float xgo = RandVecs2D[index2]; - float ygo = RandVecs2D[index2 | 1]; - xo = value * xgo; - yo = value * ygo; - } - vx += bbbb * xo; - vy += bbbb * yo; - } - } - else - { - float x1 = x0 + ((float)G2 - 1); - float y1 = y0 + (float)G2; - float b = 0.5f - x1 * x1 - y1 * y1; - if (b > 0) - { - float bbbb = (b * b) * (b * b); - float xo, yo; - if (outGradOnly) - { - int hash = Hash(seed, i + PrimeX, j) & (255 << 1); - xo = RandVecs2D[hash]; - yo = RandVecs2D[hash | 1]; - } - else - { - int hash = Hash(seed, i + PrimeX, j); - int index1 = hash & (127 << 1); - int index2 = (hash >> 7) & (255 << 1); - float xg = Gradients2D[index1]; - float yg = Gradients2D[index1 | 1]; - float value = x1 * xg + y1 * yg; - float xgo = RandVecs2D[index2]; - float ygo = RandVecs2D[index2 | 1]; - xo = value * xgo; - yo = value * ygo; - } - vx += bbbb * xo; - vy += bbbb * yo; - } - } - - coord.x += vx * warpAmp; - coord.y += vy * warpAmp; - } - - private void SingleDomainWarpOpenSimplex2Gradient(int seed, float warpAmp, float frequency, /*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z, Vector3 coord, boolean outGradOnly) - { - x *= frequency; - y *= frequency; - z *= frequency; - - /* - * --- Rotation moved to switch statements before fractal evaluation --- - * final FNLfloat R3 = (FNLfloat)(2.0 / 3.0); - * FNLfloat r = (x + y + z) * R3; // Rotation, not skew - * x = r - x; y = r - y; z = r - z; - */ - - int i = FastRound(x); - int j = FastRound(y); - int k = FastRound(z); - float x0 = (float)x - i; - float y0 = (float)y - j; - float z0 = (float)z - k; - - int xNSign = (int)(-x0 - 1.0f) | 1; - int yNSign = (int)(-y0 - 1.0f) | 1; - int zNSign = (int)(-z0 - 1.0f) | 1; - - float ax0 = xNSign * -x0; - float ay0 = yNSign * -y0; - float az0 = zNSign * -z0; - - i *= PrimeX; - j *= PrimeY; - k *= PrimeZ; - - float vx, vy, vz; - vx = vy = vz = 0; - - float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0); - for (int l = 0; ; l++) - { - if (a > 0) - { - float aaaa = (a * a) * (a * a); - float xo, yo, zo; - if (outGradOnly) - { - int hash = Hash(seed, i, j, k) & (255 << 2); - xo = RandVecs3D[hash]; - yo = RandVecs3D[hash | 1]; - zo = RandVecs3D[hash | 2]; - } - else - { - int hash = Hash(seed, i, j, k); - int index1 = hash & (63 << 2); - int index2 = (hash >> 6) & (255 << 2); - float xg = Gradients3D[index1]; - float yg = Gradients3D[index1 | 1]; - float zg = Gradients3D[index1 | 2]; - float value = x0 * xg + y0 * yg + z0 * zg; - float xgo = RandVecs3D[index2]; - float ygo = RandVecs3D[index2 | 1]; - float zgo = RandVecs3D[index2 | 2]; - xo = value * xgo; - yo = value * ygo; - zo = value * zgo; - } - vx += aaaa * xo; - vy += aaaa * yo; - vz += aaaa * zo; - } - - float b = a; - int i1 = i; - int j1 = j; - int k1 = k; - float x1 = x0; - float y1 = y0; - float z1 = z0; - - if (ax0 >= ay0 && ax0 >= az0) - { - x1 += xNSign; - b = b + ax0 + ax0; - i1 -= xNSign * PrimeX; - } - else if (ay0 > ax0 && ay0 >= az0) - { - y1 += yNSign; - b = b + ay0 + ay0; - j1 -= yNSign * PrimeY; - } - else - { - z1 += zNSign; - b = b + az0 + az0; - k1 -= zNSign * PrimeZ; - } - - if (b > 1) - { - b -= 1; - float bbbb = (b * b) * (b * b); - float xo, yo, zo; - if (outGradOnly) - { - int hash = Hash(seed, i1, j1, k1) & (255 << 2); - xo = RandVecs3D[hash]; - yo = RandVecs3D[hash | 1]; - zo = RandVecs3D[hash | 2]; - } - else - { - int hash = Hash(seed, i1, j1, k1); - int index1 = hash & (63 << 2); - int index2 = (hash >> 6) & (255 << 2); - float xg = Gradients3D[index1]; - float yg = Gradients3D[index1 | 1]; - float zg = Gradients3D[index1 | 2]; - float value = x1 * xg + y1 * yg + z1 * zg; - float xgo = RandVecs3D[index2]; - float ygo = RandVecs3D[index2 | 1]; - float zgo = RandVecs3D[index2 | 2]; - xo = value * xgo; - yo = value * ygo; - zo = value * zgo; - } - vx += bbbb * xo; - vy += bbbb * yo; - vz += bbbb * zo; - } - - if (l == 1) break; - - ax0 = 0.5f - ax0; - ay0 = 0.5f - ay0; - az0 = 0.5f - az0; - - x0 = xNSign * ax0; - y0 = yNSign * ay0; - z0 = zNSign * az0; - - a += (0.75f - ax0) - (ay0 + az0); - - i += (xNSign >> 1) & PrimeX; - j += (yNSign >> 1) & PrimeY; - k += (zNSign >> 1) & PrimeZ; - - xNSign = -xNSign; - yNSign = -yNSign; - zNSign = -zNSign; - - seed += 1293373; - } - - coord.x += vx * warpAmp; - coord.y += vy * warpAmp; - coord.z += vz * warpAmp; - } - - public static class Vector2 - { - public /*FNLfloat*/ float x; - public /*FNLfloat*/ float y; - public Vector2(/*FNLfloat*/ float x, /*FNLfloat*/ float y) - { - this.x = x; - this.y = y; - } - } - - public static class Vector3 - { - public /*FNLfloat*/ float x; - public /*FNLfloat*/ float y; - public /*FNLfloat*/ float z; - public Vector3(/*FNLfloat*/ float x, /*FNLfloat*/ float y, /*FNLfloat*/ float z) - { - this.x = x; - this.y = y; - this.z = z; - } - } -} \ No newline at end of file diff --git a/terrain-world-plugin/src/main/resources/META-INF/LICENSE-FastNoiseLite.txt b/terrain-world-plugin/src/main/resources/META-INF/LICENSE-FastNoiseLite.txt deleted file mode 100644 index 0e55394..0000000 --- a/terrain-world-plugin/src/main/resources/META-INF/LICENSE-FastNoiseLite.txt +++ /dev/null @@ -1,26 +0,0 @@ -MIT License - -Copyright(c) 2023 Jordan Peck (jordan.me2@gmail.com) -Copyright(c) 2023 Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files(the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -Upstream: https://github.com/Auburn/FastNoiseLite -Commit: 785f37a9ad76e283586a379675085f2063ae03f7 -Local change: Java package declaration only. diff --git a/terrain-world-plugin/src/main/resources/config.yml b/terrain-world-plugin/src/main/resources/config.yml deleted file mode 100644 index 0307d42..0000000 --- a/terrain-world-plugin/src/main/resources/config.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Explicit opt-in; never creates or adopts a world by default. -enabled: false -world: shacraft_lobby -recipe: terrain.json -# recipe-v1, or shacraft-natural-v1 with an empty-feature envelope recipe. -terrain-profile: recipe-v1 -# Optional absolute surface Y for lakes/rivers. Omit for dry terrain. -# water-level: 48 -# Optional exploration border, independent of the editor's authorized bounds. -# border-size: 2048 -# Optional fixed player feet position for /lobby, world spawn and deaths in this lobby. -# Omit this section to keep the recipe's original spawn at x=0, z=0. -# Set this after building a raised plaza; invisible roofs are not spawn surfaces. -# spawn: -# x: 0.5 -# y: 96 -# z: 43.5 -# yaw: 180 -# pitch: 0 -# Enable only after the two-floor clock station at the documented coordinates is built. -# station: -# enabled: true diff --git a/terrain-world-plugin/src/main/resources/plugin.yml b/terrain-world-plugin/src/main/resources/plugin.yml deleted file mode 100644 index bb43d1d..0000000 --- a/terrain-world-plugin/src/main/resources/plugin.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: ShacraftTerrain -version: '0.1.0' -main: io.github.minecraftbuilder.terrainworld.TerrainWorldPlugin -api-version: '26.2' -loadbefore: [MinecraftBuilderMCP] -commands: - lobby: - description: Visit the configured terrain world, inspect generation or export a surface map - permission: minecraftbuilder.lobby - station: - description: Visit the completed clock station vestibule or SMASH floor - permission: minecraftbuilder.station -permissions: - minecraftbuilder.lobby: - default: op - minecraftbuilder.station: - default: true diff --git a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/LobbyRespawnTest.java b/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/LobbyRespawnTest.java deleted file mode 100644 index 6f4f267..0000000 --- a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/LobbyRespawnTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerRespawnEvent; -import org.junit.jupiter.api.Test; -import java.lang.reflect.Proxy; -import static org.junit.jupiter.api.Assertions.*; - -class LobbyRespawnTest { - @Test void configuredLobbyDeathUsesPlazaFeetEvenWhenVanillaSelectedBarrierRoof() { - World lobby = world(); - Location spawn = new Location(lobby, .5, 96, 43.5, 180, 0); - LobbyRespawn listener = new LobbyRespawn(lobby, spawn); - spawn.setY(300); // The listener retains its own configuration snapshot. - var event = event(lobby, new Location(lobby, .5, 131, 43.5), PlayerRespawnEvent.RespawnReason.DEATH); - listener.onRespawn(event); - assertDestination(event.getRespawnLocation(), lobby, .5, 96, 43.5, 180, 0); - event.getRespawnLocation().setY(200); - var next = event(lobby, new Location(world(), 8, 70, 9), PlayerRespawnEvent.RespawnReason.DEATH); - listener.onRespawn(next); - assertDestination(next.getRespawnLocation(), lobby, .5, 96, 43.5, 180, 0); - } - - @Test void otherWorldDeathsAreUnchangedEvenWhenTheirDestinationIsLobby() { - World lobby = world(), other = world(); - var listener = new LobbyRespawn(lobby, new Location(lobby, .5, 96, 43.5, 180, 0)); - var event = event(other, new Location(lobby, 11, 115, 23, 90, 10), PlayerRespawnEvent.RespawnReason.DEATH); - listener.onRespawn(event); - assertDestination(event.getRespawnLocation(), lobby, 11, 115, 23, 90, 10); - } - - @Test void absentConfigurationAndNonDeathRespawnsKeepOriginalBehavior() { - World lobby = world(); - var disabled = new LobbyRespawn(lobby, null); - var death = event(lobby, new Location(lobby, 11, 115, 23), PlayerRespawnEvent.RespawnReason.DEATH); - disabled.onRespawn(death); - assertDestination(death.getRespawnLocation(), lobby, 11, 115, 23, 0, 0); - var enabled = new LobbyRespawn(lobby, new Location(lobby, .5, 96, 43.5, 180, 0)); - for (var reason : PlayerRespawnEvent.RespawnReason.values()) { - if (reason == PlayerRespawnEvent.RespawnReason.DEATH) continue; - var event = event(lobby, new Location(lobby, 11, 115, 23), reason); - enabled.onRespawn(event); - assertDestination(event.getRespawnLocation(), lobby, 11, 115, 23, 0, 0); - } - } - - private static PlayerRespawnEvent event(World origin, Location destination, PlayerRespawnEvent.RespawnReason reason) { - Player player = (Player) Proxy.newProxyInstance(Player.class.getClassLoader(), new Class[]{Player.class}, - (proxy, method, args) -> { - if (method.getName().equals("getWorld")) return origin; - throw new AssertionError("Unexpected player call: " + method.getName()); - }); - return new PlayerRespawnEvent(player, destination, false, false, false, reason); - } - - private static World world() { - return (World) Proxy.newProxyInstance(World.class.getClassLoader(), new Class[]{World.class}, - (proxy, method, args) -> { throw new AssertionError("Must not query terrain or world state: " + method.getName()); }); - } - - private static void assertDestination(Location location, World world, double x, double y, double z, float yaw, float pitch) { - assertSame(world, location.getWorld()); - assertEquals(x, location.getX()); assertEquals(y, location.getY()); assertEquals(z, location.getZ()); - assertEquals(yaw, location.getYaw()); assertEquals(pitch, location.getPitch()); - } -} diff --git a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/LobbySpawnTest.java b/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/LobbySpawnTest.java deleted file mode 100644 index e228a76..0000000 --- a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/LobbySpawnTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import org.bukkit.configuration.MemoryConfiguration; -import org.junit.jupiter.api.Test; -import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; - -class LobbySpawnTest { - @Test void absentSectionKeepsOriginalRecipeFallback() { - assertEquals(new LobbySpawn(.5, 89, .5, 180, 0), - LobbySpawn.fromConfig(new MemoryConfiguration(), () -> 89)); - } - - @Test void explicitPlazaCoordinatesDoNotConsultGeneratedTerrainOrRoof() { - MemoryConfiguration config = configured(); - config.set("spawn.yaw", 137.5); - config.set("spawn.pitch", -12.5); - assertEquals(new LobbySpawn(.5, 96, 43.5, 137.5f, -12.5f), - LobbySpawn.fromConfig(config, () -> { throw new AssertionError("Fallback must not be read"); })); - config.set("spawn.yaw", null); - config.set("spawn.pitch", null); - assertEquals(new LobbySpawn(.5, 96, 43.5, 180, 0), LobbySpawn.fromConfig(config, () -> 200)); - } - - @Test void incompleteNonNumericAndNonFiniteSettingsFailInsteadOfFallingBack() { - MemoryConfiguration config = configured(); - config.set("spawn.z", null); - assertThrows(IllegalArgumentException.class, () -> LobbySpawn.fromConfig(config, () -> 89)); - for (Object value : new Object[]{"96", Double.NaN, Double.POSITIVE_INFINITY}) { - MemoryConfiguration invalid = configured(); invalid.set("spawn.y", value); - assertThrows(IllegalArgumentException.class, () -> LobbySpawn.fromConfig(invalid, () -> 89)); - } - config.set("spawn", "invalid"); - assertThrows(IllegalArgumentException.class, () -> LobbySpawn.fromConfig(config, () -> 89)); - } - - @Test void worldAndViewLimitsAreChecked() { - assertThrows(IllegalArgumentException.class, () -> new LobbySpawn(30_000_000, 96, 0, 180, 0)); - assertThrows(IllegalArgumentException.class, () -> new LobbySpawn(0, 96, 0, Float.POSITIVE_INFINITY, 0)); - assertThrows(IllegalArgumentException.class, () -> new LobbySpawn(0, 96, 0, 180, 91)); - assertThrows(IllegalArgumentException.class, () -> new LobbySpawn(0, -65, 0, 180, 0).requireHeight(-64, 320)); - assertThrows(IllegalArgumentException.class, () -> new LobbySpawn(0, 320, 0, 180, 0).requireHeight(-64, 320)); - assertDoesNotThrow(() -> new LobbySpawn(0, 96, 0, 180, 0).requireHeight(-64, 320)); - } - - private static MemoryConfiguration configured() { - MemoryConfiguration config = new MemoryConfiguration(); - config.createSection("spawn", Map.of("x", .5, "y", 96, "z", 43.5, "yaw", 180, "pitch", 0)); - return config; - } -} diff --git a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/RecipeGeneratorTest.java b/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/RecipeGeneratorTest.java deleted file mode 100644 index e0ee26f..0000000 --- a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/RecipeGeneratorTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import com.google.gson.*; -import org.bukkit.Material; -import org.bukkit.generator.ChunkGenerator.ChunkData; -import org.junit.jupiter.api.Test; -import java.lang.reflect.Proxy; -import java.nio.file.*; -import java.util.*; -import static org.junit.jupiter.api.Assertions.*; - -class RecipeGeneratorTest { - private JsonObject naturalSource() throws Exception { - return JsonParser.parseString(Files.readString(Path.of("../examples/terrain/shacraft-natural-world.json"))).getAsJsonObject(); - } - @Test void productionFieldExactlyMatchesTheApprovedStudy() throws Exception { - var terrain = new NaturalTerrain(28092005); - var digest = java.security.MessageDigest.getInstance("SHA-256"); - var buffer = java.nio.ByteBuffer.allocate(4); - for(int z=-384;z<384;z++)for(int x=-384;x<384;x++) { - float h=terrain.height(x,z); - assertTrue(h>=16&&h<=239); - buffer.clear();buffer.putFloat(h);digest.update(buffer.array()); - } - assertEquals("3ac6a3d8d884adf8ef074ec0dd4c0e066b18832f45a50e771b6d9433e4c79096",HexFormat.of().formatHex(digest.digest())); - } - @Test void naturalCliffsHaveRockInsteadOfSoilStripes() throws Exception { - var generator=new RecipeGenerator(naturalSource(),48,"shacraft-natural-v1"); - var terrain=new NaturalTerrain(28092005); - var cells=chunk(generator,-18,-19); - int cliffs=0; - for(int x=0;x<16;x++)for(int z=0;z<16;z++) { - int wx=-288+x,wz=-304+z,top=generator.height(wx,wz); - if(terrain.slope(wx,wz)>1.5) { - cliffs++; - assertTrue(Set.of(Material.STONE,Material.ANDESITE).contains(cells[x][top+64][z])); - for(int y=top-3;y20); - assertEquals(Material.GRASS_BLOCK,generator.surfaceMaterial(0,0)); - } - @Test void naturalLakeHasContinuousWaterAndNoGrassUnderwater() throws Exception { - var generator=new RecipeGenerator(naturalSource(),48,"shacraft-natural-v1"); - var cells=chunk(generator,-14,-1); - for(int x=0;x<16;x++)for(int z=0;z<16;z++) { - int top=generator.height(-224+x,-16+z); - assertTrue(top<48); - assertTrue(Set.of(Material.STONE,Material.ANDESITE).contains(cells[x][top+64][z])); - for(int y=top+1;y<=48;y++)assertEquals(Material.WATER,cells[x][y+64][z]); - assertNull(cells[x][49+64][z]); - } - } - @Test void profileAndWaterArePartOfImmutableGenerationIdentity() throws Exception { - var source=naturalSource(); - var dry=new RecipeGenerator(source,null,"shacraft-natural-v1"); - var wet=new RecipeGenerator(source,48,"shacraft-natural-v1"); - assertNotEquals(dry.identity(),wet.identity()); - assertNotEquals(wet.identity(),new RecipeGenerator(source,48).identity()); - assertThrows(IllegalArgumentException.class,()->new RecipeGenerator(source,48,"unknown")); - assertThrows(IllegalArgumentException.class,()->new RecipeGenerator(source(),48,"shacraft-natural-v1")); - } - private JsonObject source() throws Exception { - return JsonParser.parseString(Files.readString(Path.of("../examples/terrain/shacraft-lobby-world.json"))).getAsJsonObject(); - } - private Material[][][] chunk(RecipeGenerator generator, int cx, int cz) { - Material[][][] cells = new Material[16][384][16]; - ChunkData data = (ChunkData) Proxy.newProxyInstance(ChunkData.class.getClassLoader(), new Class[]{ChunkData.class}, (p, m, args) -> { - switch (m.getName()) { - case "getMinHeight": return -64; - case "getMaxHeight": return 320; - case "setBlock": cells[(int)args[0]][(int)args[1]+64][(int)args[2]] = (Material)args[3]; return null; - case "setRegion": - for(int x=(int)args[0];x<(int)args[3];x++) for(int y=(int)args[1];y<(int)args[4];y++) for(int z=(int)args[2];z<(int)args[5];z++) - cells[x][y+64][z]=(Material)args[6]; - return null; - default: throw new UnsupportedOperationException(m.getName()); - } - }); - generator.generateNoise(null, new Random(1), cx, cz, data); - return cells; - } - @Test void spawnPlateauHasSolidFoundationAndDrySurface() throws Exception { - var generator = new RecipeGenerator(source(), 48); - var cells = chunk(generator, 0, 0); - assertEquals(106, generator.height(0, 0)); - assertEquals(Material.BEDROCK, cells[0][0][0]); - assertEquals(Material.STONE, cells[0][64][0]); - assertEquals(Material.DIRT, cells[0][169][0]); - assertEquals(Material.GRASS_BLOCK, cells[0][170][0]); - assertNull(cells[0][171][0]); - } - @Test void westernLakeHasRockBedAndSourceWaterWithoutGaps() throws Exception { - var generator = new RecipeGenerator(source(), 48); - var cells = chunk(generator, -14, -1); - int x = Math.floorMod(-220,16), z = Math.floorMod(-5,16); - assertEquals(38, generator.height(-220,-5)); - assertEquals(Material.STONE,cells[x][38+64][z]); - for(int y=39;y<=48;y++) assertEquals(Material.WATER,cells[x][y+64][z]); - assertNull(cells[x][49+64][z]); - assertEquals(48,generator.visibleHeight(-220,-5)); - assertEquals(38,new RecipeGenerator(source()).visibleHeight(-220,-5)); - } - @Test void neighboringNegativeChunksMatchTheGlobalField() throws Exception { - var generator = new RecipeGenerator(source(),48); - for(int cx=-2;cx<=-1;cx++) { - var cells=chunk(generator,cx,-18); - for(int x=0;x<16;x++)for(int z=0;z<16;z++) { - int top=generator.visibleHeight(cx*16+x,-18*16+z); - assertNotNull(cells[x][top+64][z]); - assertNull(cells[x][top+65][z]); - for(int y=-64;y=16&&h<=207,"Recipe clips at "+x+","+z); - } - assertEquals(22,low); - assertTrue(high>150); - assertEquals(118,generator.height(0,-120)); - assertEquals(124,generator.height(180,-140)); - } - @Test void cutOrPreservedRecipesCannotGenerateNewWorlds() throws Exception { - var source=source();source.addProperty("mode","cut"); - assertThrows(IllegalArgumentException.class,()->new RecipeGenerator(source)); - } -} diff --git a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/StationLiftTest.java b/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/StationLiftTest.java deleted file mode 100644 index e15d776..0000000 --- a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/StationLiftTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import org.junit.jupiter.api.Test; -import org.bukkit.Material; -import static org.junit.jupiter.api.Assertions.*; - -class StationLiftTest { - @Test void partialAndProtrudingSupportsCannotBeLandingFloors() { - assertTrue(StationLift.supportsLanding(Material.WAXED_OXIDIZED_CUT_COPPER)); - assertTrue(StationLift.supportsLanding(Material.SMOOTH_SANDSTONE)); - for (Material block : new Material[]{Material.AIR,Material.SPRUCE_FENCE,Material.STONE_BRICK_WALL, - Material.SMOOTH_STONE_SLAB,Material.STONE_BRICK_STAIRS,Material.WATER}) - assertFalse(StationLift.supportsLanding(block)); - } - @Test void onlyCompletedStopsCanBeSelected() { - assertEquals(99, StationLift.feetY(1)); - assertEquals(113, StationLift.feetY(2)); - for (int floor : new int[]{-1,0,3,4,100}) - assertThrows(IllegalArgumentException.class, () -> StationLift.feetY(floor)); - } - - @Test void decorativeGoldAndOtherHeightsDoNotActAsControls() { - assertEquals(1, StationLift.selectorFloor(-6,101,-123)); - assertEquals(2, StationLift.selectorFloor(-6,115,-123)); - assertEquals(0, StationLift.selectorFloor(-5,101,-123)); - assertEquals(0, StationLift.selectorFloor(-6,101,-122)); - assertEquals(0, StationLift.selectorFloor(-6,129,-123)); - } - - @Test void controlRequiresPlayerInsideTheMatchingCabin() { - assertTrue(StationLift.inCabin(-5.5,99,-119.5,1)); - assertTrue(StationLift.inCabin(-5.5,114,-119.5,2)); - assertFalse(StationLift.inCabin(-5.5,99,-119.5,2)); - assertFalse(StationLift.inCabin(-5.5,113,-119.5,1)); - assertFalse(StationLift.inCabin(-5.5,98.9,-119.5,1)); - assertFalse(StationLift.inCabin(-5.5,99,-117,1)); - assertFalse(StationLift.inCabin(-8.1,99,-119.5,1)); - } -} diff --git a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/SurfaceMapCaptureTest.java b/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/SurfaceMapCaptureTest.java deleted file mode 100644 index f7fefa8..0000000 --- a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/SurfaceMapCaptureTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import org.bukkit.Material; -import org.junit.jupiter.api.Test; -import java.util.*; -import static org.junit.jupiter.api.Assertions.*; - -class SurfaceMapCaptureTest { - @Test void roofAndAirGapsDoNotHideGardenOrWater() { - for (Material visible : List.of(Material.SPRUCE_LEAVES, Material.WATER, Material.STONE_SLAB)) { - List reads = new ArrayList<>(); - var column = SurfaceMapCapture.visibleColumn(-64, 122, y -> { - reads.add(y); - return switch (y) { - case 122, 121 -> Material.BARRIER; - case 120 -> Material.AIR; - case 119 -> Material.CAVE_AIR; - case 118 -> Material.VOID_AIR; - case 117 -> visible; - default -> throw new AssertionError("Read beneath first visible block"); - }; - }); - assertEquals(117, column.y()); - assertEquals(visible, column.material()); - assertEquals(List.of(122, 121, 120, 119, 118, 117), reads); - } - } - - @Test void emptyColumnHasAirSentinelAndNeverReadsBelowWorld() { - List reads = new ArrayList<>(); - var column = SurfaceMapCapture.visibleColumn(-64, -62, y -> { - assertTrue(y >= -64); - reads.add(y); - return y == -63 ? Material.BARRIER : Material.AIR; - }); - assertEquals(new SurfaceMapCapture.Column(-64, Material.AIR), column); - assertEquals(List.of(-62, -63, -64), reads); - assertEquals(new SurfaceMapCapture.Column(-64, Material.BEDROCK), - SurfaceMapCapture.visibleColumn(-64, -65, y -> { - assertEquals(-64, y); - return Material.BEDROCK; - })); - } -} diff --git a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/SurfaceMapTest.java b/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/SurfaceMapTest.java deleted file mode 100644 index ade7c35..0000000 --- a/terrain-world-plugin/src/test/java/io/github/minecraftbuilder/terrainworld/SurfaceMapTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.github.minecraftbuilder.terrainworld; - -import com.google.gson.*; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import javax.imageio.ImageIO; -import java.nio.file.*; -import java.time.Instant; -import java.util.*; -import static org.junit.jupiter.api.Assertions.*; - -class SurfaceMapTest { - @TempDir Path directory; - - @Test void exportedGridPreservesCoordinatesWaterAndMarkersIndependentOfCaptureOrder() throws Exception { - SurfaceMap map = new SurfaceMap(-17, -1, -16, 0); - map.setColumn(-16, 0, 81, "minecraft:magenta_concrete"); - map.setColumn(-17, -1, 48, "minecraft:water"); - map.setColumn(-16, -1, 65, "minecraft:stone"); - map.setColumn(-17, 0, 80, "minecraft:grass_block"); - Instant start = Instant.parse("2026-09-13T00:00:00Z"), end = start.plusSeconds(2); - map.write(directory, "live-01", "lobby", "minecraft:lobby", "world-uuid", start, end); - JsonObject json = JsonParser.parseString(Files.readString(directory.resolve("live-01.json"))).getAsJsonObject(); - assertEquals(-17, json.get("min_x").getAsInt()); - assertEquals(2, json.get("width").getAsInt()); - assertEquals(4, json.get("columns").getAsInt()); - assertEquals(48, json.get("min_surface_y").getAsInt()); - assertEquals(81, json.get("max_surface_y").getAsInt()); - assertEquals(start.toString(), json.get("capture_started_at").getAsString()); - assertEquals(end.toString(), json.get("capture_finished_at").getAsString()); - assertFalse(json.get("atomic_snapshot").getAsBoolean()); - assertEquals(List.of("minecraft:barrier"), json.getAsJsonArray("ignored_materials").asList() - .stream().map(JsonElement::getAsString).toList()); - assertTrue(json.get("surface_policy").getAsString().contains("all air variants")); - assertEquals(List.of(48, 65, 80, 81), json.getAsJsonArray("surface_y").asList().stream().map(JsonElement::getAsInt).toList()); - JsonArray palette = json.getAsJsonArray("palette"), indices = json.getAsJsonArray("material_index"); - assertEquals("minecraft:water", palette.get(indices.get(0).getAsInt()).getAsString()); - assertEquals("minecraft:magenta_concrete", palette.get(indices.get(3).getAsInt()).getAsString()); - var image = ImageIO.read(directory.resolve("live-01.png").toFile()); - assertEquals(2, image.getWidth()); assertEquals(2, image.getHeight()); - assertEquals(SurfaceMap.color("minecraft:magenta_concrete"), image.getRGB(1, 1) & 0xffffff); - } - - @Test void incompleteOrDuplicateColumnsCannotProduceMisleadingMaps() { - SurfaceMap map = new SurfaceMap(0, 0, 1, 0); - map.setColumn(0, 0, 3, "minecraft:stone"); - assertThrows(IllegalStateException.class, () -> map.setColumn(0, 0, 4, "minecraft:stone")); - assertThrows(IllegalArgumentException.class, () -> map.setColumn(-1, 0, 4, "minecraft:stone")); - assertThrows(IllegalStateException.class, map::render); - assertThrows(IllegalStateException.class, () -> map.write(directory, "incomplete", "w", "w", "w", Instant.now(), Instant.now())); - assertFalse(Files.exists(directory.resolve("incomplete.json"))); - } - - @Test void filenamesCannotTraverseOrOverwriteExistingArtifacts() throws Exception { - for (String name : List.of("../bad", "/tmp/map", "UPPER", "", "a.b", "a".repeat(49))) - assertThrows(IllegalArgumentException.class, () -> SurfaceMap.validateName(name)); - SurfaceMap map = new SurfaceMap(0, 0, 0, 0); - map.setColumn(0, 0, 1, "minecraft:yellow_concrete"); - Files.writeString(directory.resolve("existing.png"), "keep"); - assertThrows(FileAlreadyExistsException.class, () -> map.write(directory, "existing", "w", "w", "w", Instant.now(), Instant.now())); - assertEquals("keep", Files.readString(directory.resolve("existing.png"))); - assertFalse(Files.exists(directory.resolve("existing.json"))); - } - - @Test void limitsRejectOverflowAndEveryConcreteColorHasItsOwnPigment() { - assertThrows(IllegalArgumentException.class, () -> new SurfaceMap(Integer.MIN_VALUE, 0, Integer.MAX_VALUE, 0)); - assertThrows(IllegalArgumentException.class, () -> new SurfaceMap(0, 0, 4096, 4096)); - assertThrows(IllegalArgumentException.class, () -> new SurfaceMap(1, 0, 0, 0)); - Set colors = new HashSet<>(); - for (String color : List.of("white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray", - "light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black")) - colors.add(SurfaceMap.color("minecraft:" + color + "_concrete")); - assertEquals(16, colors.size()); - assertNotEquals(SurfaceMap.color("minecraft:water"), SurfaceMap.color("minecraft:grass_block")); - } -}