Back to Black Site Map Editor
SOURCE / PINNED RELEASE

Made of little things.

Black Site Map Editor

Release
32a57733b7e9…
Author-recorded commit
3f54890f542f…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.shakespeare.diy%2F/black-site-map-editor

Archive hash verified: 7a4b1abbac7db5b0…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

README.md
# Black Site Map Editor

A sandboxed NIP-5D editor for the Black Site v1 map contract in `../black-site-map-schema`. The interface follows the game's dark olive tactical HUD, lime selections and squared controls. Colors belong to the app and remain unchanged by host themes.

Read [docs/napplet-space.md](docs/napplet-space.md) for this workspace's installed tools and host boundaries. The design and authority contract is in [docs/editor-brief.md](docs/editor-brief.md).

## Use

Run `soyli dev` and open the URL it prints. Connect your Nostr identity using the host account controls. The editor uses the connected viewer, never the CLI creator key. Stop the preview with Ctrl+C after testing.

- **Help** (or F1) opens a step-by-step tutorial with vertical step tabs. It opens once automatically for new players when draft storage is available.
- **New** starts a new map. **Maps** opens a full-frame preview browser with My maps, Following, Global and Templates collections, plus saved-draft restore. Select a map for its briefing, credits, comments, reaction counts and remix relationships, then edit your own map or create a remix. Following uses your Nostr follow list; discovery and activity reflect available relays.
- Expand Terrain, Structures, Cover & supplies or Mission in the tool palette. Choose a tool and drag to draw inclusive tile rectangles. Insertion, extraction, tanks, guards and caches use a single tile origin.
- **Select / move** picks the topmost object; drag it or use arrow keys. The Details inspector edits its position, size and properties with labeled controls, or deletes it. Use the object list to select obscured layers.
- Place buildings at least 3×3 tiles, then select the building and use **Building door** or **Interior wall**. Doors belong on the building perimeter.
- Pan, zoom and Fit work on the canvas. On phones, use Map / Tools / Details tabs. Frames below 280×240 show an enlarge notice and preserve the workspace.
- Open **Settings** and choose **Map**, **Appearance**, **Gameplay**, **Files** or **Sharing** to edit related options. The preview displays the exact authored grid; procedural generation applies in the game. **Check route** validates the schema and a walkable authored route from insertion to extraction, matching the game's unit relocation rules.
- **Save draft** stores the map through optional NAP-STORAGE under the viewer's public key. Signed-out drafts have a separate guest slot. Save failures remain visible. The **Map files** section in Settings opens saved map files and downloads backups; browser downloads may be blocked by the host sandbox.
- **Publish** uploads exact, untransformed JSON bytes via NAP-UPLOAD, verifies hash and size, then requests a native kind 35858 event through NAP-OUTBOX. Failed publication retains the upload and an exportable receipt for retry in the current session.
- **Edit** keeps your published map address and rejects stale revisions. **Remix** creates a new address with parent, root and all author credits. Credits survive updates and saved drafts. Owner-only **Delete** sends a NIP-09 request; deletion does not erase Blossom bytes or guarantee removal from every relay.

## Integrate from other napplets

The editor advertises archetype **`black-site`** and these exact NAP-INTENT conventions:

| Intent      | Convention URI                 | Payload                  | Resulting editor workspace                                                                          |
| ----------- | ------------------------------ | ------------------------ | --------------------------------------------------------------------------------------------------- |
| `map-edit`  | `napplet:black-site/map-edit`  | `{ address, relays? }`   | Edit the connected player's published map at its existing address. Another author's map is refused. |
| `map-open`  | `napplet:black-site/map-open`  | `{ address, relays? }`   | Edit an owned map; offer a remix for another author's map.                                          |
| `map-remix` | `napplet:black-site/map-remix` | `{ address, relays? }`   | Create a new map ID with parent/root attribution preserved.                                         |
| `map-new`   | `napplet:black-site/map-new`   | `{ map? }` or no payload | Start a new map, optionally seeded with a complete validated Black Site v1 map.                     |

Published-map payloads use these fields:

```ts
type PublishedMapPayload = {
  address: string; // "35858:<64 lowercase hex author public key>:<map ID>"
  relays?: string[];
};
```

Map IDs match `[a-z0-9][a-z0-9-]{0,31}`. Optional relay hints accept at most eight public WSS DNS-host URLs on port 443, without credentials or fragments; host relay policy still applies. Published-map payloads must fit within 4 KiB of serialized UTF-8 JSON. New-map JSON must pass the [Black Site v1 validator](src/vendor/validate-map.mjs) and fit within 256 KiB. Unknown payload fields are rejected.

