Basic Cohub abilities. Most agents already have these mounted; listed here for reference.
Platform Published by the Neta team.
Official Made by the Neta team.
Verified Reviewed by the Neta team.
Community Shared by a Cohub user. Not reviewed — read SKILL.md before you use it.
panorama-npc
Officialneta-search/panorama-npc
Use it in Cohub
Paste this into your agent's chat — it fetches the skill and follows it.
Use the skill: panorama-npc. Run `npx -y @neta-art/neta-search-cli@latest skills use neta-search/panorama-npc --via site` and follow what it prints. Read its complete output, redirecting it to a temporary file first if necessary, and resolve relative paths from the skill directory it prints.
npx -y @neta-art/neta-search-cli@latest skills use neta-search/panorama-npc
Summary
Stand inside a 360° scene while characters walk around, meet, and chat in speech bubbles.
Characters stroll across the scene, stop when they meet, and take turns speaking with floating bubbles that follow them.
Tap a character to jump into a roleplay adventure with them, picking up the conversation where they left off.
Works best with a wide panorama of a place and full-body character art showing their look head to toe.
Delivers a complete web page you can share or embed, with smooth camera rotation and soft character shadows.
SKILL.md
# Panorama NPC — 360° panorama + wandering, chatting NPCs
Single-file three.js page: the camera is pinned at the center of the panorama sphere (rotates only, never moves), several transparent portrait NPCs patrol the scene, randomly meet, stop, and take turns speaking (bubbles track the character's projected position), with soft shadows. Zero build, pure static, three.js from a CDN. **Complete work by default** = panorama + static portraits + patrol chatter + walk animation + tap-to-open native roleplay (adventure).
- Template: [template/index.html](template/index.html) — top CONFIG section + bottom engine section; edit only the CONFIG section
- Tuning guide: [references/tuning.md](references/tuning.md) — re-calibration for a new image, coordinate meanings, troubleshooting, architectural invariants (**read before changing any parameter**)
- Asset specs: [references/assets.md](references/assets.md) — hard requirements for panorama/portraits + verification commands
- Roleplay integration: [references/roleplay.md](references/roleplay.md) — tap-NPC → native roleplay, shareUrl acquisition, login-state pitfalls (**read before wiring tap-to-play**)
## Language
**The work's content follows the language the user is writing in.** This skill's docs and the template's default strings are English, but everything the end user sees must match the user's own language:
- **NPC names / titles / dialogue** (`name`, `title`, `dialogues`, `idleLines`): write them in the user's language.
- **Page UI strings** in `template/index.html`: translate `Loading panorama…`, the hint (`Residents stroll and chat…` / `Tap a resident to start roleplay`), and the buttons (`Auto-rotate` / `Reset view`) into the user's language.
- **Roleplay campaign content** (character atoms, adventure premise / hooks / story directions — roleplay.md §5): write in the user's language, matching the character atoms already in the world.
- Prompt the image models in English regardless (English prompts give more reliable image results); only the visible copy follows the user.
- **Your own replies follow the user's language too** — progress lines, blocked/status notes, the delivery message and the closing options. This skill is written in English; do not let that pull your replies into English when the user wrote in another language. Names and titles the user gave keep their original form.
If the user writes in Chinese, the whole work reads Chinese; if English, English; if mixed, follow the most recent message's language.
## Inputs
| Parameter | Required | Notes |
|---|---|---|
| Panorama | Yes | 2:1 equirectangular; user-provided, or generated via text-to-image (tuning.md §2c) |
| NPC portraits | Yes | Transparent-background full-body PNG; user-provided, or generated + background-removed (tuning.md §2b) |
| Walk animation | Yes | Single-row frame strip transparent PNG; generated from the portrait + background removal (tuning.md §2d). Give an NPC `sheet/frames/fps` and it cycles while moving, holds still while idle |
| NPC setup | No | Name, height, position; default 3 |
| Dialogue | No | Pairwise lines; default placeholder lines, edit CONFIG.dialogues |
| Title | No | Page title and top-left label |
| Adventure (native roleplay) | **Second step, guided** | Not part of the first deliverable. Ship the panorama first; then ask the user to tap "share" on each adventure card in Studio and paste the URLs back — attach them as `shareUrl` (roleplay.md). Do NOT claim tap-to-roleplay works until the URLs are in hand and verified. |
Generate any missing assets first (see below); don't block on asking the user.
## Deliverables (two stages)
1. **Stage 1 — the panorama scene (always, and it's the ship-able work).** Panorama + portraits + walk animation + patrol chatter. The NPCs stroll, meet, and talk; tapping does nothing (no `shareUrl`). This stage is complete on its own and is what you deliver first.
2. **Stage 2 — tap-to-roleplay (only after the user supplies share links).** Ask the user to open each adventure card in Studio and tap "share / publish", then paste the `https://neta.art/share/work/work_share_<uuid>` links back. Attach them to each NPC's `shareUrl`, republish, and only then does tapping open the roleplay.
## Workflow
### 1. Create the scene directory, place assets
The scene dir is the web Work's own folder, `works/web/<slug>` (`ls works/web/` first to avoid a duplicate slug). Register the Work stub **before** generating anything, so the build always ends in a Work:
```bash
cd /workspace
node /mods/neta/neta work init web works/web/<slug> --title "<title>"
cp <skill-dir>/template/* works/web/<slug>/ # index.html + three.module.min.js (the bundled engine)
# Put panorama and portraits in the same directory; the page references them by relative path
```
> **Keep the engine local.** `three.module.min.js` (three.js r160, MIT) ships with the template and must sit next to `index.html`; the importmap points at `./three.module.min.js`. Never switch it to a CDN (jsdelivr, unpkg, esm.sh…): many users can't reach those hosts and the page stays black on "Loading panorama…".
> **Run the neta/cohub CLIs from the workspace root, not from inside `<scene-dir>`.** Generating images / roleplays / the `work init` stub while `cd`'d into the scene dir nests the world's files (`works/`, generated media, `meta.json`) inside the scene (it relativizes paths against the cwd). Always `cd /workspace` first, and pass the full work-ref (`works/web/<slug>` or `works/webs/<slug>`).
### 2. Prepare assets (fill whatever is missing)
- Background removal / crop / equal split: `convert` / `cwebp` commands in assets.md, tuning.md §2d
- Text-to-image: `cohub generate` (model list via `cohub models ls --model-type multimodal --json`; prompt templates in tuning.md §2b/§2c)
- Generate intermediate assets with `cohub generate ... --output <file>`, **not** `neta generate image` — the latter registers each image as its own media Work on the canvas. Panorama, portraits and strips are ingredients; the only deliverable is the one web Work.
- The birefnet-general background-removal model accepts https URLs only: run `cohub public upload` first to get a URL, then pass `--image`; add `?v=` to bust cache on re-uploads
- **Panorama**: generate with gpt-image-2 (true equirectangular, NOT Gemini — it's fisheye), then convert PNG → WebP ≤ 1.5MB, keep PNG fallback. Details in tuning.md §2c.
- **Walk animation (default on)**: generate a 4-frame strip using a rig pose reference + the character portrait as dual references (tuning.md §2d), then run `scripts/sheet-bg-remove.sh` to split frames → background-remove each → stitch back; set `sheet/frames/fps` in CONFIG. **Long/gown-length characters have hidden legs — skip the animation and use the static portrait (the only legitimate skip case)**; if generation fails, fall back to static and note in delivery that "this NPC is static"
**Speed notes (runs the slow parts in parallel):**
- `sheet-bg-remove.sh` now background-removes frames **in parallel** (default 4 at once; pass a 6th arg to raise concurrency). For 3 NPCs × 4 frames, run the 3 strips' removal concurrently instead of one after another — total wall time drops from ~12 serial minutes to ~3-4 minutes.
- Portraits (text-to-image) and panoramas can also be generated in parallel with each other when there's no dependency between them.
- Order the dependencies: panorama + portraits first (parallel) → walk strips need the finished portraits → frame-removal is per-strip (parallel within and across strips).
**If generation is blocked (credits / quota / "Add credits to continue") — still ship Stage 1 with what's in hand; never end the turn with no Work.** Step down per missing asset:
- Panorama missing → use an existing image of the place already in the world (the location material's cover, or a scene image generated earlier) as an interim backdrop.
- Portrait missing → a static NPC from the character's existing cover / reference image: birefnet if it still runs, else a local ImageMagick cutout (`convert in.png -fuzz 12% -fill none -draw "color 0,0 floodfill" -trim out.png`); if the background won't cut cleanly, use the cover as a framed card standee.
- Walk strip missing → static portrait (as above).
Then fill CONFIG, verify (step 4) and publish the web Work (step 5) as usual. In the reply, say in one line which parts are interim and that after a top-up you will regenerate them and re-run `neta work publish web` on the same work-ref (same URL); add the billing button from interaction-components. Only if the world holds no usable image of the place at all: run `neta work fail web <work-ref> --error "<reason>"`, state plainly that the build is blocked, and give the billing button.
### 3. Fill CONFIG (top config section)
Replace `{{TITLE}}`, `{{PANO_WEBP}}`/`{{PANO_PNG}}`, each NPC's `{{NPCx_NAME}}`/`{{NPCx_IMG}}`/`{{NPCx_SHEET}}`/`{{LINE}}`, and set position parameters per tuning.md §0-3. **If you swap the panorama you must recompute the walk band `walk.minD/maxD`** (method: tuning.md §1.2), or NPCs will float on water / in the void.
### 3b. Roleplay integration (stage 2 — guided, after the panorama is delivered)
Full flow in [references/roleplay.md](references/roleplay.md). Hard conclusions up front:
- **The panorama ships first without roleplay.** Leave every `shareUrl` as `''`; tapping does nothing yet. Do not block Stage 1 on getting share links.
- **The share link must come from the user, in Studio.** Open each adventure card → tap "share / publish" → copy the `https://neta.art/share/work/work_share_<uuid>` link. Ask the user to paste them back (one per NPC), attach to `shareUrl`, republish. Only then does tapping work.
- **`window.open` a new page, never an in-page iframe**: the login state is a `SameSite=Lax` cookie and is never sent inside iframes, so an iframe overlay loops on login.
- **Share pages are read-only**: no scene-image generation inside; chatting, save and load work. A fixed boundary.
- Do NOT claim tap-to-roleplay works until the user's links are in hand and the republish succeeded.
### 4. Local server + browser verification
```bash
cd <scene-dir> && python3 -m http.server 8123 &
agent-browser open http://localhost:8123 --args "--no-sandbox,--disable-dev-shm-usage"
agent-browser wait --load networkidle
agent-browser screenshot scene.png
```
Must verify (details in tuning.md §4/§6):
- Screenshot: panorama crisp, NPCs standing on walkable ground, no stretching, sensible shadows; for walk animation, compare burst screenshots for region pixel changes (`mov:true` must be playing, `talk` holds still)
- `agent-browser eval "JSON.stringify(window.__dbg)"`:
- each NPC's `d` within `[minD, maxD]`; `mov` consistent with movement state
- after ~15s, `phase` reaches `approach`/`talk`, bubbles track the speaker
- initial `phase: 'idle'` is normal
- `agent-browser console` no errors, no 404s
### 5. Publish
Pick one (they can stack):
- **Current-world work (on the Studio canvas / Works panel, recommended)**: `neta work init web` then `neta work publish web` (see the create-web-work skill). This is the proper path for "make a work in our space" — it registers a Cohub Work record, produces a `neta.art/w/<id>` page URL, schedules a thumbnail, and places the card on the canvas.
- **Standalone app (independent of the world space)**: `cohub apps publish <slug> --dir <scene-dir>` (cohub-apps skill), add `--hide-cohub-bar` for immersive display.
- **Public static page (fastest, display only)**: `cohub public upload` the directory (public-files skill).
> If you already ran `neta work publish web` and then changed the page, just re-run `neta work publish web` with the same work-ref (same Cohub Work, URL unchanged, new version).
**Hand the Work over as a card, not just a URL in prose.** Load the interaction-components skill and emit one `link` block on its own line whose target is this web Work: `{ "type": "work", "workId": "<cohubWorkId>" }`, with the id taken from this turn's `neta work publish web` output (`cohubRegisterTask.data.cohubWorkId`) or `meta.json` (`cohubWorkId` / `preview.workId`) — never an id you put together yourself. Do not use an `external-url` target for the `neta.art/w/...` page, and do not link `index.html` as a file. The `previewUrl` goes in the block's `fallback` as a Markdown link. The closing next-steps options come after this card and do not replace it.
**Default deliverable is always the web Work** (`neta work publish web works/web/<slug>`, then confirm `meta.json` shows `status: "ready"` with a `preview`) — including Stage 1 without roleplay and interim builds. Use the standalone app / public static page only when the user explicitly asks for them, and never in place of the web Work.
## Extension points
- Add an NPC: append to CONFIG.npcs + add `dialogues` combinations (pairs without a dialogue table never meet)
- Monologue: in the idle phase, pop a bubble for a visible NPC (`idleLines` is reserved, the engine doesn't consume it yet)
- Multi-scene switching / true 3D occlusion / VR: out of scope for this skill; see the workspace README (panorama-viewer / vr-demo / depth-demo routes)
## Notes
- The engine section (after ② in the template) stays untouched by default; if you must change it, read tuning.md §5 architectural invariants first, and don't use OrbitControls
- A single panorama has no depth, so NPCs are never occluded by buildings — a fixed limitation of the approach, not a bug
- Lines ≤ 20 chars each; dialogue key id-pair order is arbitrary, the engine normalizes it