scroll-3d-world
Produce a landing page where scroll drives a camera flying through ONE
continuous 3D world — from outside each scene into its interior, then on to the
next, with no cuts and no visible seams. Built entirely with Three.js: real
geometry, one real camera path, rendered live in the browser. No AI
generation, no video files, no credits, no GPU required — it runs on any
laptop (8 GB Macs included) and is free forever.
Why this skill exists: the scroll-world skill achieves the same effect with
paid Higgsfield AI-video credits and chained clips that must be frame-locked at
every seam. This skill replaces the whole paid pipeline with real 3D. Because
there is exactly one continuous camera path, the seam problem — the #1
failure mode of the video approach — cannot exist here. And because every
pixel renders live, the page is resolution-independent (crisp on any phone,
any screen), re-editable in seconds, and costs nothing to iterate.
The camera choreography, the pacing discipline, and the copy-first section
design carry over from the video approach; the asset pipeline (generate → seam
→ encode) is replaced by building dioramas with code.
Do not assume a frontend framework. The engine in references/engine.js is
a self-contained ES module that mounts into a container you give it — plain
HTML, Next.js, Vue, anything. The template ships a complete working example;
adapt it.
Step 0 — Bootstrap
- three.js r160+. The template uses an importmap with the unpkg CDN
build. For a bundled project,
npm i three and import normally — the
engine's only import is three.
- No other dependencies. The engine is one file; the dioramas are built
with its kit helpers (procedural primitives). Blender/GLB and AI-texture
backdrops are optional upgrades — see Step 3.
- For QA you need a headless browser (Playwright/Puppeteer with a Chromium
binary) — used only to screenshot and verify the flight. Not required to
run the page itself.
Step 1 — Interview the user
The subject is the user's to state — ask it as an open question in plain
prose, never a fabricated multiple-choice. A made-up list of industries
biases them. Let them answer in their own words (their real business, a
client's, or any idea). Capture:
- Subject (ask openly) — "What should this world be about? Your business,
a client's, or any idea — a word or a sentence is fine." Capture the
industry/product + a one-line pitch (e.g. "a bubble tea company, from leaf
to last sip"), and a brand name if they have one; otherwise propose one.
- Brand kit — three paths, pick one:
- The user hands you palette + name + tone directly.
- You propose a palette + name and let them approve.
- Import from a URL (only if they have a site): fetch it with your web
tools and extract name/colours/tone yourself.
Capture 4–6 named hex values, a display name, and a tone word or two.
These hexes become
world.palette and the accent per section.
- Art direction — default is "soft matte low-poly clay diorama,
isometric, tilt-shift miniature, warm light". Offer alternatives (flat
papercraft, glossy toy, neon night, frosted glass). The choice determines
the material recipe (roughness, flatShading, light colour) — one recipe for
the whole world, never mixed.
- The journey (sections) — the ordered scenes the camera flies through.
Propose a set derived from the subject's own value chain and let the user
edit. 4–7 works well (fewer sections = more detail per scene). Boba
example: farms → pearl kitchen → flagship shop → delivery → community plaza
→ the hero product. Each section needs: a short subject description (what's
IN the diorama), an eyebrow, a headline, one line of body, and 0–3 tag
pills. The last section is usually the hero product + the CTA.
No mobile question is needed — unlike the video approach, 3D is
resolution-independent: the same world renders sharp on any phone, and the
engine hardens the page for touch (DPR clamp, safe-area copy, height-only
resize ignored) by default. Tell the user this when they ask.
Step 2 — World layout & camera choreography (the make-or-break step)
The feel of the page comes from the flight, not the geometry. Get this right
before building a single mesh.
Layout — one world, one spine, pods per section.
- Place the pods along a line or gentle arc in world space (a spine). Pods
spaced 3–4 pod-radii apart read as a connected world; farther apart reads as
empty air; closer reads as one blob. Default camera flies the spine in
order, so order the pod positions along the spine in section order.
- Give each pod a
pos, radius (scene footprint) and height (vertical
scale of its tallest object — the engine derives camera waypoints from
these two numbers).
- Vary pod y slightly (a raised plaza, a sunken garden) for terrain interest —
the spine need not be flat.
Camera grammar — the flight is the story. The engine builds one
CatmullRom curve through [approach₀, settle₀, approach₁, settle₁, …] where the
depart of each section is the next section's approach. That one rule is what
makes the journey one continuous take. Per section the camera:
- Approaches — high and outside the pod, looking down at it (the
establishing shot). The engine default:
pod.pos + (0, height·2.1, radius·2.5).
- Settles — descends to eye height in front of the subject: default
pod.pos + (0, height·0.85, radius·1.3), looking at the subject center
(pod.pos + (0, height·0.5, 0)).
- Dwells — the scroll warp holds the camera near the settle point while
the section's copy peaks (that's the
linger knob), then pushes in the
last few percent.
- Departs — pulls up and glides to the next pod's approach. Free air —
never thread the camera through geometry.
Pacing knobs (per section): scroll = viewport-heights of scroll
consumed by that section (default 1.5; hero and finale get more, transit
sections less), and linger 0–0.4 (default ~0.3; how long the camera rests at
the settle). The copy peaks exactly while the camera rests — that's the
magic. Keep linger ≤ 0.4; bigger makes the page feel sticky.
Scroll is a scrubber — visitors scroll up, so the flight also plays in
reverse. That's free and expected: curves are reversible by construction and
the camera never teleports. It's one more reason to keep waypoints in free
air and pacing smooth (no per-section camera jumps, ever).
Waypoint overrides: a section may supply explicit waypoints: [{pos,look},{pos,look}] (approach, settle) instead of the defaults — for a
hero product you want a slow orbit, give a settle that passes beside the
object; for a dramatic reveal, raise the approach. Keep the motion handoff
rule: the approach point of the NEXT section is the depart of the current, so
an override on one section changes the preceding flight too — look at the
pair, not just the section.
Step 3 — Build the dioramas (the craft)
This is where the art happens. Default is fully procedural — primitives
composed with the engine's kit — so a world needs zero asset files. Full
recipes per scene archetype live in references/diorama-kit.md; the rules
that matter everywhere:
- Palette discipline. Limit each pod to 3–5 colours from the brand kit
plus one light neutral (cream/white). Fewer colours reads as intentional
"clay toy"; more reads as noise. One material recipe for the whole world
(
roughness ~0.9, flatShading: true, no metalness) — that is the
"clay" look.
- One light for the whole world. The engine provides a warm key + cool
hemisphere fill for everything. Never add per-pod lights; per-pod lights are
how worlds lose cohesion.
- Fog is your friend. Set
fog.far ≈ world scale so far pods fade in
softly instead of popping. fog.near ≈ half of far.
- Fake shadows by default. The kit's
blob(x, z, radius) drops a soft
dark disc under every main object — cheap, deterministic, always looks
right in a clay world. Real shadow maps (world.shadows: true) are the
optional upgrade; budget for them (2048 maps, tight bounds) and skip on
mobile.
- Composition per scene. Big simple shapes; the focal subject framed with
clearance (it's the camera's target — check it from the settle waypoint);
silhouette readable from the approach angle (tall things behind, low things
in front); a foreground element or two for parallax depth.
- Ground. Every pod gets a kit
ground(radius, color) disc; the world
optionally gets one big ground plane. Match the ground tone to the fog tone
so the world doesn't look like floating islands (unless floating islands
ARE the concept).
Three optional upgrades:
- Blender models (free). For richer dioramas, model a pod in Blender,
export GLB, and set
model: 'assets/scenes/shop.glb' on the section — the
engine loads, auto-scales to the pod radius, and grounds it. Keep PBR
simple; the clay recipe applies to models too (rough, matte, flat palette).
- AI stills as backdrops (free). A user who misses the AI look can drop a
free-generated isometric image behind a pod as a billboard or ground
texture. The cohesion burden is on you: match the backdrop's palette and
light to the world, and place it where the camera looks (behind the pod).
- R3F (React Three Fiber) if the user's stack is React — port the
config/diorama code; the choreography rules are identical.
Step 4 — Wire the engine
Copy references/engine.js (and references/index-template.html to see it
wired) into the project. Mount:
import { mountScrollWorld } from './engine.js';
mountScrollWorld(document.getElementById('world'), { ...config });
Config: brand, hint, world.{sky, fog, ground, palette, shadows, sections[]}. Each section: { id, label, accent, eyebrow, title, body, tags, cta, scroll, linger, pod:{pos,radius,height}, waypoints?, build(world)?, model? }. The build(world) function receives the kit (world.box/cyl/ sphere/cone/ring/torus/group/ground/blob/loadGLB) — helpers auto-add to the
section's pod, so a build is a few lines:
build(world) {
const { cyl, sphere, box, blob, ground } = world;
ground(5, '#E0D3BA');
blob(0, 0, 4.5);
cyl('#6B4A32', 0.85, 0.85, 1.2, { x: 0, y: 1.6, z: 0 });
sphere('#E8A33D', 1.4, { x: 3, y: 3, z: -2 });
}
The engine handles: the one continuous camera path (CatmullRom, arc-length
parameterized), scroll→arc mapping with per-section dwell warps, copy panels
(fade in as the camera arrives, peak at the settle, fade on depart), route
rail, progress bar, drift particles, prefers-reduced-motion (static hero
settle frame + working CTA), touch hardening (DPR clamp, safe-area copy,
height-only resizes ignored), and a window.__sw3d.last().debug() hook for
QA. Theme the chrome with CSS variables (--sw3-ink, --sw3-ink-soft,
--sw3-accent, --sw3-font) — the world's sky/fog/palette are set in the
config, the page chrome stays quiet.
If the user's backend is non-JS (Python/Rails/static): serve engine.js +
three.js, drop the importmap and one module script into the rendered HTML.
Nothing about the engine is framework-specific.
Step 5 — QA the flight (don't skip)
Drive the page in a headless browser (Playwright/Puppeteer + Chromium):
- Frame check. Screenshot at each section's settle (scroll fraction ≈
cumulative scroll share; use
window.__sw3d.last() to read the camera
arc/position). The subject must be framed — visible, centered with
clearance, nothing clipping the lens. If a pod's subject is off-frame or
hidden behind another object, adjust waypoints/composition, not the scroll
mapping.
- Continuity. Scroll the whole chain forward fast, then back. There are
no seams, so the only failure is a hitch: a kink where the curve turns
hard or a band where the warp is too aggressive (camera visibly stops or
reverses). Smooth both ways = done.
- Console clean. No errors;
window.__sw3d.last().drawCalls should be
low (the reference demo runs 3–25 calls, a few thousand triangles — if
you're over ~200 calls, you've added too many separate meshes; merge or
reduce segments).
- Mobile viewport. Emulate a phone: copy clears the UI, no horizontal
scroll, still smooth (DPR is auto-clamped). Landscape + portrait.
- Reduced motion. Emulate
prefers-reduced-motion: reduce: page shows
the hero settle frame statically with its copy and CTA; no particles, no
scroll effect, no jump when toggled.
- Perf sanity. WebGL context count = 1; no layout thrash on scroll (the
engine only touches transform/opacity of copy panels); resize mid-scroll
doesn't reset the camera.
Gotchas (hard-won)
- Camera inside geometry → waypoints must live in free air; the defaults
always are. If a scene wants an interior moment, either make the opening
generous, cut the building in cross-section, or fly to the entrance and
settle outside it (eye height, close) — don't thread the path through a
doorway the camera doesn't fit.
- "The flight feels jerky" → you overrode waypoints with sharp turns
(CatmullRom smooths, but a 90° corner still reads), or
linger is too big
(the dwell reads as a stop). Reduce linger, spread the waypoints along the
spine, and let the curve ease. The engine's exponential smoothing
(k≈11/s) is deliberate — don't remove it.
- "It looks empty" → the fix is fog + big soft shapes + particle dust,
not more geometry. Far pods popping in =
fog.far too short. Flat sky =
add a sun orb and a couple of cloud puffs.
- Pods that don't match each other → different material recipes or extra
lights per pod. One recipe, one light, one palette — the whole world.
- Shadows that flicker/z-fight → fake blob shadows (
depthWrite:false,
polygon offset — the kit handles it); if using real shadows, check
shadow.bias and tighten the shadow camera to the world bounds.
- Slow on low-end phones → real shadow maps off, DPR clamp (engine
default 1.5 on coarse pointers), fewer segment counts (
seg on kit
helpers), no postprocessing. The reference world renders at a few thousand
triangles — keep it that light.
- Copy cut off on small screens → the engine stacks copy at the bottom on
≤760px; keep headlines short (the example style works at any width).
- "I want the AI-video look" → that's the paid scroll-world skill. This
skill is the free, live-3D route: same fly-through feel, every pixel crisp,
nothing to generate. Say so plainly, offer the trade (deterministic style
vs. AI painterliness), and build — don't upsell a paid pipeline.
References
references/engine.js — the engine: kit helpers, camera path, scroll
mapping + dwell warps, copy/rail/progress, reduced-motion and touch
hardening, QA debug hook.
references/index-template.html — standalone page with a complete working
4-section coffee-world (hills → roastery → cart → hero cup) — build
functions for every archetype, ready to adapt.
references/diorama-kit.md — clay-style recipes per scene archetype
(farm/hills, kitchen/workshop, retail, plaza/city, product hero), palette +
light rules, composition checklist, Blender/AI-backdrop guidance.
1---2name: scroll-3d-world3description: Build an immersive 3D scroll-scrubbed "fly through the world" landing page — a continuous low-poly clay-diorama world in Three.js that a camera flies through as the visitor scrolls: dive into each scene, dwell on the copy, glide to the next. Zero AI generation, zero video, zero credits, zero seams. The free alternative to the Higgsfield scroll-world pipeline. Use this whenever the user wants a "3D world" / "browse-through-the-industry" hero, a scroll cinematic, a diorama landing page, an Emons-style isometric world, an Apple-style scroll-through page, a WebGL/Three.js/R3F landing page, or wants to turn a business into a scrollable world WITHOUT paying for AI video. Make sure to use this skill whenever the user mentions 3D scroll, scrollable world, diorama hero, fly-through landing page, scroll-scrubbed site, or asks for a free alternative to scroll-world / Higgsfield / Seedance — even if they don't explicitly say "skill".4---56# scroll-3d-world78Produce a landing page where **scroll drives a camera** flying through ONE9continuous 3D world — from outside each scene into its interior, then on to the10next, with no cuts and no visible seams. Built entirely with **Three.js**: real11geometry, one real camera path, rendered live in the browser. **No AI12generation, no video files, no credits, no GPU required** — it runs on any13laptop (8 GB Macs included) and is free forever.1415**Why this skill exists:** the scroll-world skill achieves the same effect with16paid Higgsfield AI-video credits and chained clips that must be frame-locked at17every seam. This skill replaces the whole paid pipeline with real 3D. Because18there is exactly **one continuous camera path**, the seam problem — the #119failure mode of the video approach — **cannot exist here**. And because every20pixel renders live, the page is resolution-independent (crisp on any phone,21any screen), re-editable in seconds, and costs nothing to iterate.2223The camera choreography, the pacing discipline, and the copy-first section24design carry over from the video approach; the asset pipeline (generate → seam25→ encode) is replaced by building dioramas with code.2627**Do not assume a frontend framework.** The engine in `references/engine.js` is28a self-contained ES module that mounts into a container you give it — plain29HTML, Next.js, Vue, anything. The template ships a complete working example;30adapt it.3132---3334## Step 0 — Bootstrap35361. **three.js r160+.** The template uses an importmap with the unpkg CDN37 build. For a bundled project, `npm i three` and import normally — the38 engine's only import is `three`.392. **No other dependencies.** The engine is one file; the dioramas are built40 with its kit helpers (procedural primitives). Blender/GLB and AI-texture41 backdrops are optional upgrades — see Step 3.423. For QA you need a headless browser (Playwright/Puppeteer with a Chromium43 binary) — used only to screenshot and verify the flight. Not required to44 run the page itself.4546## Step 1 — Interview the user4748The **subject is the user's to state — ask it as an open question in plain49prose**, never a fabricated multiple-choice. A made-up list of industries50biases them. Let them answer in their own words (their real business, a51client's, or any idea). Capture:52531. **Subject** (ask openly) — "What should this world be about? Your business,54 a client's, or any idea — a word or a sentence is fine." Capture the55 industry/product + a one-line pitch (e.g. "a bubble tea company, from leaf56 to last sip"), and a brand name if they have one; otherwise propose one.572. **Brand kit** — three paths, pick one:58 - The user hands you palette + name + tone directly.59 - You propose a palette + name and let them approve.60 - Import from a URL (only if they have a site): fetch it with your web61 tools and extract name/colours/tone yourself.62 Capture **4–6 named hex values**, a display name, and a tone word or two.63 These hexes become `world.palette` and the accent per section.643. **Art direction** — default is "soft matte low-poly **clay diorama**,65 isometric, tilt-shift miniature, warm light". Offer alternatives (flat66 papercraft, glossy toy, neon night, frosted glass). The choice determines67 the material recipe (roughness, flatShading, light colour) — one recipe for68 the whole world, never mixed.694. **The journey (sections)** — the ordered scenes the camera flies through.70 Propose a set derived from the subject's own value chain and let the user71 edit. 4–7 works well (fewer sections = more detail per scene). Boba72 example: farms → pearl kitchen → flagship shop → delivery → community plaza73 → the hero product. Each section needs: a short subject description (what's74 IN the diorama), an eyebrow, a headline, one line of body, and 0–3 tag75 pills. The last section is usually the hero product + the CTA.7677**No mobile question is needed** — unlike the video approach, 3D is78resolution-independent: the same world renders sharp on any phone, and the79engine hardens the page for touch (DPR clamp, safe-area copy, height-only80resize ignored) by default. Tell the user this when they ask.8182## Step 2 — World layout & camera choreography (the make-or-break step)8384The feel of the page comes from the flight, not the geometry. Get this right85before building a single mesh.8687**Layout — one world, one spine, pods per section.**8889- Place the pods along a **line or gentle arc** in world space (a spine). Pods90 spaced 3–4 pod-radii apart read as a connected world; farther apart reads as91 empty air; closer reads as one blob. Default camera flies the spine in92 order, so order the pod positions along the spine in section order.93- Give each pod a `pos`, `radius` (scene footprint) and `height` (vertical94 scale of its tallest object — the engine derives camera waypoints from95 these two numbers).96- Vary pod y slightly (a raised plaza, a sunken garden) for terrain interest —97 the spine need not be flat.9899**Camera grammar — the flight is the story.** The engine builds one100CatmullRom curve through [approach₀, settle₀, approach₁, settle₁, …] where the101*depart of each section is the next section's approach*. That one rule is what102makes the journey one continuous take. Per section the camera:1031041. **Approaches** — high and outside the pod, looking down at it (the105 establishing shot). The engine default: `pod.pos + (0, height·2.1, radius·2.5)`.1062. **Settles** — descends to eye height in front of the subject: default107 `pod.pos + (0, height·0.85, radius·1.3)`, looking at the subject center108 (`pod.pos + (0, height·0.5, 0)`).1093. **Dwells** — the scroll warp holds the camera near the settle point while110 the section's copy peaks (that's the `linger` knob), then pushes in the111 last few percent.1124. **Departs** — pulls up and glides to the next pod's approach. Free air —113 never thread the camera through geometry.114115**Pacing knobs (per section):** `scroll` = viewport-heights of scroll116consumed by that section (default 1.5; hero and finale get more, transit117sections less), and `linger` 0–0.4 (default ~0.3; how long the camera rests at118the settle). The copy peaks exactly while the camera rests — that's the119magic. Keep `linger ≤ 0.4`; bigger makes the page feel sticky.120121**Scroll is a scrubber** — visitors scroll up, so the flight also plays in122reverse. That's free and expected: curves are reversible by construction and123the camera never teleports. It's one more reason to keep waypoints in free124air and pacing smooth (no per-section camera jumps, ever).125126**Waypoint overrides:** a section may supply explicit `waypoints:127[{pos,look},{pos,look}]` (approach, settle) instead of the defaults — for a128hero product you want a slow orbit, give a settle that passes beside the129object; for a dramatic reveal, raise the approach. Keep the motion handoff130rule: the approach point of the NEXT section is the depart of the current, so131an override on one section changes the preceding flight too — look at the132pair, not just the section.133134## Step 3 — Build the dioramas (the craft)135136This is where the art happens. Default is **fully procedural** — primitives137composed with the engine's kit — so a world needs zero asset files. Full138recipes per scene archetype live in `references/diorama-kit.md`; the rules139that matter everywhere:140141- **Palette discipline.** Limit each pod to 3–5 colours from the brand kit142 plus one light neutral (cream/white). Fewer colours reads as intentional143 "clay toy"; more reads as noise. One material recipe for the whole world144 (`roughness ~0.9`, `flatShading: true`, no metalness) — that is the145 "clay" look.146- **One light for the whole world.** The engine provides a warm key + cool147 hemisphere fill for everything. Never add per-pod lights; per-pod lights are148 how worlds lose cohesion.149- **Fog is your friend.** Set `fog.far` ≈ world scale so far pods fade in150 softly instead of popping. `fog.near` ≈ half of `far`.151- **Fake shadows by default.** The kit's `blob(x, z, radius)` drops a soft152 dark disc under every main object — cheap, deterministic, always looks153 right in a clay world. Real shadow maps (`world.shadows: true`) are the154 optional upgrade; budget for them (2048 maps, tight bounds) and skip on155 mobile.156- **Composition per scene.** Big simple shapes; the focal subject framed with157 clearance (it's the camera's target — check it from the settle waypoint);158 silhouette readable from the approach angle (tall things behind, low things159 in front); a foreground element or two for parallax depth.160- **Ground.** Every pod gets a kit `ground(radius, color)` disc; the world161 optionally gets one big ground plane. Match the ground tone to the fog tone162 so the world doesn't look like floating islands (unless floating islands163 ARE the concept).164165**Three optional upgrades:**166167- **Blender models (free).** For richer dioramas, model a pod in Blender,168 export GLB, and set `model: 'assets/scenes/shop.glb'` on the section — the169 engine loads, auto-scales to the pod radius, and grounds it. Keep PBR170 simple; the clay recipe applies to models too (rough, matte, flat palette).171- **AI stills as backdrops (free).** A user who misses the AI look can drop a172 free-generated isometric image behind a pod as a billboard or ground173 texture. The cohesion burden is on you: match the backdrop's palette and174 light to the world, and place it where the camera looks (behind the pod).175- **R3F (React Three Fiber)** if the user's stack is React — port the176 config/diorama code; the choreography rules are identical.177178## Step 4 — Wire the engine179180Copy `references/engine.js` (and `references/index-template.html` to see it181wired) into the project. Mount:182183```js184import { mountScrollWorld } from './engine.js';185mountScrollWorld(document.getElementById('world'), { ...config });186```187188Config: `brand`, `hint`, `world.{sky, fog, ground, palette, shadows,189sections[]}`. Each section: `{ id, label, accent, eyebrow, title, body, tags,190cta, scroll, linger, pod:{pos,radius,height}, waypoints?, build(world)?,191model? }`. The `build(world)` function receives the kit (`world.box/cyl/192sphere/cone/ring/torus/group/ground/blob/loadGLB`) — helpers auto-add to the193section's pod, so a build is a few lines:194195```js196build(world) {197 const { cyl, sphere, box, blob, ground } = world;198 ground(5, '#E0D3BA');199 blob(0, 0, 4.5);200 cyl('#6B4A32', 0.85, 0.85, 1.2, { x: 0, y: 1.6, z: 0 });201 sphere('#E8A33D', 1.4, { x: 3, y: 3, z: -2 });202}203```204205The engine handles: the one continuous camera path (CatmullRom, arc-length206parameterized), scroll→arc mapping with per-section dwell warps, copy panels207(fade in as the camera arrives, peak at the settle, fade on depart), route208rail, progress bar, drift particles, `prefers-reduced-motion` (static hero209settle frame + working CTA), touch hardening (DPR clamp, safe-area copy,210height-only resizes ignored), and a `window.__sw3d.last().debug()` hook for211QA. Theme the chrome with CSS variables (`--sw3-ink`, `--sw3-ink-soft`,212`--sw3-accent`, `--sw3-font`) — the world's sky/fog/palette are set in the213config, the page chrome stays quiet.214215If the user's backend is non-JS (Python/Rails/static): serve `engine.js` +216three.js, drop the importmap and one module script into the rendered HTML.217Nothing about the engine is framework-specific.218219## Step 5 — QA the flight (don't skip)220221Drive the page in a headless browser (Playwright/Puppeteer + Chromium):222223- **Frame check.** Screenshot at each section's settle (scroll fraction ≈224 cumulative scroll share; use `window.__sw3d.last()` to read the camera225 arc/position). The subject must be framed — visible, centered with226 clearance, nothing clipping the lens. If a pod's subject is off-frame or227 hidden behind another object, adjust waypoints/composition, not the scroll228 mapping.229- **Continuity.** Scroll the whole chain forward fast, then back. There are230 no seams, so the only failure is a *hitch*: a kink where the curve turns231 hard or a band where the warp is too aggressive (camera visibly stops or232 reverses). Smooth both ways = done.233- **Console clean.** No errors; `window.__sw3d.last().drawCalls` should be234 low (the reference demo runs 3–25 calls, a few thousand triangles — if235 you're over ~200 calls, you've added too many separate meshes; merge or236 reduce segments).237- **Mobile viewport.** Emulate a phone: copy clears the UI, no horizontal238 scroll, still smooth (DPR is auto-clamped). Landscape + portrait.239- **Reduced motion.** Emulate `prefers-reduced-motion: reduce`: page shows240 the hero settle frame statically with its copy and CTA; no particles, no241 scroll effect, no jump when toggled.242- **Perf sanity.** WebGL context count = 1; no layout thrash on scroll (the243 engine only touches transform/opacity of copy panels); resize mid-scroll244 doesn't reset the camera.245246## Gotchas (hard-won)247248- **Camera inside geometry** → waypoints must live in free air; the defaults249 always are. If a scene wants an interior moment, either make the opening250 generous, cut the building in cross-section, or fly to the entrance and251 settle outside it (eye height, close) — don't thread the path through a252 doorway the camera doesn't fit.253- **"The flight feels jerky"** → you overrode waypoints with sharp turns254 (CatmullRom smooths, but a 90° corner still reads), or `linger` is too big255 (the dwell reads as a stop). Reduce linger, spread the waypoints along the256 spine, and let the curve ease. The engine's exponential smoothing257 (`k≈11/s`) is deliberate — don't remove it.258- **"It looks empty"** → the fix is fog + big soft shapes + particle dust,259 not more geometry. Far pods popping in = `fog.far` too short. Flat sky =260 add a sun orb and a couple of cloud puffs.261- **Pods that don't match each other** → different material recipes or extra262 lights per pod. One recipe, one light, one palette — the whole world.263- **Shadows that flicker/z-fight** → fake blob shadows (`depthWrite:false`,264 polygon offset — the kit handles it); if using real shadows, check265 `shadow.bias` and tighten the shadow camera to the world bounds.266- **Slow on low-end phones** → real shadow maps off, DPR clamp (engine267 default 1.5 on coarse pointers), fewer segment counts (`seg` on kit268 helpers), no postprocessing. The reference world renders at a few thousand269 triangles — keep it that light.270- **Copy cut off on small screens** → the engine stacks copy at the bottom on271 ≤760px; keep headlines short (the example style works at any width).272- **"I want the AI-video look"** → that's the paid scroll-world skill. This273 skill is the free, live-3D route: same fly-through feel, every pixel crisp,274 nothing to generate. Say so plainly, offer the trade (deterministic style275 vs. AI painterliness), and build — don't upsell a paid pipeline.276277## References278279- `references/engine.js` — the engine: kit helpers, camera path, scroll280 mapping + dwell warps, copy/rail/progress, reduced-motion and touch281 hardening, QA debug hook.282- `references/index-template.html` — standalone page with a complete working283 4-section coffee-world (hills → roastery → cart → hero cup) — build284 functions for every archetype, ready to adapt.285- `references/diorama-kit.md` — clay-style recipes per scene archetype286 (farm/hills, kitchen/workshop, retail, plaza/city, product hero), palette +287 light rules, composition checklist, Blender/AI-backdrop guidance.