Use the official `@napplet/sdk` URI API (tested with SDK 0.29.0):

```ts
import { intent } from '@napplet/sdk';

async function editMap(mapAuthor: string, mapId: string) {
  if (!window.napplet?.intent) {
    showError('This host does not support opening the map editor.');
    return;
  }

  const result = await intent.invoke('napplet:black-site/map-edit', {
    payload: { address: `35858:${mapAuthor}:${mapId}` },
  });
  if (!result.ok) showError(result.error ?? 'Could not open the map editor.');
}
```

`showError` represents your app's error UI. Use the same payload with `map-open` or `map-remix`. To create a blank map or hand off an unpublished map:

```ts
await intent.invoke('napplet:black-site/map-new');
await intent.invoke('napplet:black-site/map-new', {
  payload: { map: validatedMap },
});
```

Check `result.ok` for each call. It means the host accepted responsibility for delivery; it does not confirm that the target loaded the map or that the player saved it. The host discovers an installed/published handler from its manifest and may show a chooser. Use the exact queryless convention URI and pass data through `payload`.

The editor waits for Nostr login before loading published maps, verifies signed metadata and map bytes through OUTBOX/RESOURCE, and asks before replacing unsaved work. Intent delivery never automatically uploads or publishes a map. Keep INTENT optional in callers and provide a fallback when unavailable. See [docs/map-intents.md](docs/map-intents.md) for the full receiver contract.

## Integration status

The adjacent `../napplet-soy` source host now implements constrained native kind35858 publication, owner-only map deletion and current NAP-INTENT routing. The original `../black-site` game now discovers, verifies and plays custom maps and opens this editor for editing/remixing. Both use the v1 schema contract.

The installed soyLI0.27.0 executable and public website do not automatically acquire source changes. For the fully integrated local preview, run the updated source CLI from this editor directory:

```bash
bun ../napplet-soy/apps/cli/src/index.ts dev --port 0
```

Use its printed URL and connect a browser-extension viewer identity. Runtime relay/Blossom hints use this project’s existing configured public destinations; edit `napplet.json` to use another runtime provider. The preview can install a signed editor manifest into its handler catalog; published web catalogs resolve handlers from verified manifests. Handler defaults are a host setting. Builds and source-host checks do not claim a public deployment or release.

Native host admission validates bounded event metadata and remix lineage; the editor and game independently validate JSON/schema/hash/size. Every upload and publication still needs viewer consent. Current NIP46 sessions may need renewed kind35858 permission; the CLI creator's signer is never used for player actions.

## Verify

```bash
pnpm verify
pnpm test:conformance
pnpm test:ui
soyli check
```

`verify` includes schema/game grid parity, editing invariants, native event publishing, verified downloads, upload retry, identity and stale-revision tests. Browser fixtures exercise a real `allow-scripts` iframe with controlled SDK domains; they prove editor behavior separately from actual host permission. Set `SOYLI_PREVIEW_URL` to an owned `soyli dev` URL for the real-host test. Screenshots and other generated proof live in ignored `.napplet-space` state.

The reference conformance harness does not measure manifest, wire or lifecycle unless supplied evidence; report skipped checks separately. See [docs/editor-verification.md](docs/editor-verification.md) for recorded results and remaining requirements.

## License and attribution

Editor derivatives and copied Black Site map contract/templates are CC-BY-NC-SA-4.0. Original scaffold code retains its MIT notice in `LICENSES/napplet-boilerplate-MIT.txt`. Copied map libraries, schema and templates are attributed in `src/vendor/README.md`; the complete license is `src/vendor/LICENSE`. No game signing, relay pool, host DOM or direct-network implementation is included.

## Metadata and icons

`napplet.json` owns the listing title, description, topics, license, capabilities and implemented intents. Its `icon` selects `assets/icons/icon-512.png` for hash-addressed publication. The existing publication identifier and destinations are preserved.

The original tactical map/pencil icon family includes 16, 32, 48, 180, 192 and 512px PNGs plus a multi-size ICO. Browser and Apple touch icons are bundled into the single-file HTML; the listing icon is published separately by soyLI. Regenerate the family with `python scripts/generate-icons.py` (Pillow required). Icons share the project's CC-BY-NC-SA-4.0 license. The representative listing cover remains `preview.png`.