Back to Sickman Survival
SOURCE / PINNED RELEASE

Made of little things.

Sickman Survival

Release
7335c9fdf0a8…
Author-recorded commit
cbd2eeafa70d…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-8804151df7f

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

README.md
## 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.

# Sickman Survival

A physics platformer about outlasting a meteor storm. A stickman runs, jumps and
explores a randomly generated, fully destructible world — cliffs with overhangs,
floating islands, multi-storey houses, trees and bouncy mushrooms, over a cave
network full of explosive moles — while meteors fall faster and hit harder the
longer they survive. A direct hit turns
the stickman into a ragdoll flung away from the blast; a near miss shoves and
spins them.

## Game brief

- **Job:** survive as long as possible; the clock is the score.
- **World:** one level per run, generated from a random seed on a 600×260 grid of
  4-unit cells (`src/terrain.ts`). The landscape sits in the top 160 rows.
  Beneath it, worm-carved caves form a shallow and a deep chain of chambers,
  linked by walkable slopes. Sloping mouths open from the surface, away from
  the spawn. A rock crust keeps the caves sealed except at the mouths, until
  meteors break through. Foreground cells collide; planks, leaves and
  mushroom caps are one-way platforms (jump up through them, press ↓ to drop
  through them). Caps bounce. Background cells (house walls, windows, trunks,
  stems) can be walked past and still get blown away. Bedrock at the bottom
  can't be destroyed.
- **Storm:** meteor interval `max(0.16 s, 1.9 s · 0.5^(t/45))`, and the blast
  radius grows with time. Early meteors mostly land elsewhere and start aiming
  for the stickman as the storm grows. Red chevrons on the top edge mark
  incoming meteors that are still off screen.
- **Moles** (`src/moles.ts`): 11 at the start, topping up to 22 as the storm
  grows. They swim through solid earth without changing it; trickling grains and
  faint red eyes give them away. When they find a cave they pop out, wander, and
  hunt the stickman. They never surface near daylight. Coming within 30 units of
  a surfaced mole arms it: it swells, flashes and shuffles closer for 0.8 s, then
  explodes like a small meteor (radius 30). Any blast sets off nearby moles,
  buried or not, so chain reactions happen.
- **Blasts:** carve a rough crater and scorch the rim. Inside 0.7× the radius
  the stickman dies and becomes a position-based ragdoll (`src/stickman.ts`).
  Out to 2.6× the radius they're thrown back and tumble.
- **Controls:** keyboard (←/→ or A/D, Space/W/↑ jump, C/Shift crouch and crawl, ↓/S drop, P/Esc pause,
  Enter/R restart, M sound), on-screen multi-touch pads, or any standard-mapping
  gamepad (`src/input.ts`). On a gamepad: left stick or D-pad runs, A/B or D-pad up
  jumps, holding X or LT crouches, and stick or D-pad down on a plank drops through it. Crouching lowers the
  hitbox to 16 units so the stickman can crawl through low gaps at about a third
  of running speed; they stand back up only where there is headroom. Start starts, pauses, resumes and restarts;
  A also starts, resumes and restarts. Select/Back toggles sound. Nearby blasts
  rumble the pad of the player they hit. If the host doesn't grant the frame the
  gamepad permission, keyboard and touch still work.
- **Local split screen** (`src/roster.ts`): up to four players, with no setup.
  - Keyboard, touch and a single gamepad all drive one solo player.
  - Plugging in a second pad splits the screen. P1 keeps their spot and takes the
    first pad, and each further pad drops in as a new player beside a living
    teammate, with a short spawn shield.
  - Unplugging a pad removes that player and re-splits the screen. Back down to
    one pad, play returns to full-screen solo with the keyboard.
  - Two players split side by side or stacked, whichever keeps panes closer to
    the game's view shape. Three or four use a 2×2 grid.
  - A dead player watches their body, then the nearest teammate. The shared
    clock runs until everyone is down.
  - Pads are tracked by browser index (`src/gamepad.ts`). Non-standard pads are
    read in the standard layout.
- **Sound** (`src/audio.ts`): synthesised with Web Audio at runtime, with no
  audio files and no music. Effects: jump blips, footsteps, landing thuds,
  mushroom boings, meteor whistles, and layered explosions (sub thump, roar,
  crack, debris patter, ear-ring on close calls). Blasts are panned and
  distance-filtered, and muffled through rock. Moles squeak and crunch out of
  walls, scratch audibly while digging nearby, and fizz and beep faster as the
  fuse burns. The ambience crossfades with depth: wind, birds early on, and
  thunder that builds with the storm on the surface; a hollow drone, drips,
  settling rock and echo in the caves. Audio starts on the first key, tap or
  click, as browsers require. Pausing freezes it.
- **Capabilities:** nothing is required. Optional `storage` keeps the best
  time and the mute toggle; without it the best time lasts for the session and the game says so.
  Optional `config` controls screen shake, touch pads, sound volume and
  controller rumble. No network or Nostr
  use.

## Visual direction

- **Character:** "Dusk before impact". A saturated sunset world in chunky
  pixel cells, with a crisp vector stickman in black ink. The sky darkens from
  violet and amber to deep red as the storm builds, so the danger shows in the
  scene itself, not only in the HUD. Underground, an earth back wall fills
  open cells, and darkness closes in beyond a pool of light around the stickman.
  Teal fungus is the one cool color in the world. It glows through the dark
  along with the moles' ember eyes and lit fuses.
- **Main surface:** a parallax sky with a low sun, stars and two hill layers.
  Terrain cells are lit from the top and darkened on exposed sides, with
  per-cell noise, and craters are scorched. Houses have lit windows. Meteors
  glow orange-white with smoke trails, and blasts flash with debris in the
  colors of the material that was blown away.
- **Interface:** condensed uppercase type, like stencilled warning signs. The
  HUD text is cream on ink, molten orange marks the single primary action and
  the best time, and ember red is reserved for incoming-meteor warnings. The
  HUD floats over the scene with no panels. Title, pause and game-over screens
  share one overlay.
- **Theme policy:** colors are app-owned. Host theme data is not applied to the
  scene or the HUD.
- **Proof frames:** a close call mid-tumble, and the ragdoll arcing away from a
  crater. Checked at 200×160, 320×560, 390×700 (touch), 900×600 and 1600×900.

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
```