Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ A local visual map and building editor for **Project Zomboid Build 42**, written
## Streamed world editor (development branch)

- **Work on one section:** save a world as a section workspace, browse its manifest and open only the section being edited. Switching saves committed edits first; stale revisions retain the local copy and offer an explicit reload/discard action.
- **See neighbouring geometry:** section outlines distinguish loaded and unloaded terrain. Crossing buildings and infrastructure remain visible and reserved; shared geometry is read-only in an isolated section.
- **See neighbouring geometry:** section outlines distinguish loaded and unloaded terrain. Crossing buildings, decorations, fences and infrastructure remain visible and reserved; shared geometry is read-only in an isolated section.
- **Share decorations across seams:** move a section decoration group into the world's **Shared decorations** layer. It is stored once in world tiles, and every section draws the part inside it.
- **Control editing layers:** hide or lock terrain, roads, paths, buildings, objects, models, spawns and authoring zones. Annotation order is adjustable; physical sprites keep their depth order.
- **Draw and review:** finish geometry drafts before applying them. Author polygon game/vehicle zones, reversible zombie-density regions and reviewed whole-room mappings. The merged core supports native fence lines and geographic rotation; unsupported adapter operations stay downloadable drafts. The current building adapter accepts footprints from 6×6 tiles.
- **Draw and review:** finish geometry drafts before applying them. Author polygon game/vehicle zones, reversible zombie-density regions and reviewed whole-room mappings. The merged core supports native fence lines and geographic rotation; unsupported adapter operations stay downloadable drafts. Buildings accept footprints from 2×2 tiles; under 6 tiles on a side a building is one room (a shed, kiosk or booth).
- **Export a saved revision:** section and world export jobs read immutable section data in sequence. Compatibility whole-world JSON loading retains its separate size limit.
- **Name and install the mod:** every export accepts a mod id, map folder and title, can place `media/maps` under `common/`, and can install straight into `Zomboid/mods` without replacing folders it did not create.
- **Build without the browser:** `simplepzmap build scene.json` imports a real place into a world, honours building tags (`pz:kind`, `pz:style`, `roof:colour`, `pz:wall`, `pz:trim`, attached buildings), runs the seam check and exports; `validate --game` checks every tile name against the installed game, `tiles` builds `.pack`/`.tiles` tile mods from PNG frames and `probe` loads the export with the game's own classes. [Command line guide](docs/CLI.md).

[Step-by-step section, layer and polygon guide](docs/STREAMED-WORLD-EDITOR.md) · [Zone semantics and API](docs/ZONE-AUTHORING.md) · [Core/editor integration contract](docs/SPZM-SCENE-CONTRACT.md)

Expand Down
1 change: 1 addition & 0 deletions SimplePZMapmaker.slnx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/SimplePZMapmaker.Cli/SimplePZMapmaker.Cli.csproj" />
<Project Path="src/SimplePZMapmaker.Core/SimplePZMapmaker.Core.csproj" />
<Project Path="src/SimplePZMapmaker.Web/SimplePZMapmaker.Web.csproj" />
</Folder>
Expand Down
84 changes: 84 additions & 0 deletions docs/CLI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Headless command line

`simplepzmap` builds, checks and exports maps without the browser editor. It uses the same importer, validators and native exporter as the application, so a scene built here opens unchanged in the editor.

Run it from the repository with `dotnet run --project src/SimplePZMapmaker.Cli -- <command> …`, or build it once with `dotnet build src/SimplePZMapmaker.Cli -c Release` and call `simplepzmap.exe`.

| Command | What it does |
|---|---|
| `build <scene.json>` | Imports GeoJSON or Overpass JSON into a geographic world, promotes seam-crossing buildings and fences to the world, runs the seam check and writes `.pzworld.json`; `--export mod.zip` also writes the Build 42 mod. |
| `validate <file>` | Validates a `.pzmap.json` or `.pzworld.json`. With `--source` it runs the seam check against the original data. |
| `export <file> --out mod.zip` | Writes the Build 42 mod of a map or world. |
| `tiles <tileset.json> --out tiles.zip` | Builds a tile mod (`.pack` + `.tiles`) from PNG frames. |
| `probe <mod.zip> --game <path>` | Loads the exported lots with the installed game's own classes (headers, chunks, rooms, every tile sprite; stairs with `--expect-stairs`). Needs the repository's `scripts/` folder and Java from the game. |

