SOURCE / PINNED RELEASE
Made of little things.
Black Site
- Release
- 666bc7c74ebd…
- Author-recorded commit
- 05e95af79676…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-d2225ddc391
Archive hash verified: 1544e072e245c4a8…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
# Black Site (napplet)
The Black Site game packaged as a NIP-5D napplet. This repository is the game's only source:
the game code in `src/game/`, the napplet wrapper and the data specs in `spec/` are all edited here. The game is licensed CC BY-NC-SA 4.0 (remixes welcome, with credit, non-commercial, same license); see LICENSE.
Keyboard input goes through NAP-KEYS (`src/game-keyboard.ts`), with local fallbacks.
## Brief
- **Visual direction:** a port, so the game keeps its existing art. That means dark military UI, lime accents and a Bahnschrift HUD over a top-down gritty map. Colors are app-owned (no host theme).
- **Frame:** fills the frame. Below 560×380 it shows a "make the window bigger" notice instead of a clipped HUD, and resumes as soon as the frame grows.
- **Domains:** none required. `keys`, `storage`, `identity`, `common`, `resource` and `outbox` are optional; `storage` holds saved progress (funds, upgrades, loadout, map, campaign, difficulty, character, best payout). Without it the game runs normally and keeps progress for the session only.
- **Keyboard boundary:** optional NAP-KEYS registers nine stable `blacksite.*` actions for equipment, reload, knife, interaction, inventory (I) and pause (P). The runtime owns forwarding and rebinding; successful registrations replace fixed action shortcuts even when unbound. Missing or denied registrations retain local defaults. WASD and Shift remain local held controls, respecting runtime-consumed events; Escape remains a local close/pause control. Tab retains focus navigation. Bindings and handlers are released on page teardown.
- **Current user:** optional `identity` reads the connected viewer and subscribes to account changes; optional `common` resolves their profile. Optional `resource` loads their profile image with an initials fallback. A clickable player entry at the bottom of the main menu opens player-account details and updates automatically. Controls and mission instructions live in Help & How to Play. See [the proposal brief](docs/identity-proposal.md). Shared map loading and friends are future proposals.
- **Account sync:** optional OUTBOX and the runtime’s app-data policy restore permanent armory progression and player customization through separate account records. Player Account offers approved batch saves and deliberate imports of device saves; unsupported runtimes show a small warning with no sync actions. See [account sync](docs/account-sync.md).
- **Removed authority:** the game's `localStorage` saves are replaced by the shell-backed store in `src/main.ts`. The game makes no direct network calls; the runtime handles remote account data.
## Workflow
```sh
bun install # Windows: soyLI's toolchain doesn't run here yet, so Bun stands in for pnpm
bun x tsc --noEmit # type-check
bun run build # -> dist/index.html (single self-contained file)
bun test tests/guidance.test.mjs tests/game-authority.test.mjs tests/keyboard.test.mjs
bun run test:spec # maps, campaigns and their specs
bun x napplet-conformance ./dist
bun scripts/smoke-frame.mjs # plays the build in a sandboxed iframe; screenshots in scripts/shots/
```
For the NAP-KEYS hosted regression, start `soyli dev --port 0`, then run
`soyli run test:keyboard:host <printed URL>`. It checks actual registration, repeat
handling, inventory, runtime rebinding, shell-triggered actions and forwarding.
Stop the preview after testing.
`src/game/*.js` are classic scripts that share top-level names. The `virtual:black-site-game`
plugin in `vite.config.ts` joins them into one module function (`bootGame`).
Maps are JSON files in `src/game/maps/` (campaign order in `index.json`), injected as `MAPS` by
the same plugin.
**[spec/](spec/README.md)** holds every data spec for integrators: the map format, custom maps as
kind 35858 events with the JSON on Blossom, and campaigns as kind 35859 events. It has the JSON
Schemas, dependency-free reference validators and helpers, and examples. `pnpm test:spec` checks
every built-in map and example against them.
---
## napplet soyLI workspace
Read [docs/napplet-space.md](docs/napplet-space.md) first for this project's CLI commands, installed skills, preview cleanup and host capabilities. Read [docs/napplet-visual-design.md](docs/napplet-visual-design.md) before styling the creation and its UI. This project and its adapted skills are already installed; do not re-scaffold or reinstall them. Use soyli dev and its printed URL for hosted preview, and stop your preview session after testing. Use the upstream guidance below with those tooling mappings.
# my-napplet
A small TypeScript starter for building a NIP-5D napplet with the published
`@napplet` packages.
Napplets are sandboxed iframe apps. The shell/runtime injects `window.napplet`
before app code runs; the app uses `@napplet/sdk` to call available NAP domains
such as outbox, identity, storage, resource, and notify.
## Start
```bash
soyli setup
soyli dev
```
Build and verify the production artifact:
```bash
pnpm verify
```
`soyli build` uses the pinned Vite plugin to produce one inlined `index.html`.
`soyli publish` signs the standalone NIP-5D manifest; its `x` is the HTML SHA-256.
Do not use the plugin's optional legacy manifest for publication. Read
`docs/napplet-manifest.md` for editable capabilities, discovery metadata and icons.
## Conformance Testing
Verify the napplet conforms to the NAP protocol before publishing. Two variants,
mirroring `vitest` vs `vitest --ui`:
```bash
pnpm test:conformance # headless: build + check; non-zero exit on failure (CI)
pnpm test:conformance:ui # live web runtime; re-runs on every source change
```
It loads the build into a real `sandbox="allow-scripts"` iframe and drives the
protocol with a reference shell. Boot, forbidden-global, and degradation checks
run for a local directory; manifest, wire, and lifecycle checks report `SKIP`
unless the invocation supplies the evidence they require.
Exacting requirements for a passing build:
- Build to a **single self-contained `index.html`** (`@napplet/vite-plugin`
`artifactMode: 'single-file'` in `vite.config.ts`). NIP-5D loads a napplet via
`iframe.srcdoc` with
`sandbox="allow-scripts"` and no `allow-same-origin` (an opaque origin) — there
is no served origin from which to fetch an external `<script src>`, so the JS
must be inlined into the one file. External-asset builds do not boot.
- Do not import `@napplet/shim` from app code. The runtime injects
`window.napplet`; conformance verifies boot and emitted envelopes through the
reference runtime.
- Emit only well-formed envelopes via `@napplet/sdk`. Declare only hard NAP
requirements in `vite.config.ts`; guard optional domains with
`window.napplet?.domain` checks and a graceful fallback.
- Do not reference `window.nostr` or use direct `fetch`/`WebSocket`/`localStorage`.
The executable `index.html` does **not** carry its own manifest hash. Before
execution, the runtime verifies the signed manifest and the HTML SHA-256 against
its `x` tag. Earlier manifests remain readable through the legacy validator.
This generic starter declares no hard domains. Each demo action is optional and
disabled when its injected domain is absent. Add `requires` only after replacing
the demo with a core task that genuinely cannot run without that domain.
## Included
- Vanilla Vite + TypeScript napplet app.
- Typed `@napplet/sdk` helpers over the runtime-injected `window.napplet`
namespace.
- Build-time `@napplet/vite-plugin` wiring for the single-file artifact and
optional local manifest generation.
- Default app-chrome text selection disabled in `src/styles.css`, with
opt-in controls for copyable or editable regions.
- Context documents for NIP-5D, shell boundaries, package surfaces, and authoring
patterns.
- Guidance for handling missing NAP interfaces or numbered wire formats without
submitting unnecessary protocol PRs.
- A responsive capability demo with a replaceable local palette and opt-in host
theming. Choose the creation's own scene/content and UI direction using
`docs/napplet-visual-design.md`; the demo is not a required visual style.
- Installed soyLI-adapted agent skills; tooling guidance in docs/napplet-space.md.
## Authoring Context
Read these before changing protocol-facing behavior:
- `docs/nip-5d.md`
- `docs/boundaries.md`
- `docs/design-patterns.md`
- `docs/package-surfaces.md`
- `docs/new-nap-proposals.md`
- `docs/authoring-checklist.md`
The living NIP-5D source is referenced from `docs/nip-5d.md`; this template does
not treat its local notes as normative protocol text.
For agent-driven work, use the installed `napplet-make` skill. Read
`docs/napplet-space.md` first; `soyli skills update` refreshes managed guidance
while preserving your edits. Living NIP-5D and NAP documents remain protocol truth.
## Text Selection
The starter disables accidental text selection by default. To change the whole
napplet, set `--napplet-text-selection: text` in `src/styles.css`. To opt in one
region, add `data-napplet-select="text"` or `data-napplet-select="all"`.
## Package Scripts
```bash
pnpm dev # local Vite dev server
pnpm type-check # TypeScript strict-mode check
pnpm build # Vite production build
pnpm preview # preview dist/
pnpm test:guidance # stale-guidance and optional-domain regression checks
pnpm verify # guidance test + type-check + build
pnpm test:conformance # headless NAP conformance (build + check, CI exit code)
pnpm test:conformance:ui # live conformance web runtime, re-runs on change
```