Common options:

- `--game <path>` points to the Build 42 installation. `build`, `validate` and `export` then check every tile name the exporter would write against the game's tile definitions and validate resources; `tiles` reads properties for `copyFrom`.
- `--strict` makes `build` fail when the seam check finds a lost or duplicated source feature.
- `--report report.json` saves the seam check.
- Mod options: `--mod-id`, `--map-folder` (the server `Map=` value, defaults to the mod id), `--mod-name` (title in the mod list and map selector), `--common-maps` (puts `media/maps` under `common/` instead of `42/`), `--install` (extracts into `Zomboid/mods`, or `--mods-dir`). An install replaces only a folder that SimplePZMapmaker created earlier (it carries a `.simplepzmapmaker` marker).

The same mod options are available on every HTTP export as query parameters: `modId`, `mapFolder`, `modName`, `commonMaps=true`, `install=true` (for example `POST /api/world/export?modId=PilnaVillage&install=true`). `install=true` is accepted only on POST, which the editor host limits to JSON requests from its own origin; workspace exports and job downloads accept POST with an empty JSON body for that. A GET can only rename the returned archive.

## Scene file

```json
{
"format": "SimplePZScene", "version": 1,
"name": "PilnaVillage",
"source": "pilna.geojson",
"reference": {
"bounds": { "south": 55.5363, "north": 55.5781, "west": 45.8833, "east": 45.9422 },
"metresPerTile": 1, "originCellX": 100, "originCellY": 100, "rotationDegrees": -11.22
},
"sectionSize": 1024, "seed": 42, "skipEmptySections": true,
"export": { "modId": "PilnaVillage", "name": "Pilna village", "commonMaps": false }
}
```

- `source` is one GeoJSON FeatureCollection or Overpass JSON file; `sources` lists several GeoJSON files whose features are merged (a feature `id` seen twice is kept once). Paths are relative to the scene file.
- `reference` is the rotated geographic reference of the [transfer contract](TRANSFER-CONTRACT.md#1-coordinates). The area is cut into `sectionSize` sections; a world holds up to 128 sections and 8,192 native cells.
- Section identities follow contract section 2: the first 32 hex digits of SHA-256(`name|X,Y`), so a rebuild keeps them.
- `skipEmptySections` leaves out sections without buildings, fences or paths.

Every section imports the whole source; buildings and barrier lines crossing a section edge are stored once at world level. The import honours the building tags of contract sections 5 and 6 (`pz:kind`, `pz:style`, `roof:colour`, `pz:wall`, `pz:trim`, `pz:attached`, `pz:door-to`).

## Seam check

The check reads the source again and assigns every building outline and barrier line to exactly one outcome:

Real output for the Pilna OpenStreetMap extract in `examples/Pilna` (1 m per tile, 1024-tile sections, rotated −11.22°):

```
world PilnaVillage: 26 sections, 1640 section buildings, 61 shared buildings, 0 shared fences
seam check building: placed-section 1640, placed-world 61, evidence 88, lost 0, duplicated 0
evidence 42: skipped: Water or road
evidence 27: skipped: Outline too large
evidence 18: skipped: Building overlap
evidence 1: skipped: crossing outline rejected at world level (water, overlap or access)
seam check: passed
tiles: all 2471 references exist in the game
```

The build took about five minutes; `probe --expect-stairs` on the resulting mod loaded 416 cells, 425,984 chunks and 9,565 rooms (1,429 upper-floor rooms, 249 stair tiles) with the installed game's classes. That extract contains no barrier lines, so fences are covered by the automated tests instead.

`evidence` features were skipped for a recorded reason (water or road, overlap, outside the world, a crossing outline rejected at world level). `lost` and `duplicated` features fail the check.

## Tileset file

```json
{
"format": "SimplePZTileset", "version": 1,
"modId": "PilnaTiles", "name": "Pilna tiles",
"tileset": "pilna_fence_01", "fileNumber": 300,
"tiles": [
{ "png": "fence_w.png", "copyFrom": "fencing_01_40", "properties": { "CustomName": "Pilna fence" } },
{ "png": "fence_n.png", "copyFrom": "fencing_01_41" }
]
}
```

Each PNG is one 8-bit RGBA or RGB frame, normally 128×256 like the game's 2x tiles. Frames are trimmed to their opaque pixels and packed into one atlas page. Tiles are named `<tileset>_<index>` in file order, eight per row. `copyFrom` copies the properties of an installed tile (wall, fence, door flags and materials); `properties` adds or overrides keys. `fileNumber` is the `tiledef` number (100–8190) and must not clash with other active mods. The archive contains `42/mod.info` (`pack=` and `tiledef=`), `42/media/texturepacks/<tileset>.pack` and `42/media/<tileset>.tiles`.

Enable the tile mod in **Game resources** to use its tiles in the editor; maps that use them then list it in `require=` automatically.
2 changes: 1 addition & 1 deletion docs/SPZM-SCENE-CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The implemented fence edge layer uses `Fences`, `FenceStyles` and `GateStyles` o

Fence authoring requires map format **12** and world format **7**; nonzero geographic rotation also requires world format **7**. A browser adapter must commit the actual fence layer and retain its resource bindings, source paths and gate styles. It must never approximate fences as ordinary decorations. Unsupported geometry or unavailable adapter capabilities remain explicit drafts.

Small-building drawing records the exact requested footprint and must not silently enlarge a 2×2 shed. Apply is enabled only for the active adapter's supported size and shared-wall behavior. The current general building adapter supports 6–64 tiles per side; the [canonical small/attached building extension](TRANSFER-CONTRACT.md#6-small-and-attached-buildings) must not be advertised as implemented merely because its schema is documented.
Small-building drawing records the exact requested footprint and must not silently enlarge a 2×2 shed. Apply is enabled only for the active adapter's supported size and shared-wall behavior. The general building adapter supports 2–64 tiles per side; footprints under 6 tiles on a side are one room. Attached buildings from the [small/attached building extension](TRANSFER-CONTRACT.md#6-small-and-attached-buildings) are implemented in the importer (`pz:attached`, map format 13); the editor keeps and validates them but has no manual attach control.

## Source appearance tags

Expand Down
19 changes: 18 additions & 1 deletion docs/STATUS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
# Transfer items 2, 4–7 and 9 — 2026-10-17

- **Building tags (item 4):** `pz:kind`, `pz:style`, `roof:colour` (named or `#rrggbb`, matched to roof sets by colours measured from the installed sprites), `pz:wall` and `pz:trim` are applied to section and shared imports and reach the native lot; unknown `pz:*` keys and unusable values are reported, never fatal.
- **Attached buildings (item 2):** `pz:attached`/`pz:door-to` place a building against another with no free line; the larger one owns the single shared wall, the smaller one keeps its own entrance, and a door joins them. Map format 13.
- **Mod export (item 6):** every export takes a mod id, map folder and title, can move `media/maps` under `common/`, and installs into `Zomboid/mods` without touching folders it did not create.
- **Command line (items 7 and 9):** `simplepzmap build/validate/export/tiles/probe`; see [CLI](CLI.md). `--game` checks every exported tile name against the installed definitions.
- **Tile mods (item 5):** `tiles` writes a PZPK v1 atlas and tdef v1 definitions from PNG frames, copying properties from installed tiles.
- **Seam check (contract §3):** every source building and fence is placed in one section, once at world level, or kept as evidence with a reason.
- Real Pilna OSM through `build --strict --game`: 26 sections, 1,640 section and 61 shared buildings, 0 lost or duplicated, 2,471 tile names found; installed game classes loaded 416 cells, 425,984 chunks and 9,565 rooms. Suites pass **404 C# tests and 262 JavaScript tests**.

# Remaining streamed-world features — 2026-10-17

- **Buildings under 6×6:** footprints from 2×2 tiles generate one accessible room; stairs fit only along a side of at least 6 tiles. Imported outlines are no longer inflated to 6×6, and tiny sheds/kiosks keep one floor with a report note. Geometry drafts, the building workshop and new one-room presets accept the smaller sizes.
- **Imported fences across seams:** the OSM world importer promotes crossing barrier lines once into the world fence layer, merges per-section gates and keeps authored lines local. Preserve edits keeps authored world fences.
- **Shared decorations:** world format 8 stores decoration groups once in world tiles. They are validated against every section, clipped into each section, protected in isolated editing, kept on their prior ground by preserve-edits reimport, stored in section workspaces and exported identically by streamed and complete-world exports.
- Suites pass **392 C# tests and 261 JavaScript tests**. A live browser session against the installed game catalog placed a 3×3 kiosk, promoted a real two-tile bed across a seam, moved, removed and exported it, and edited it through the world inspector.

# Streamed world editor verification — 2026-10-17

- Integrated the companion rotation, native fence layer, barrier import and seam ownership changes from `develop`; retained polygon zones, density and reviewed room metadata. The combined suites pass **377 C# tests and 257 JavaScript tests**.
- Section workspaces pin immutable revisions, read/save independent sections, retain old revisions and export without materializing a complete world. Explicit compatibility limits remain: 48 MiB per section, 16 MiB shared metadata, 128 MiB monolithic world JSON and 256 MiB compressed native output.
- The self-contained browser test uses **13 synthetic 1024×1024 sections**, with a house and fence crossing a seam plus a shared road. Switching loads only the requested section; committed edits persist. Polygon density, resource-backed fence drawing with a wicket, reload restoration, historical section review and locked undo were exercised through the UI.
- Independent native decoding accepts the selected section (**16 cells, 16,384 chunks, 16 rooms**) and combined world (**208 cells, 212,992 chunks, 20 room fragments**). Streamed authoring source hashes match the pinned index. Installed game classes accept the section and report **8 upper rooms and 6 stair tiles**. No interactive game session was started.
- Private populated Pilna scenes were not used for acceptance. The illustrated [guide](STREAMED-WORLD-EDITOR.md) records remaining source fence promotion, tiny-building, shared-decoration and isolated-lighting boundaries. Public packaging excludes private art and local installation paths.
- Private populated Pilna scenes were not used for acceptance. The illustrated [guide](STREAMED-WORLD-EDITOR.md) records the isolated-lighting boundary. Public packaging excludes private art and local installation paths.

# Real-place transfer development verification — 2026-10-16

Expand Down
2 changes: 2 additions & 0 deletions docs/STREAMED-WORKSPACE-STORAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ The native writer composes one authored/projected section at a time. Shared supp

World format 7 stores shared `Fences`, `FenceStyles`, and `GateStyles` in the metadata blob. A section source retains only its own fences. Projections translate complete shared lines before the native composer clips them, preserving gate position and post phase across seams. Conflicting world/section style definitions and duplicate local/shared fence IDs are rejected. Fence pieces resolve through the active resource catalog; existing section provider bindings are checked before they can be replaced. The current fence DTO has no independent world resource-binding field, so never infer an original provider for an unbound imported style.

World format 8 adds shared `Decorations` to the metadata blob. Each group keeps absolute world tiles, its parts and its own `RequiredMods`/`AssetSources`. A section source never stores them; projection clips each group to the section and adds its providers to the projected section. A section save is refused when local content covers or floods a shared decoration, or repeats its ID.

## Explicit budgets

- One section blob: 48 MiB; shared metadata: 16 MiB; manifest: 1 MiB.
Expand Down
Loading