Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e4a5c256d | |||
| 8cfceffb76 | |||
| 5f8b8f14e6 | |||
| 59c1c3a895 | |||
| 582183db2f | |||
| c13ce36787 | |||
| 21830069ad | |||
| 50366c056e |
@@ -0,0 +1,94 @@
|
||||
# Session 0027.0 — Transcript
|
||||
|
||||
> App: human-experience-filter-art
|
||||
> Start: 2026-06-29T22-04 (PST)
|
||||
> End: 2026-06-30T06-47 (PST)
|
||||
> Type: planning-and-executing
|
||||
> Posture: yolo
|
||||
> Claude-Session: 3e51cce1-9023-4a16-b607-99894dfe0597
|
||||
> Checkout: /Users/benstull/git/benstull.org/benstull/human-experience-filter-art
|
||||
> Status: **FINALIZED**
|
||||
|
||||
## Launch prompt
|
||||
|
||||
```
|
||||
Add left and right brain annotations (including localization) for any that are missing.
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
> Anchor: operator direct instruction (standalone leaf content-fill task, §4.3 R2b).
|
||||
|
||||
**Goal:** Author the missing **left-brain** factual `LABELS` (tiered general→scientific→+fact
|
||||
labels) for the rotating-pool clips that lack them, plus their **es/fr/ja** translations,
|
||||
then rebuild + merge the manifest. Right-brain `affect` is auto-generated per scale for
|
||||
every clip via `_affect_for_clip` — nothing missing there.
|
||||
|
||||
## Pre-state
|
||||
|
||||
- `main` at `5beee55` locally but **diverged from `origin/main`**: 11 merged-but-unpushed
|
||||
commits (the entire i18n localization feature + a video-loop safety-net fix) stranded
|
||||
locally; `origin/main` still at `eff43bd` (PR #29). Plus 3 files with an uncommitted
|
||||
"boots-silent / no auto-start" change. Two stale `--INPROGRESS` placeholders (0015, 0017).
|
||||
- Annotation reality (from a read-only survey of `build_pool_manifest.py` + catalogs):
|
||||
right-brain `affect` present on all 41 clips (auto-per-scale); left-brain `LABELS`
|
||||
present on only 13 of 41. 28 pool clips had `"annotations": []`.
|
||||
|
||||
## Arc
|
||||
|
||||
1. **Scoped the task** — explored the annotation/i18n system; established that the gap was
|
||||
left-brain only (right-brain is auto-generated), 28 clips, + their es/fr/ja strings.
|
||||
2. **Claimed session 0027** (peek showed the two stale placeholders; noted + proceeded).
|
||||
3. **Surfaced the unclean baseline**; operator chose *"Push stranded, also commit dirty."*
|
||||
- Stashed the 3 dirty files, merged `origin/main`'s claim commit, **pushed the 11
|
||||
stranded commits** (`020219f`).
|
||||
- Committed the boots-silent change on `fix/boot-silent-no-autostart`, verified
|
||||
(299 pytest pass), **merged + pushed** (`7bd6c9a`).
|
||||
4. **Annotation work** on `feat/left-brain-annotations`:
|
||||
- Added 28 `LABELS` entries (static tiered labels) to `build_pool_manifest.py`
|
||||
(Round-5 block); rebuilt manifest → all 41 clips annotated; extracted en catalog.
|
||||
- Dispatched **3 parallel translator subagents** (es/fr/ja) for the 67 new keys,
|
||||
matching existing catalog voice; fixed 2 ja `measure.*` values to the `約`/localized
|
||||
convention; merged fragments into catalogs; merged all 3 langs into the manifest.
|
||||
- Verified: 41 clips × en/es/fr/ja full key + tier-length parity; 299 pytest pass
|
||||
(3 pre-existing `cv2` env failures only); 37 i18n/manifest tests green; spot-checked
|
||||
translations. **Merged + pushed** (`934f60c`), deleted both branches.
|
||||
|
||||
## Cut state
|
||||
|
||||
- `main` == `origin/main` @ `934f60c`, clean tree, branches removed.
|
||||
- 41/41 clips carry left+right annotations with full en/es/fr/ja parity.
|
||||
- §9: local tests green. No PPE/E2E machinery exists for this app yet (pipeline is policy,
|
||||
not automated — §10.6); prod promotion stays operator-gated. Visual review deferred to
|
||||
operator (no Chrome on box).
|
||||
|
||||
## Operator plate
|
||||
|
||||
- **Review by eye** the 28 newly-labelled clips across all 4 languages in the running sim.
|
||||
- The labels are **static**; the abyss/reef *creature* clips would benefit from a later
|
||||
**tracked-label** upgrade via author mode (couldn't auto-file as a tracker issue — the
|
||||
capture token returned 401 in this env; full context is in the session-0027 memory note).
|
||||
- A few species IDs were inferred from clip META, not the frame (`reef_redsea`→anthias,
|
||||
`reef_flowergarden`→jack/scad+sergeant-major) — worth confirming at review.
|
||||
- Two stale `--INPROGRESS` sessions (0015, 0017) remain unfinalized — likely the crashed
|
||||
sessions that stranded the 11 commits; left untouched for the operator.
|
||||
|
||||
## Next-session prompt
|
||||
|
||||
```
|
||||
/goal Review the new left-brain annotations (all 28 newly-labelled pool clips, across en/es/fr/ja) by eye in the running simulator; correct any species/box that reads wrong, and decide whether the abyss/reef creature clips should get tracked labels via author mode.
|
||||
```
|
||||
|
||||
## Deferred decisions
|
||||
|
||||
- **Static labels, not tracked.** Authored all 28 clips' left-brain labels as fixed-box
|
||||
`static_label`s rather than motion-tracked labels. Alternative: tracked labels (as the
|
||||
reef/abyss showcase clips use) so the box follows drifting creatures. Chose static because
|
||||
the footage wasn't eyeballed — fabricating track coordinates would claim precision I don't
|
||||
have. The abyss/reef creature clips are the ones that would most benefit from a later
|
||||
tracked-label upgrade via author mode.
|
||||
- **Some species IDs inferred from clip metadata, not the footage.** `reef_redsea`→anthias,
|
||||
`reef_flowergarden`→jack/scad + sergeant-major — read from the clip META title/source.
|
||||
High-confidence from provenance, but worth an eyeball check.
|
||||
- **`O-type` kept verbatim in es/fr/ja** (per the translation brief) rather than the more
|
||||
natural `tipo O` / `type O`. Minor; flagged by the es translator.
|
||||
@@ -1,28 +0,0 @@
|
||||
# Session 0027.0 — Transcript
|
||||
|
||||
> App: human-experience-filter-art
|
||||
> Start: 2026-06-29T22-04 (PST)
|
||||
> Type: planning-and-executing
|
||||
> Posture: yolo
|
||||
> Claude-Session: 3e51cce1-9023-4a16-b607-99894dfe0597
|
||||
> Checkout: /Users/benstull/git/benstull.org/benstull/human-experience-filter-art
|
||||
> Status: **PLACEHOLDER — claimed at session start; finalized at session end.**
|
||||
>
|
||||
> This file reserves session ID 0027 for human-experience-filter-art. The driver replaces this
|
||||
> body with the full transcript and renames the file to its final
|
||||
> SESSION-0027.0-TRANSCRIPT-2026-06-29T22-04--<end>.md form at session end.
|
||||
|
||||
## Launch prompt
|
||||
|
||||
```
|
||||
Add left and right brain annotations (including localization) for any that are missing.
|
||||
|
||||
Resolved scope: right-brain `affect` is auto-generated per scale for every clip (nothing missing). The gap is left-brain `LABELS` (factual/scientific tiered labels) on 28 rotating-pool clips that have no entry in simulator/build_pool_manifest.py, plus their es/fr/ja translations in tools/i18n/catalogs. Author them following the existing static_label pattern, rebuild the manifest, merge translations, run tests.
|
||||
|
||||
```
|
||||
|
||||
## Deferred decisions
|
||||
|
||||
_Autonomous-mode low-confidence calls the driver made and would have
|
||||
liked operator input on. Appended as the session runs; surfaced at
|
||||
finalize. Empty if none._
|
||||
@@ -0,0 +1,125 @@
|
||||
# Session 0028.0 — Transcript
|
||||
|
||||
> App: human-experience-filter-art
|
||||
> Start: 2026-06-30T06-53 (PST)
|
||||
> Type: planning-and-executing
|
||||
> Posture: yolo
|
||||
> Claude-Session: d9da7994-e036-4b7c-b2d4-d2675ac4e5a1
|
||||
> Checkout: /Users/benstull/git/benstull.org/benstull/human-experience-filter-art
|
||||
> Status: **FINALIZED.**
|
||||
> Worktree: `worktree-session-0028` (isolated; concurrent session in flight). Torn down at finalize.
|
||||
> Outcome: D3 reverse-landing frame jump FIXED, app-side. PR #30 → main (`582183d`).
|
||||
|
||||
## Launch prompt
|
||||
|
||||
`/goal next` — operator then directed: "There's a concurrent session — do this one out
|
||||
of a worktree in a different dir." Session 0028 runs isolated in worktree
|
||||
`worktree-session-0028` while a concurrent session owns the simulator startup ("Run
|
||||
simulation" button: `index.html`/`style.css`/`i18n.js`).
|
||||
|
||||
## Plan
|
||||
|
||||
> Anchor: leaf bug — D3 reverse-landing frame jump (no tracker issue; the s0027 capture
|
||||
> token returned 401. Residual flagged in the altitude-morph-and-scrub memory note +
|
||||
> scrub-driven-transitions design.)
|
||||
|
||||
**Goal:** kill the ~3-second frame jump when ASCENDING the altitude dial and landing on
|
||||
the higher-altitude clip.
|
||||
|
||||
**Root cause (verified by reading the bake + player):** each baked morph
|
||||
`transitions/{h}__{l}.mp4` spans `src(h)@0s` (frac 0) → `dst(l)@~LOOP_TAIL_S` (frac 1),
|
||||
and the scrub uses this single forward file in BOTH directions. The steady loop element
|
||||
always seeks to `LOOP_TAIL_S` (3.0s) on landing. So:
|
||||
- DESCEND (dir>0) lands on `dst` at frac 1 → morph ends `dst@3` ≈ loop@3 → seamless ✓
|
||||
(the s0026 "loop-from-tail" fix).
|
||||
- ASCEND (dir<0) lands on `src` at frac 0 → morph shows `src@0`, but loop seeks `src@3`
|
||||
→ ~3s jump ✗ (D3).
|
||||
|
||||
**Fix (app-side, no media re-bake — beats re-baking 154 LFS morphs):** make the loop
|
||||
landing frame DIRECTION-AWARE. On a reverse landing seek the loop to 0 (= the morph's
|
||||
frac-0 frame) and wrap `[0,dur]` (the base is already a seamless crossfade-loop); forward
|
||||
keeps `[LOOP_TAIL_S,dur]`. Pure decision extracted to `scrub.js loopLandFrame(dir,
|
||||
loopTailS)` + node tests; wired through `loadLoop`/settle/wrap handlers in `app.js`.
|
||||
|
||||
**Tasks:**
|
||||
1. TDD `HEFScrub.loopLandFrame(dir, loopTailS)` in `scrub.js` (+ `scrub.test.js`).
|
||||
2. Wire `app.js`: `loadLoop(clip, landFrame)`, directional settle seek, `dataset.loopStart`
|
||||
in the `timeupdate`/`ended` wrap handlers.
|
||||
3. Add a reverse-landing e2e (mirror of the existing forward "loops from tail" test).
|
||||
4. Verify: node unit tests + pytest + e2e (best-effort) all green.
|
||||
5. Branch → PR → merge (yolo). PPE/E2E machinery not built for this app (§10.6) — local
|
||||
green is the bar; prod promotion stays operator-gated.
|
||||
|
||||
## Deferred decisions
|
||||
|
||||
_Autonomous-mode low-confidence calls the driver made and would have
|
||||
liked operator input on. Appended as the session runs; surfaced at
|
||||
finalize. Empty if none._
|
||||
|
||||
- **Fix shape: app-side direction-aware loop landing, NOT a 154-morph re-bake.** The
|
||||
s0026 memory note guessed the D3 fix would be "per-clip loop files." Reading the bake +
|
||||
player showed the root cause is simpler: the morph's frac-0 frame is `src@0` but the
|
||||
loop always seeked the tail (`LOOP_TAIL_S`). Fixed entirely in `app.js`/`scrub.js` by
|
||||
landing the loop on the morph's actual frac-0 frame (0) when ascending. This avoids
|
||||
re-baking 154 LFS morphs (no media churn, a tiny reviewable diff, the base clips are
|
||||
already seamless crossfade-loops so `[0,dur]` wraps cleanly). Alternative considered:
|
||||
re-bake morphs to start at `src@LOOP_TAIL_S` — heavier, no benefit.
|
||||
- **Pre-existing e2e failure left as-is:** `loop-recovery.spec.ts` fails in this
|
||||
environment on the CLEAN baseline too (verified by stashing my changes). It boots
|
||||
without enabling video, so the loop's `loopTail` is never armed and the `ended` guard
|
||||
returns early. Not introduced here; my change preserves that guard exactly. Worth a
|
||||
separate look (the test likely needs to `enableVideo` first), but out of scope for D3.
|
||||
- **Could not verify by eye.** The fix is logic- and test-verified (node + the new
|
||||
reverse-landing e2e asserting the loop anchors at the head, not the tail); the actual
|
||||
visual seamlessness of an ascending landing still wants an operator eyeball in the
|
||||
running sim.
|
||||
|
||||
## Session arc
|
||||
|
||||
1. Claimed session **0028** (race-free, via git). Two stale `--INPROGRESS` placeholders
|
||||
(0015, 0017) noted as orphaned, left untouched.
|
||||
2. Baseline survey surfaced uncommitted `run-sim` button edits + main behind by 2
|
||||
(session commits). Operator confirmed a **concurrent session** owns that startup work
|
||||
and directed isolation → created worktree `worktree-session-0028` off `origin/main`,
|
||||
leaving the concurrent session's edits in the canonical clone untouched.
|
||||
3. Orientation: in-repo `docs/ROADMAP.md` is stale (formal frontier = deferred hardware);
|
||||
live frontier is in memory. Stored `/goal next` (0027's by-eye annotation review) is
|
||||
operator-eyes work I can't complete; the run-sim button is the concurrent session's.
|
||||
Asked the operator to pick 0028's disjoint item → **D3 reverse-landing fix**.
|
||||
4. Read the bake (`build_pool_manifest.transition_cmd`) + player (`app.js` loop/morph) to
|
||||
pin the root cause (morph spans `src@0`→`dst@tail`; loop always seeks the tail).
|
||||
5. TDD: `HEFScrub.loopLandFrame` (red → green), then wired `app.js`
|
||||
(`loadLoop(clip,landFrame)`, `dataset.loopStart`, directional settle, wrap handlers).
|
||||
6. Added a reverse-landing e2e. Verified: node 16/16, **altitude-lock e2e 12/12** (forward
|
||||
no-regression + new reverse test), pytest 299 pass. Confirmed `loop-recovery.spec.ts`
|
||||
fails on the clean baseline too (pre-existing, not mine) by stashing.
|
||||
7. Committed → PR #30 → merged to `main` (`582183d`) → deleted branch. Updated memory.
|
||||
|
||||
## Cut state
|
||||
|
||||
- **On `main` (`582183d`):** D3 fix — `simulator/static/scrub.js` (+`loopLandFrame`),
|
||||
`simulator/static/app.js` (direction-aware loop landing), `simulator/unit/scrub.test.js`,
|
||||
`simulator/e2e/tests/altitude-lock.spec.ts` (reverse-landing test).
|
||||
- **Tests:** node 16/16; altitude-lock e2e 12/12; pytest 299 pass / 3 pre-existing
|
||||
env-only failures (real-ffprobe/ffmpeg + ML detect; untouched — no Python changed) / 4
|
||||
skipped. `loop-recovery.spec.ts` is a pre-existing baseline failure in this env.
|
||||
- **§9 / deploy:** this whole project is **exempt** from flotilla/PPE/§9 (memory:
|
||||
project-exempt-from-wiggleverse-deploy; public target is static Cloudflare). Local-green
|
||||
is the bar — met. No PPE stage to run.
|
||||
- **No plan artifact archived:** fused leaf fix, plan inline in this transcript; app has
|
||||
**no content repo** (`CONTENT_REMOTE` empty) — nothing to archive.
|
||||
|
||||
## Operator plate
|
||||
|
||||
- **Review by eye** an ASCENDING altitude landing in the running sim — confirm the
|
||||
~3s reverse-landing jump is gone (logic/test-verified only; no Chrome on box here).
|
||||
- Still standing from s0027: by-eye review of the 28 left-brain annotations (4 langs);
|
||||
decide on tracked labels for abyss/reef creature clips.
|
||||
- A concurrent session was shipping the "Run simulation" startup button — reconcile/land
|
||||
that separately (its edits are in the canonical clone, not touched here).
|
||||
|
||||
## Next-session prompt
|
||||
|
||||
```
|
||||
/goal Review by eye in the running simulator: (1) an ASCENDING altitude landing — confirm the D3 reverse-landing frame jump is gone; (2) the 28 left-brain annotations across en/es/fr/ja, correcting any species/box that reads wrong; then decide whether the abyss/reef creature clips should get tracked labels via author mode.
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
# Session 0029.0 — Transcript
|
||||
|
||||
> App: human-experience-filter-art
|
||||
> Start: 2026-06-30T08-09 (PST)
|
||||
> Type: planning-and-executing
|
||||
> Posture: yolo
|
||||
> Claude-Session: c87a07d3-917c-44bc-82cf-c6d8edee952b
|
||||
> Checkout: /Users/benstull/git/benstull.org/benstull/human-experience-filter-art
|
||||
> Status: **PLACEHOLDER — claimed at session start; finalized at session end.**
|
||||
>
|
||||
> This file reserves session ID 0029 for human-experience-filter-art. The driver replaces this
|
||||
> body with the full transcript and renames the file to its final
|
||||
> SESSION-0029.0-TRANSCRIPT-2026-06-30T08-09--<end>.md form at session end.
|
||||
|
||||
## Launch prompt
|
||||
|
||||
_(launch prompt not captured at claim time)_
|
||||
|
||||
## Deferred decisions
|
||||
|
||||
_Autonomous-mode low-confidence calls the driver made and would have
|
||||
liked operator input on. Appended as the session runs; surfaced at
|
||||
finalize. Empty if none._
|
||||
@@ -79,5 +79,11 @@
|
||||
},
|
||||
"0027": {
|
||||
"title": ""
|
||||
},
|
||||
"0028": {
|
||||
"title": ""
|
||||
},
|
||||
"0029": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +269,7 @@ LABELS: dict[str, list[dict]] = {
|
||||
measure("measure.distance", "≈7,500 ly", [0.06, 0.06, 0.2, 0.08], 3),
|
||||
],
|
||||
"cosmos_galaxies": [
|
||||
measure("measure.count", "~2T galaxies", [0.06, 0.16, 0.24, 0.08], 2),
|
||||
static_label("detected.galaxy", 4, ["galaxy", "spiral galaxy", "barred spiral", "barred spiral · ~10¹¹ stars"], [0.34, 0.30, 0.30, 0.34]),
|
||||
measure("measure.distance", "~Mly", [0.06, 0.06, 0.18, 0.08], 3),
|
||||
],
|
||||
@@ -291,13 +292,19 @@ LABELS: dict[str, list[dict]] = {
|
||||
],
|
||||
"orbit_bluemarble": [
|
||||
static_label("detected.globe", 4, ["Earth", "the globe", "terrestrial planet", "terrestrial planet · 12,742 km across"], [0.28, 0.18, 0.44, 0.6]),
|
||||
# Rotating full-disk globe: continents drift through frame, so label the
|
||||
# PERSISTENT features (clouds, starfield) with static boxes, not a continent.
|
||||
static_label("detected.cloud_band", 3, ["clouds", "weather systems", "cloud systems", "global cloud systems · clouds cover ~67% of Earth"], [0.34, 0.42, 0.34, 0.32]),
|
||||
static_label("detected.starfield", 1, ["stars", "starfield", "background stars", "background starfield · a rendered backdrop, not to scale"], [0.74, 0.05, 0.22, 0.30]),
|
||||
],
|
||||
# ---------- coast ----------
|
||||
"coast_birdrock": [
|
||||
static_label("detected.seabirds", 2, ["birds", "seabirds", "nesting seabirds", "seabird colony · the rookery that names the rock"], [0.35, 0.18, 0.25, 0.2]),
|
||||
static_label("detected.surf", 4, ["waves", "surf", "breaking swell", "breaking swell · wind-driven, ~10 s period"], [0.20, 0.55, 0.6, 0.3]),
|
||||
static_label("detected.searock", 2, ["rock", "sea stack", "coastal sea stack", "sea stack · wave-cut residual rock"], [0.30, 0.25, 0.22, 0.3]),
|
||||
],
|
||||
"coast_surfgrass": [
|
||||
static_label("detected.tidepool", 2, ["pool", "tidepool", "intertidal pool", "tidepool · a pocket sea bared at low tide"], [0.15, 0.6, 0.4, 0.25]),
|
||||
static_label("detected.surfgrass", 4, ["grass", "surfgrass", "Phyllospadix", "Phyllospadix · a marine seagrass, not algae"], [0.18, 0.40, 0.5, 0.4]),
|
||||
static_label("detected.coralline", 2, ["pink", "coralline algae", "crustose coralline", "crustose coralline · calcified red algae"], [0.6, 0.55, 0.2, 0.2]),
|
||||
],
|
||||
@@ -336,6 +343,7 @@ LABELS: dict[str, list[dict]] = {
|
||||
measure("measure.depth", "−22 m", [0.06, 0.06, 0.16, 0.08], 3),
|
||||
],
|
||||
"reef_hawkfish": [
|
||||
static_label("detected.coral", 2, ["coral", "reef coral", "hard coral", "hard coral · the reef this fish grazes into sand"], [0.1, 0.6, 0.55, 0.3]),
|
||||
tracked_label(
|
||||
"detected.parrotfish", 4,
|
||||
["fish", "parrotfish", "Bolbometopon muricatum", "B. muricatum · humphead, grazes reef into sand"],
|
||||
@@ -353,6 +361,7 @@ LABELS: dict[str, list[dict]] = {
|
||||
),
|
||||
],
|
||||
"reef_coralspacific": [
|
||||
static_label("detected.polyp", 1, ["polyps", "coral polyps", "living polyps", "polyps · each a tiny animal, the colony’s builders"], [0.25, 0.4, 0.3, 0.3]),
|
||||
static_label("detected.coral", 4, ["coral", "coral colony", "Pacific scleractinian", "Pacific scleractinian · a symbiosis with algae"], [0.2, 0.4, 0.4, 0.4]),
|
||||
tracked_label(
|
||||
"detected.spotfish", 2,
|
||||
@@ -393,6 +402,7 @@ LABELS: dict[str, list[dict]] = {
|
||||
measure("measure.depth", "−1,600 m", [0.06, 0.06, 0.16, 0.08], 2),
|
||||
],
|
||||
"abyss_hiding": [
|
||||
static_label("detected.marinesnow", 1, ["specks", "marine snow", "falling detritus", "marine snow · organic debris drifting down from above"], [0.1, 0.1, 0.8, 0.6]),
|
||||
tracked_label(
|
||||
"detected.jelly", 4,
|
||||
["jelly", "crimson jelly", "Scyphozoa", "Scyphozoa · red is invisible in the lightless deep"],
|
||||
@@ -454,55 +464,68 @@ LABELS: dict[str, list[dict]] = {
|
||||
],
|
||||
# ---------- sky ----------
|
||||
"sky_grca_templesa": [
|
||||
static_label("detected.butte", 2, ["tower", "butte", "rock temple", "rock ‘temple’ · an erosional butte left standing"], [0.4, 0.3, 0.2, 0.35]),
|
||||
static_label("detected.canyon", 4, ["canyon", "gorge", "the Grand Canyon", "Grand Canyon · ~1.8 Gyr of rock, cut by the Colorado"], [0.10, 0.38, 0.8, 0.52]),
|
||||
static_label("detected.strata", 2, ["layers", "rock strata", "sedimentary beds", "strata · stacked epochs of deposition"], [0.15, 0.55, 0.6, 0.25]),
|
||||
],
|
||||
"sky_greenland_landice": [
|
||||
static_label("detected.crevasse", 2, ["cracks", "crevasses", "glacial crevasses", "crevasses · the ice fracturing as it flows"], [0.2, 0.55, 0.5, 0.25]),
|
||||
static_label("detected.icesheet", 4, ["ice", "ice sheet", "the Greenland ice sheet", "ice sheet · up to ~3 km thick, flowing slowly seaward"], [0.08, 0.40, 0.84, 0.5]),
|
||||
static_label("detected.snow", 2, ["snow", "snowfield", "firn", "firn · old snow compacting toward glacial ice"], [0.20, 0.20, 0.6, 0.25]),
|
||||
],
|
||||
"sky_greenland_suture": [
|
||||
static_label("detected.floe", 2, ["plates", "ice floes", "pack-ice floes", "pack-ice floes · drifting plates of frozen sea"], [0.15, 0.2, 0.3, 0.3]),
|
||||
static_label("detected.seaice", 4, ["ice", "sea ice", "drift ice", "drift ice · a frozen ocean skin, cracking and refreezing"], [0.08, 0.10, 0.84, 0.8]),
|
||||
static_label("detected.lead", 2, ["crack", "lead", "open lead", "lead · a fracture of open water between floes"], [0.30, 0.40, 0.4, 0.2]),
|
||||
],
|
||||
"sky_jungle_amazon": [
|
||||
static_label("detected.mist", 1, ["haze", "canopy mist", "transpiration haze", "transpiration haze · the forest exhaling water vapor"], [0.06, 0.1, 0.88, 0.18]),
|
||||
static_label("detected.canopy", 4, ["trees", "forest canopy", "the Amazon canopy", "rainforest canopy · among the densest biodiversity on Earth"], [0.06, 0.30, 0.88, 0.6]),
|
||||
static_label("detected.emergent", 2, ["tree", "tall tree", "emergent tree", "emergent · a giant breaking above the canopy roof"], [0.42, 0.40, 0.16, 0.2]),
|
||||
],
|
||||
"sky_jungle_waterfall": [
|
||||
static_label("detected.spray", 2, ["mist", "spray", "plunge spray", "plunge spray · the river aerosolized on impact"], [0.36, 0.62, 0.28, 0.25]),
|
||||
static_label("detected.waterfall", 4, ["falls", "waterfall", "cataract", "cataract · a river plunging off the canopy’s edge"], [0.34, 0.20, 0.3, 0.7]),
|
||||
static_label("detected.canopy", 2, ["trees", "jungle canopy", "rainforest canopy", "canopy · dense forest crowding the gorge"], [0.05, 0.30, 0.9, 0.5]),
|
||||
],
|
||||
"sky_coast_cliffspain": [
|
||||
static_label("detected.headland", 1, ["point", "headland", "rock promontory", "promontory · a cliffed arm of land into the sea"], [0.1, 0.2, 0.3, 0.4]),
|
||||
static_label("detected.seacliff", 4, ["cliff", "sea cliff", "coastal headland", "sea cliff · Atlantic rock cut back by the surf"], [0.10, 0.25, 0.8, 0.55]),
|
||||
static_label("detected.surf", 2, ["waves", "surf", "breaking swell", "breaking swell · ocean meeting stone"], [0.15, 0.70, 0.7, 0.25]),
|
||||
],
|
||||
"sky_mtn_castlecrags": [
|
||||
static_label("detected.talus", 2, ["scree", "talus", "talus slope", "talus · frost-shattered rock piled below the crags"], [0.3, 0.55, 0.4, 0.25]),
|
||||
static_label("detected.spire", 4, ["rock", "spire", "granite spire", "granite spire · a glacier-carved pluton, exhumed and weathered"], [0.30, 0.20, 0.4, 0.6]),
|
||||
static_label("detected.forest", 2, ["trees", "conifer forest", "montane forest", "montane forest · cloaking the slopes below the crags"], [0.05, 0.60, 0.9, 0.35]),
|
||||
],
|
||||
"sky_mtn_rocky": [
|
||||
static_label("detected.snowfield", 2, ["snow", "snowfield", "alpine snowfield", "alpine snowfield · lingering high-elevation snow"], [0.3, 0.22, 0.3, 0.22]),
|
||||
static_label("detected.summit", 4, ["peak", "summit", "alpine summit", "alpine summit · above tree line, snow-streaked granite"], [0.25, 0.20, 0.5, 0.5]),
|
||||
static_label("detected.tundra", 2, ["meadow", "alpine tundra", "alpine tundra", "alpine tundra · low cushion plants above the trees"], [0.10, 0.62, 0.8, 0.3]),
|
||||
],
|
||||
# ---------- coast ----------
|
||||
"coast_kelp": [
|
||||
static_label("detected.pneumatocyst", 1, ["floats", "gas bladders", "pneumatocysts", "pneumatocysts · gas floats lifting the blades to light"], [0.4, 0.18, 0.2, 0.25]),
|
||||
static_label("detected.kelp", 4, ["kelp", "giant kelp", "Macrocystis pyrifera", "Macrocystis · can grow ~0.5 m a day toward the light"], [0.20, 0.10, 0.6, 0.8]),
|
||||
static_label("detected.frond", 2, ["leaf", "blade", "kelp frond", "frond · gas-filled floats hold it upright"], [0.40, 0.30, 0.2, 0.4]),
|
||||
],
|
||||
"coast_otters": [
|
||||
static_label("detected.fur", 1, ["fur", "dense fur", "the densest fur", "densest fur on Earth · ~1M hairs/in², no blubber"], [0.34, 0.38, 0.3, 0.25]),
|
||||
static_label("detected.otter", 4, ["otter", "sea otter", "Enhydra lutris", "Enhydra lutris · eats ~25% of its weight a day to stay warm"], [0.30, 0.35, 0.4, 0.35]),
|
||||
static_label("detected.water", 2, ["water", "estuary", "tidal slough", "tidal slough · sheltered nursery water"], [0.05, 0.05, 0.9, 0.25]),
|
||||
],
|
||||
"coast_kalaloch": [
|
||||
static_label("detected.sunset", 2, ["glow", "sunset", "golden hour", "golden hour · low sun reddened through more air"], [0.05, 0.05, 0.9, 0.22]),
|
||||
static_label("detected.surf", 4, ["waves", "surf", "breaking swell", "breaking swell · long-period Pacific swell"], [0.15, 0.50, 0.7, 0.35]),
|
||||
static_label("detected.searock", 2, ["rock", "sea stack", "coastal sea stack", "sea stack · wave-cut residual rock"], [0.30, 0.25, 0.3, 0.3]),
|
||||
],
|
||||
"coast_seals": [
|
||||
static_label("detected.whiskers", 1, ["whiskers", "vibrissae", "sensing whiskers", "vibrissae · whiskers that feel prey in murky water"], [0.3, 0.42, 0.2, 0.15]),
|
||||
static_label("detected.seal", 4, ["seals", "harbor seals", "Phoca vitulina", "Phoca vitulina · hauls out on rock to rest and warm"], [0.20, 0.40, 0.6, 0.35]),
|
||||
static_label("detected.searock", 2, ["rock", "haul-out rock", "intertidal rock", "haul-out · a tide-washed resting ledge"], [0.05, 0.6, 0.9, 0.3]),
|
||||
],
|
||||
"coast_mist": [
|
||||
static_label("detected.swell", 2, ["waves", "swell", "ocean swell", "ocean swell · wind-built waves from distant storms"], [0.1, 0.58, 0.8, 0.25]),
|
||||
static_label("detected.mist", 4, ["mist", "sea mist", "advection fog", "advection fog · warm air cooling over cold upwelling"], [0.05, 0.10, 0.9, 0.5]),
|
||||
static_label("detected.searock", 2, ["rock", "shore rock", "coastal rock", "coastal rock · the standing edge of the land"], [0.20, 0.55, 0.6, 0.35]),
|
||||
],
|
||||
@@ -519,19 +542,43 @@ LABELS: dict[str, list[dict]] = {
|
||||
],
|
||||
# ---------- abyss ----------
|
||||
"abyss_bigfin": [
|
||||
static_label("detected.squid", 4, ["squid", "bigfin squid", "Magnapinna", "Magnapinna · elbowed arms trailing meters into the dark"], [0.30, 0.20, 0.4, 0.65]),
|
||||
static_label("detected.arms", 2, ["arms", "elbowed arms", "trailing filaments", "elbowed arms · held out, then trailing meters of thread"], [0.35, 0.5, 0.3, 0.4]),
|
||||
tracked_label(
|
||||
"detected.squid", 4,
|
||||
["squid", "bigfin squid", "Magnapinna", "Magnapinna · elbowed arms trailing meters into the dark"],
|
||||
0.0, 1.0,
|
||||
[(0.0, [0.3, 0.36, 0.28, 0.3]), (0.5, [0.42, 0.22, 0.26, 0.28]), (1.0, [0.55, 0.1, 0.26, 0.3])],
|
||||
),
|
||||
measure("measure.depth", "−2,000 m", [0.06, 0.06, 0.16, 0.08], 2),
|
||||
],
|
||||
"abyss_dandelion": [
|
||||
static_label("detected.siphonophore", 4, ["orb", "siphonophore", "dandelion siphonophore", "Rhodaliidae · a colony of clones tethered to the seabed"], [0.32, 0.28, 0.36, 0.4]),
|
||||
static_label("detected.tentacle", 2, ["threads", "tentacles", "feeding tentacles", "feeding tentacles · a drifting net for prey"], [0.3, 0.45, 0.4, 0.3]),
|
||||
tracked_label(
|
||||
"detected.siphonophore", 4,
|
||||
["orb", "siphonophore", "dandelion siphonophore", "Rhodaliidae · a colony of clones tethered to the seabed"],
|
||||
0.0, 1.0,
|
||||
[(0.0, [0.38, 0.28, 0.26, 0.36]), (0.5, [0.3, 0.2, 0.34, 0.46]), (1.0, [0.18, 0.1, 0.45, 0.7])],
|
||||
),
|
||||
measure("measure.depth", "−2,500 m", [0.06, 0.06, 0.16, 0.08], 2),
|
||||
],
|
||||
"abyss_octopus": [
|
||||
static_label("detected.octopus", 4, ["octopus", "deep-sea octopus", "Graneledone", "Graneledone boreopacifica · broods its eggs for ~4.5 years"], [0.25, 0.30, 0.5, 0.45]),
|
||||
static_label("detected.arms", 2, ["arms", "eight arms", "sucker-lined arms", "eight arms · sucker-lined, tasting what they touch"], [0.25, 0.45, 0.5, 0.3]),
|
||||
tracked_label(
|
||||
"detected.octopus", 4,
|
||||
["octopus", "deep-sea octopus", "Graneledone", "Graneledone boreopacifica · broods its eggs for ~4.5 years"],
|
||||
0.0, 1.0,
|
||||
[(0.0, [0.5, 0.1, 0.35, 0.55]), (0.5, [0.42, 0.16, 0.36, 0.52]), (1.0, [0.05, 0.02, 0.92, 0.95])],
|
||||
),
|
||||
measure("measure.depth", "−2,500 m", [0.06, 0.06, 0.16, 0.08], 2),
|
||||
],
|
||||
"abyss_seapig": [
|
||||
static_label("detected.seacucumber", 4, ["blob", "sea cucumber", "Enypniastes eximia", "Enypniastes · a swimming sea cucumber, the “headless chicken”"], [0.30, 0.30, 0.4, 0.4]),
|
||||
static_label("detected.veil", 2, ["veil", "oral veil", "swimming veil", "oral veil · sweeps sediment, and flaps to swim"], [0.3, 0.3, 0.35, 0.25]),
|
||||
tracked_label(
|
||||
"detected.seacucumber", 4,
|
||||
["blob", "sea cucumber", "Enypniastes eximia", "Enypniastes · a swimming sea cucumber, the “headless chicken”"],
|
||||
0.0, 1.0,
|
||||
[(0.0, [0.58, 0.38, 0.18, 0.3]), (0.5, [0.46, 0.32, 0.19, 0.32]), (1.0, [0.38, 0.28, 0.2, 0.36])],
|
||||
),
|
||||
measure("measure.depth", "−2,700 m", [0.06, 0.06, 0.16, 0.08], 2),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -164,6 +164,38 @@ test("a landed clip loops from the morph-tail offset (no jump-back to frame 0)",
|
||||
expect(v.t).toBeGreaterThanOrEqual(2.5); // looping from ~3s, not 0
|
||||
});
|
||||
|
||||
test("ascending re-lands anchored at the clip head (no reverse-landing jump)", async ({ page }) => {
|
||||
// D3: a morph spans src@0 (frac 0) -> dst@~3s (frac 1). Descending lands on dst at its
|
||||
// tail (~3s); ASCENDING lands on src at its HEAD (~0s). The steady loop must continue
|
||||
// from 0 there — seeking to the tail (~3s) was the ~3s reverse-landing jump.
|
||||
await boot(page);
|
||||
await enableVideo(page); // base loop only loads when video is shown
|
||||
// Descend one altitude (cosmos -> orbit), then ascend back (orbit -> cosmos): the
|
||||
// ascent is the reverse landing under test.
|
||||
const start = (await page.locator("#scale-name").textContent())!;
|
||||
await wheelOnStage(page, 60); // descend
|
||||
await page.waitForFunction((s) => document.querySelector("#scale-name")?.textContent !== s, start, { timeout: 20000 });
|
||||
const mid = (await page.locator("#scale-name").textContent())!;
|
||||
expect(mid).toContain("orbit");
|
||||
await wheelOnStage(page, -60); // ascend back
|
||||
await page.waitForFunction((s) => document.querySelector("#scale-name")?.textContent !== s, mid, { timeout: 20000 });
|
||||
expect(await page.locator("#scale-name").textContent()).toContain("cosmos");
|
||||
// The loop element is now anchored at the HEAD (loopStart "0"), playing — not jumped to
|
||||
// the ~3s tail the descending case uses.
|
||||
await page.waitForFunction(
|
||||
() => { const v = document.querySelector("#vid-loop") as HTMLVideoElement; return v.dataset.loopTail === "1" && v.dataset.loopStart === "0"; },
|
||||
null,
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
const v = await page.evaluate(() => {
|
||||
const el = document.querySelector("#vid-loop") as HTMLVideoElement;
|
||||
return { loopStart: el.dataset.loopStart, loopTail: el.dataset.loopTail, paused: el.paused };
|
||||
});
|
||||
expect(v.loopTail).toBe("1"); // tail-loop machinery armed
|
||||
expect(v.loopStart).toBe("0"); // anchored at the head, not the morph tail
|
||||
expect(v.paused).toBe(false); // the loop is actually playing
|
||||
});
|
||||
|
||||
test("zoom in plays the matching morph, lands locked, and stays put while parked", async ({ page }) => {
|
||||
await boot(page);
|
||||
const start = (await page.locator("#scale-name").textContent())!;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+49
-20
@@ -238,20 +238,42 @@ function preloadChip() {
|
||||
// to the loop's first frame, and the morph-covered intro never re-shows at rest.
|
||||
const LOOP_TAIL_S = 3.0;
|
||||
|
||||
// Load a clip's base into the LOOP element (#vid-loop), seeked to the tail and PAUSED
|
||||
// on that exact frame. Idempotent per clip. Called during a scrub to PRELOAD the clip
|
||||
// we're about to land on, so settle just plays an already-decoded element at the morph's
|
||||
// last frame — no reload, no seek, no stall. `playLoop()` starts it at landing.
|
||||
function loadLoop(clip) {
|
||||
if (!clip || loopVid.dataset.clip === clip.id) return;
|
||||
// The frame the current loop both LANDS on and WRAPS back to — stored per load so the
|
||||
// wrap handlers honor a direction-aware landing (forward=LOOP_TAIL_S, reverse=0).
|
||||
function loopStartFrame() {
|
||||
const s = parseFloat(loopVid.dataset.loopStart);
|
||||
return Number.isFinite(s) ? s : LOOP_TAIL_S;
|
||||
}
|
||||
|
||||
// Load a clip's base into the LOOP element (#vid-loop), seeked to `landFrame` and PAUSED
|
||||
// on that exact frame. `landFrame` is the frame the morph last showed for THIS landing:
|
||||
// LOOP_TAIL_S when descending (lands on the morph's dst-tail), 0 when ascending (lands on
|
||||
// the morph's src-head — fixes the D3 reverse-landing jump). Omitted → LOOP_TAIL_S, the
|
||||
// forward default for non-scrub paths (initial load, knob changes). Idempotent per clip;
|
||||
// an explicit `landFrame` re-seeks the already-loaded clip if the travel direction flipped
|
||||
// the landing phase, but a bare call (ensureClipMedia) never clobbers a phase a scrub set.
|
||||
// Called during a scrub to PRELOAD the clip we're about to land on, so settle just plays
|
||||
// an already-decoded element at the right frame — no reload, no seek, no stall.
|
||||
function loadLoop(clip, landFrame) {
|
||||
if (!clip) return;
|
||||
const seekTo = (t) => { try { loopVid.currentTime = t; } catch (e) { /* not seekable yet */ } };
|
||||
if (loopVid.dataset.clip === clip.id) {
|
||||
if (landFrame != null && loopVid.dataset.loopStart !== String(landFrame)) {
|
||||
loopVid.dataset.loopStart = String(landFrame);
|
||||
if (loopVid.readyState >= 1) seekTo(landFrame);
|
||||
else loopVid.addEventListener("loadedmetadata", () => seekTo(landFrame), { once: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const lf = landFrame == null ? LOOP_TAIL_S : landFrame;
|
||||
loopVid.dataset.clip = clip.id;
|
||||
loopVid.dataset.loopTail = "1"; // arm the [LOOP_TAIL_S, duration] wrap handler
|
||||
loopVid.dataset.loopTail = "1"; // arm the [loopStart, duration] wrap handler
|
||||
loopVid.dataset.loopStart = String(lf);
|
||||
loopVid.src = mediaUrl(clip.base_file);
|
||||
loopVid.loop = false; // native loop restarts at 0; we loop from the tail instead
|
||||
loopVid.loop = false; // native loop restarts at 0; we loop from loopStart
|
||||
loopVid.muted = true;
|
||||
const seek = () => { try { loopVid.currentTime = LOOP_TAIL_S; } catch (e) { /* not seekable yet */ } };
|
||||
if (loopVid.readyState >= 1) seek();
|
||||
else loopVid.addEventListener("loadedmetadata", seek, { once: true });
|
||||
if (loopVid.readyState >= 1) seekTo(lf);
|
||||
else loopVid.addEventListener("loadedmetadata", () => seekTo(lf), { once: true });
|
||||
}
|
||||
|
||||
function playLoop() {
|
||||
@@ -267,21 +289,22 @@ function ensureClipMedia() {
|
||||
}
|
||||
|
||||
// The custom loop on the LOOP element: when a base loop reaches the end, jump back to
|
||||
// the tail offset (not 0). The morph element (#vid) is driven directly by the scrub.
|
||||
// its landing offset (LOOP_TAIL_S forward, 0 reverse — see loopStartFrame), not a fixed
|
||||
// tail. The morph element (#vid) is driven directly by the scrub.
|
||||
loopVid.addEventListener("timeupdate", () => {
|
||||
if (loopVid.dataset.loopTail === "1" && loopVid.duration && loopVid.currentTime >= loopVid.duration - 0.08) {
|
||||
try { loopVid.currentTime = LOOP_TAIL_S; } catch (e) { /* not seekable */ }
|
||||
try { loopVid.currentTime = loopStartFrame(); } catch (e) { /* not seekable */ }
|
||||
}
|
||||
});
|
||||
|
||||
// Safety net: the timeupdate wrap above is a narrow window (the last 0.08 s). Under
|
||||
// real-browser decode/GPU load a `timeupdate` can skip past it, the clip fires
|
||||
// `ended`, and — with native loop off — freezes on its last frame ("not looping").
|
||||
// `ended` GUARANTEES recovery: seek back to the tail and resume. Headless rarely
|
||||
// hits this; loaded real machines do.
|
||||
// `ended` GUARANTEES recovery: seek back to the landing offset and resume. Headless
|
||||
// rarely hits this; loaded real machines do.
|
||||
loopVid.addEventListener("ended", () => {
|
||||
if (loopVid.dataset.loopTail !== "1") return;
|
||||
try { loopVid.currentTime = LOOP_TAIL_S; } catch (e) { /* not seekable */ }
|
||||
try { loopVid.currentTime = loopStartFrame(); } catch (e) { /* not seekable */ }
|
||||
loopVid.play().catch(() => {});
|
||||
});
|
||||
|
||||
@@ -835,7 +858,12 @@ function setPos(next) {
|
||||
busy = false;
|
||||
delete vid.dataset.morph; // clear so re-entering the same segment reloads the morph (not the base)
|
||||
currentClipId = null; // force ensureClipMedia to reload + loop the locked clip
|
||||
playLoop(); // the loop element was preloaded to this clip @ tail during the scrub → instant, no reload
|
||||
// Land the loop on the frame the morph last showed for THIS travel direction:
|
||||
// descending lands on the morph's dst-tail (LOOP_TAIL_S), ascending on its src-head
|
||||
// (0). Explicit (not relying on the scrub's preload) so a reverse landing never
|
||||
// jumps even if the heading clip wasn't warmed in time. (D3 fix.)
|
||||
loadLoop(activeClip(), HEFScrub.loopLandFrame(scrubDir, LOOP_TAIL_S));
|
||||
playLoop(); // the loop element was preloaded to this clip during the scrub → instant, no reload
|
||||
showActiveSource();
|
||||
setNeedle(ringIndex * dialStep());
|
||||
renderScaleReadout();
|
||||
@@ -846,10 +874,11 @@ function setPos(next) {
|
||||
}
|
||||
busy = true; // mid-morph: block update() from reloading the base clip under us
|
||||
if (!activeSeg || activeSeg.lo !== lo) rebuildSegment(lo, ringIndex);
|
||||
// Preload the clip we're heading toward into the LOOP element (paused @ tail) so the
|
||||
// landing is a swap, not a reload+seek. dir>0 lands on clipHi, dir<0 on clipLo.
|
||||
// Preload the clip we're heading toward into the LOOP element (paused on its landing
|
||||
// frame) so the landing is a swap, not a reload+seek. dir>0 lands on clipHi at the
|
||||
// morph's dst-tail (LOOP_TAIL_S); dir<0 lands on clipLo at the morph's src-head (0).
|
||||
const headingId = dir > 0 ? activeSeg.clipHi : activeSeg.clipLo;
|
||||
loadLoop(clipsById[headingId]);
|
||||
loadLoop(clipsById[headingId], HEFScrub.loopLandFrame(dir, LOOP_TAIL_S));
|
||||
showActiveSource(); // morph element is the live source while scrubbing
|
||||
setNeedle(pos * dialStep());
|
||||
blendAudio(lo, frac);
|
||||
|
||||
@@ -29,6 +29,18 @@
|
||||
return { from: 1 - f, to: f };
|
||||
}
|
||||
|
||||
// The base-loop frame a landing must continue from so the steady loop picks up
|
||||
// the EXACT frame the morph last showed — no jump. A morph spans src@0 (frac 0)
|
||||
// -> dst@loopTailS (frac 1), scrubbed bidirectionally. Descending (dir>0) lands
|
||||
// on dst at frac 1, whose frame is dst@loopTailS, so the loop resumes at loopTailS
|
||||
// (and the morph-covered intro never re-shows). Ascending (dir<0) lands on src at
|
||||
// frac 0, whose frame is src@0, so the loop must resume at 0 — seeking to loopTailS
|
||||
// there is the ~3s reverse-landing jump (D3). dir 0 defaults to the forward/tail
|
||||
// case (matches the loadLoop default used by non-scrub paths).
|
||||
function loopLandFrame(dir, loopTailS) {
|
||||
return dir < 0 ? 0 : loopTailS;
|
||||
}
|
||||
|
||||
// Integers strictly crossed moving prevPos -> pos, in travel order. Landing
|
||||
// exactly on an integer counts as crossing it (it commits that altitude).
|
||||
function integerCrossings(prevPos, pos) {
|
||||
@@ -41,5 +53,5 @@
|
||||
return out;
|
||||
}
|
||||
|
||||
return { clamp01, wrapIndex, segmentOf, accumToPos, fracToTime, crossfadeGains, integerCrossings };
|
||||
return { clamp01, wrapIndex, segmentOf, accumToPos, fracToTime, crossfadeGains, loopLandFrame, integerCrossings };
|
||||
});
|
||||
|
||||
@@ -58,3 +58,17 @@ test("integerCrossings: multiple crossings in travel order", () => {
|
||||
assert.deepEqual(S.integerCrossings(2.4, 4.1), [{ index: 3, dir: 1 }, { index: 4, dir: 1 }]);
|
||||
assert.deepEqual(S.integerCrossings(3.2, 1.8), [{ index: 3, dir: -1 }, { index: 2, dir: -1 }]);
|
||||
});
|
||||
|
||||
test("loopLandFrame: descend lands on the morph's tail frame, ascend on its head", () => {
|
||||
// A morph spans src@0 (frac 0) -> dst@loopTailS (frac 1). Descending (dir>0) lands
|
||||
// on dst at frac 1 -> the base loop must continue from loopTailS. Ascending (dir<0)
|
||||
// lands on src at frac 0 -> the loop must continue from 0 (NOT loopTailS — that was
|
||||
// the D3 reverse-landing jump).
|
||||
assert.equal(S.loopLandFrame(1, 3), 3); // descend -> tail
|
||||
assert.equal(S.loopLandFrame(-1, 3), 0); // ascend -> head
|
||||
// dir 0 (no travel) is treated as a forward/tail landing (the default everywhere else).
|
||||
assert.equal(S.loopLandFrame(0, 3), 3);
|
||||
// honors any tail value
|
||||
assert.equal(S.loopLandFrame(1, 2.5), 2.5);
|
||||
assert.equal(S.loopLandFrame(-1, 2.5), 0);
|
||||
});
|
||||
|
||||
+146
-1
@@ -69,7 +69,8 @@
|
||||
"longing",
|
||||
"yearning",
|
||||
"an aching yearning"
|
||||
]
|
||||
],
|
||||
"measure.count": "~2T galaxies"
|
||||
},
|
||||
"cosmos_orion": {
|
||||
"detected.nebula": [
|
||||
@@ -312,6 +313,18 @@
|
||||
"terrestrial planet",
|
||||
"terrestrial planet · 12,742 km across"
|
||||
],
|
||||
"detected.cloud_band": [
|
||||
"clouds",
|
||||
"weather systems",
|
||||
"cloud systems",
|
||||
"global cloud systems · clouds cover ~67% of Earth"
|
||||
],
|
||||
"detected.starfield": [
|
||||
"stars",
|
||||
"starfield",
|
||||
"background stars",
|
||||
"background starfield · a rendered backdrop, not to scale"
|
||||
],
|
||||
"feel.serenity": [
|
||||
"calm",
|
||||
"serenity",
|
||||
@@ -529,6 +542,12 @@
|
||||
"vertigo",
|
||||
"giddiness",
|
||||
"a giddy, falling vertigo"
|
||||
],
|
||||
"detected.butte": [
|
||||
"tower",
|
||||
"butte",
|
||||
"rock temple",
|
||||
"rock ‘temple’ · an erosional butte left standing"
|
||||
]
|
||||
},
|
||||
"sky_greenland_landice": {
|
||||
@@ -567,6 +586,12 @@
|
||||
"vertigo",
|
||||
"giddiness",
|
||||
"a giddy, falling vertigo"
|
||||
],
|
||||
"detected.crevasse": [
|
||||
"cracks",
|
||||
"crevasses",
|
||||
"glacial crevasses",
|
||||
"crevasses · the ice fracturing as it flows"
|
||||
]
|
||||
},
|
||||
"sky_greenland_suture": {
|
||||
@@ -605,6 +630,12 @@
|
||||
"vertigo",
|
||||
"giddiness",
|
||||
"a giddy, falling vertigo"
|
||||
],
|
||||
"detected.floe": [
|
||||
"plates",
|
||||
"ice floes",
|
||||
"pack-ice floes",
|
||||
"pack-ice floes · drifting plates of frozen sea"
|
||||
]
|
||||
},
|
||||
"sky_jungle_amazon": {
|
||||
@@ -643,6 +674,12 @@
|
||||
"vertigo",
|
||||
"giddiness",
|
||||
"a giddy, falling vertigo"
|
||||
],
|
||||
"detected.mist": [
|
||||
"haze",
|
||||
"canopy mist",
|
||||
"transpiration haze",
|
||||
"transpiration haze · the forest exhaling water vapor"
|
||||
]
|
||||
},
|
||||
"sky_jungle_waterfall": {
|
||||
@@ -681,6 +718,12 @@
|
||||
"vertigo",
|
||||
"giddiness",
|
||||
"a giddy, falling vertigo"
|
||||
],
|
||||
"detected.spray": [
|
||||
"mist",
|
||||
"spray",
|
||||
"plunge spray",
|
||||
"plunge spray · the river aerosolized on impact"
|
||||
]
|
||||
},
|
||||
"sky_coast_cliffspain": {
|
||||
@@ -719,6 +762,12 @@
|
||||
"vertigo",
|
||||
"giddiness",
|
||||
"a giddy, falling vertigo"
|
||||
],
|
||||
"detected.headland": [
|
||||
"point",
|
||||
"headland",
|
||||
"rock promontory",
|
||||
"promontory · a cliffed arm of land into the sea"
|
||||
]
|
||||
},
|
||||
"sky_mtn_castlecrags": {
|
||||
@@ -757,6 +806,12 @@
|
||||
"vertigo",
|
||||
"giddiness",
|
||||
"a giddy, falling vertigo"
|
||||
],
|
||||
"detected.talus": [
|
||||
"scree",
|
||||
"talus",
|
||||
"talus slope",
|
||||
"talus · frost-shattered rock piled below the crags"
|
||||
]
|
||||
},
|
||||
"sky_mtn_rocky": {
|
||||
@@ -795,6 +850,12 @@
|
||||
"vertigo",
|
||||
"giddiness",
|
||||
"a giddy, falling vertigo"
|
||||
],
|
||||
"detected.snowfield": [
|
||||
"snow",
|
||||
"snowfield",
|
||||
"alpine snowfield",
|
||||
"alpine snowfield · lingering high-elevation snow"
|
||||
]
|
||||
},
|
||||
"coast_birdrock": {
|
||||
@@ -833,6 +894,12 @@
|
||||
"melancholy",
|
||||
"longing",
|
||||
"a soft seaward longing"
|
||||
],
|
||||
"detected.seabirds": [
|
||||
"birds",
|
||||
"seabirds",
|
||||
"nesting seabirds",
|
||||
"seabird colony · the rookery that names the rock"
|
||||
]
|
||||
},
|
||||
"coast_surfgrass": {
|
||||
@@ -871,6 +938,12 @@
|
||||
"melancholy",
|
||||
"longing",
|
||||
"a soft seaward longing"
|
||||
],
|
||||
"detected.tidepool": [
|
||||
"pool",
|
||||
"tidepool",
|
||||
"intertidal pool",
|
||||
"tidepool · a pocket sea bared at low tide"
|
||||
]
|
||||
},
|
||||
"coast_kelp": {
|
||||
@@ -909,6 +982,12 @@
|
||||
"melancholy",
|
||||
"longing",
|
||||
"a soft seaward longing"
|
||||
],
|
||||
"detected.pneumatocyst": [
|
||||
"floats",
|
||||
"gas bladders",
|
||||
"pneumatocysts",
|
||||
"pneumatocysts · gas floats lifting the blades to light"
|
||||
]
|
||||
},
|
||||
"coast_otters": {
|
||||
@@ -947,6 +1026,12 @@
|
||||
"melancholy",
|
||||
"longing",
|
||||
"a soft seaward longing"
|
||||
],
|
||||
"detected.fur": [
|
||||
"fur",
|
||||
"dense fur",
|
||||
"the densest fur",
|
||||
"densest fur on Earth · ~1M hairs/in², no blubber"
|
||||
]
|
||||
},
|
||||
"coast_kalaloch": {
|
||||
@@ -985,6 +1070,12 @@
|
||||
"melancholy",
|
||||
"longing",
|
||||
"a soft seaward longing"
|
||||
],
|
||||
"detected.sunset": [
|
||||
"glow",
|
||||
"sunset",
|
||||
"golden hour",
|
||||
"golden hour · low sun reddened through more air"
|
||||
]
|
||||
},
|
||||
"coast_seals": {
|
||||
@@ -1023,6 +1114,12 @@
|
||||
"melancholy",
|
||||
"longing",
|
||||
"a soft seaward longing"
|
||||
],
|
||||
"detected.whiskers": [
|
||||
"whiskers",
|
||||
"vibrissae",
|
||||
"sensing whiskers",
|
||||
"vibrissae · whiskers that feel prey in murky water"
|
||||
]
|
||||
},
|
||||
"coast_mist": {
|
||||
@@ -1061,6 +1158,12 @@
|
||||
"melancholy",
|
||||
"longing",
|
||||
"a soft seaward longing"
|
||||
],
|
||||
"detected.swell": [
|
||||
"waves",
|
||||
"swell",
|
||||
"ocean swell",
|
||||
"ocean swell · wind-built waves from distant storms"
|
||||
]
|
||||
},
|
||||
"reef_lionfish": {
|
||||
@@ -1172,6 +1275,12 @@
|
||||
"immersion",
|
||||
"absorption",
|
||||
"a held, breathless absorption"
|
||||
],
|
||||
"detected.coral": [
|
||||
"coral",
|
||||
"reef coral",
|
||||
"hard coral",
|
||||
"hard coral · the reef this fish grazes into sand"
|
||||
]
|
||||
},
|
||||
"reef_coralspacific": {
|
||||
@@ -1210,6 +1319,12 @@
|
||||
"immersion",
|
||||
"absorption",
|
||||
"a held, breathless absorption"
|
||||
],
|
||||
"detected.polyp": [
|
||||
"polyps",
|
||||
"coral polyps",
|
||||
"living polyps",
|
||||
"polyps · each a tiny animal, the colony’s builders"
|
||||
]
|
||||
},
|
||||
"reef_redsea": {
|
||||
@@ -1399,6 +1514,12 @@
|
||||
"dread",
|
||||
"foreboding",
|
||||
"a slow, cold foreboding"
|
||||
],
|
||||
"detected.marinesnow": [
|
||||
"specks",
|
||||
"marine snow",
|
||||
"falling detritus",
|
||||
"marine snow · organic debris drifting down from above"
|
||||
]
|
||||
},
|
||||
"abyss_bigfin": {
|
||||
@@ -1432,6 +1553,12 @@
|
||||
"dread",
|
||||
"foreboding",
|
||||
"a slow, cold foreboding"
|
||||
],
|
||||
"detected.arms": [
|
||||
"arms",
|
||||
"elbowed arms",
|
||||
"trailing filaments",
|
||||
"elbowed arms · held out, then trailing meters of thread"
|
||||
]
|
||||
},
|
||||
"abyss_dandelion": {
|
||||
@@ -1465,6 +1592,12 @@
|
||||
"dread",
|
||||
"foreboding",
|
||||
"a slow, cold foreboding"
|
||||
],
|
||||
"detected.tentacle": [
|
||||
"threads",
|
||||
"tentacles",
|
||||
"feeding tentacles",
|
||||
"feeding tentacles · a drifting net for prey"
|
||||
]
|
||||
},
|
||||
"abyss_octopus": {
|
||||
@@ -1498,6 +1631,12 @@
|
||||
"dread",
|
||||
"foreboding",
|
||||
"a slow, cold foreboding"
|
||||
],
|
||||
"detected.arms": [
|
||||
"arms",
|
||||
"eight arms",
|
||||
"sucker-lined arms",
|
||||
"eight arms · sucker-lined, tasting what they touch"
|
||||
]
|
||||
},
|
||||
"abyss_seapig": {
|
||||
@@ -1531,6 +1670,12 @@
|
||||
"dread",
|
||||
"foreboding",
|
||||
"a slow, cold foreboding"
|
||||
],
|
||||
"detected.veil": [
|
||||
"veil",
|
||||
"oral veil",
|
||||
"swimming veil",
|
||||
"oral veil · sweeps sediment, and flaps to swim"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+150
-5
@@ -69,7 +69,8 @@
|
||||
"anhelo",
|
||||
"añoranza",
|
||||
"una añoranza punzante"
|
||||
]
|
||||
],
|
||||
"measure.count": "~2 billones de galaxias"
|
||||
},
|
||||
"cosmos_orion": {
|
||||
"feel.wonder": [
|
||||
@@ -312,6 +313,18 @@
|
||||
"planeta terrestre",
|
||||
"planeta terrestre · 12.742 km de diámetro"
|
||||
],
|
||||
"detected.cloud_band": [
|
||||
"nubes",
|
||||
"sistemas meteorológicos",
|
||||
"sistemas nubosos",
|
||||
"sistemas nubosos globales · las nubes cubren ~67% de la Tierra"
|
||||
],
|
||||
"detected.starfield": [
|
||||
"estrellas",
|
||||
"campo estelar",
|
||||
"estrellas de fondo",
|
||||
"campo estelar de fondo · un telón renderizado, sin escala"
|
||||
],
|
||||
"feel.serenity": [
|
||||
"calma",
|
||||
"serenidad",
|
||||
@@ -529,6 +542,12 @@
|
||||
"estratos de roca",
|
||||
"lechos sedimentarios",
|
||||
"estratos · épocas de sedimentación apiladas"
|
||||
],
|
||||
"detected.butte": [
|
||||
"torre",
|
||||
"cerro testigo",
|
||||
"templo de roca",
|
||||
"‘templo’ de roca · un cerro testigo de la erosión"
|
||||
]
|
||||
},
|
||||
"sky_greenland_landice": {
|
||||
@@ -567,6 +586,12 @@
|
||||
"nevero",
|
||||
"neviza",
|
||||
"neviza · nieve vieja que se compacta hacia hielo glaciar"
|
||||
],
|
||||
"detected.crevasse": [
|
||||
"grietas",
|
||||
"grietas",
|
||||
"grietas glaciares",
|
||||
"grietas · el hielo fracturándose al fluir"
|
||||
]
|
||||
},
|
||||
"sky_greenland_suture": {
|
||||
@@ -605,6 +630,12 @@
|
||||
"canal",
|
||||
"canal abierto",
|
||||
"canal · una fractura de agua abierta entre témpanos"
|
||||
],
|
||||
"detected.floe": [
|
||||
"placas",
|
||||
"témpanos",
|
||||
"témpanos de banquisa",
|
||||
"témpanos · placas de mar congelado a la deriva"
|
||||
]
|
||||
},
|
||||
"sky_jungle_amazon": {
|
||||
@@ -643,6 +674,12 @@
|
||||
"árbol alto",
|
||||
"árbol emergente",
|
||||
"emergente · un gigante que rompe sobre el techo del dosel"
|
||||
],
|
||||
"detected.mist": [
|
||||
"bruma",
|
||||
"bruma del dosel",
|
||||
"bruma de transpiración",
|
||||
"bruma de transpiración · el bosque exhalando vapor"
|
||||
]
|
||||
},
|
||||
"sky_jungle_waterfall": {
|
||||
@@ -681,6 +718,12 @@
|
||||
"dosel de la jungla",
|
||||
"dosel selvático",
|
||||
"dosel · bosque denso que abarrota la garganta"
|
||||
],
|
||||
"detected.spray": [
|
||||
"rocío",
|
||||
"aerosol",
|
||||
"rocío de caída",
|
||||
"rocío de caída · el río pulverizado al impactar"
|
||||
]
|
||||
},
|
||||
"sky_coast_cliffspain": {
|
||||
@@ -719,6 +762,12 @@
|
||||
"rompiente",
|
||||
"oleaje rompiente",
|
||||
"oleaje rompiente · el océano que encuentra la piedra"
|
||||
],
|
||||
"detected.headland": [
|
||||
"punta",
|
||||
"promontorio",
|
||||
"promontorio rocoso",
|
||||
"promontorio · un brazo de tierra acantilado en el mar"
|
||||
]
|
||||
},
|
||||
"sky_mtn_castlecrags": {
|
||||
@@ -757,6 +806,12 @@
|
||||
"bosque de coníferas",
|
||||
"bosque montano",
|
||||
"bosque montano · cubriendo las laderas bajo los riscos"
|
||||
],
|
||||
"detected.talus": [
|
||||
"pedrera",
|
||||
"talud",
|
||||
"talud de derrubios",
|
||||
"talud · roca rota por el hielo amontonada bajo los riscos"
|
||||
]
|
||||
},
|
||||
"sky_mtn_rocky": {
|
||||
@@ -795,6 +850,12 @@
|
||||
"tundra alpina",
|
||||
"tundra alpina",
|
||||
"tundra alpina · plantas en cojín bajas sobre los árboles"
|
||||
],
|
||||
"detected.snowfield": [
|
||||
"nieve",
|
||||
"nevero",
|
||||
"nevero alpino",
|
||||
"nevero alpino · nieve persistente de gran altitud"
|
||||
]
|
||||
},
|
||||
"coast_birdrock": {
|
||||
@@ -833,6 +894,12 @@
|
||||
"melancolía",
|
||||
"anhelo",
|
||||
"un suave anhelo hacia el mar"
|
||||
],
|
||||
"detected.seabirds": [
|
||||
"aves",
|
||||
"aves marinas",
|
||||
"aves marinas anidando",
|
||||
"colonia de aves marinas · el criadero que da nombre a la roca"
|
||||
]
|
||||
},
|
||||
"coast_surfgrass": {
|
||||
@@ -871,6 +938,12 @@
|
||||
"melancolía",
|
||||
"anhelo",
|
||||
"un suave anhelo hacia el mar"
|
||||
],
|
||||
"detected.tidepool": [
|
||||
"charca",
|
||||
"poza de marea",
|
||||
"poza intermareal",
|
||||
"poza de marea · un mar en miniatura al bajar la marea"
|
||||
]
|
||||
},
|
||||
"coast_kelp": {
|
||||
@@ -909,6 +982,12 @@
|
||||
"lámina",
|
||||
"fronda de alga",
|
||||
"fronda · flotadores llenos de gas la mantienen erguida"
|
||||
],
|
||||
"detected.pneumatocyst": [
|
||||
"flotadores",
|
||||
"vejigas de gas",
|
||||
"neumatocistos",
|
||||
"neumatocistos · flotadores de gas que alzan las hojas a la luz"
|
||||
]
|
||||
},
|
||||
"coast_otters": {
|
||||
@@ -947,6 +1026,12 @@
|
||||
"estuario",
|
||||
"estero de marea",
|
||||
"estero de marea · aguas resguardadas de crianza"
|
||||
],
|
||||
"detected.fur": [
|
||||
"pelaje",
|
||||
"pelaje denso",
|
||||
"el pelaje más denso",
|
||||
"el pelaje más denso de la Tierra · ~1 M de pelos/in², sin grasa"
|
||||
]
|
||||
},
|
||||
"coast_kalaloch": {
|
||||
@@ -985,6 +1070,12 @@
|
||||
"farallón",
|
||||
"farallón costero",
|
||||
"farallón · roca residual tallada por las olas"
|
||||
],
|
||||
"detected.sunset": [
|
||||
"resplandor",
|
||||
"atardecer",
|
||||
"hora dorada",
|
||||
"hora dorada · el sol bajo enrojecido por más atmósfera"
|
||||
]
|
||||
},
|
||||
"coast_seals": {
|
||||
@@ -1023,6 +1114,12 @@
|
||||
"roca de descanso",
|
||||
"roca intermareal",
|
||||
"descansadero · una repisa de descanso bañada por la marea"
|
||||
],
|
||||
"detected.whiskers": [
|
||||
"bigotes",
|
||||
"vibrisas",
|
||||
"bigotes sensores",
|
||||
"vibrisas · bigotes que detectan presas en agua turbia"
|
||||
]
|
||||
},
|
||||
"coast_mist": {
|
||||
@@ -1061,6 +1158,12 @@
|
||||
"roca de orilla",
|
||||
"roca costera",
|
||||
"roca costera · el borde firme de la tierra"
|
||||
],
|
||||
"detected.swell": [
|
||||
"olas",
|
||||
"mar de fondo",
|
||||
"oleaje de fondo",
|
||||
"mar de fondo · olas formadas por tormentas lejanas"
|
||||
]
|
||||
},
|
||||
"reef_lionfish": {
|
||||
@@ -1172,6 +1275,12 @@
|
||||
"inmersión",
|
||||
"absorción",
|
||||
"una absorción contenida y sin aliento"
|
||||
],
|
||||
"detected.coral": [
|
||||
"coral",
|
||||
"coral de arrecife",
|
||||
"coral duro",
|
||||
"coral duro · el arrecife que este pez muele en arena"
|
||||
]
|
||||
},
|
||||
"reef_coralspacific": {
|
||||
@@ -1210,6 +1319,12 @@
|
||||
"inmersión",
|
||||
"absorción",
|
||||
"una absorción contenida y sin aliento"
|
||||
],
|
||||
"detected.polyp": [
|
||||
"pólipos",
|
||||
"pólipos de coral",
|
||||
"pólipos vivos",
|
||||
"pólipos · cada uno un animal diminuto, constructores de la colonia"
|
||||
]
|
||||
},
|
||||
"reef_redsea": {
|
||||
@@ -1399,6 +1514,12 @@
|
||||
"pavor",
|
||||
"presagio",
|
||||
"un presagio lento y frío"
|
||||
],
|
||||
"detected.marinesnow": [
|
||||
"motas",
|
||||
"nieve marina",
|
||||
"detrito que cae",
|
||||
"nieve marina · restos orgánicos que descienden desde arriba"
|
||||
]
|
||||
},
|
||||
"abyss_bigfin": {
|
||||
@@ -1432,7 +1553,13 @@
|
||||
"Magnapinna",
|
||||
"Magnapinna · brazos acodados que se extienden metros en la oscuridad"
|
||||
],
|
||||
"measure.depth": "−2.000 m"
|
||||
"measure.depth": "−2.000 m",
|
||||
"detected.arms": [
|
||||
"brazos",
|
||||
"brazos acodados",
|
||||
"filamentos colgantes",
|
||||
"brazos acodados · extendidos y luego colgando metros de hilo"
|
||||
]
|
||||
},
|
||||
"abyss_dandelion": {
|
||||
"feel.unease": [
|
||||
@@ -1465,7 +1592,13 @@
|
||||
"sifonóforo diente de león",
|
||||
"Rhodaliidae · una colonia de clones anclada al lecho marino"
|
||||
],
|
||||
"measure.depth": "−2.500 m"
|
||||
"measure.depth": "−2.500 m",
|
||||
"detected.tentacle": [
|
||||
"hilos",
|
||||
"tentáculos",
|
||||
"tentáculos de caza",
|
||||
"tentáculos de caza · una red a la deriva para atrapar presas"
|
||||
]
|
||||
},
|
||||
"abyss_octopus": {
|
||||
"feel.unease": [
|
||||
@@ -1498,7 +1631,13 @@
|
||||
"Graneledone",
|
||||
"Graneledone boreopacifica · incuba sus huevos durante ~4,5 años"
|
||||
],
|
||||
"measure.depth": "−2.500 m"
|
||||
"measure.depth": "−2.500 m",
|
||||
"detected.arms": [
|
||||
"brazos",
|
||||
"ocho brazos",
|
||||
"brazos con ventosas",
|
||||
"ocho brazos · con ventosas que saborean lo que tocan"
|
||||
]
|
||||
},
|
||||
"abyss_seapig": {
|
||||
"feel.unease": [
|
||||
@@ -1531,6 +1670,12 @@
|
||||
"Enypniastes eximia",
|
||||
"Enypniastes · un pepino de mar nadador, el “pollo sin cabeza”"
|
||||
],
|
||||
"measure.depth": "−2.700 m"
|
||||
"measure.depth": "−2.700 m",
|
||||
"detected.veil": [
|
||||
"velo",
|
||||
"velo oral",
|
||||
"velo natatorio",
|
||||
"velo oral · barre el sedimento y aletea para nadar"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+150
-5
@@ -69,7 +69,8 @@
|
||||
"désir",
|
||||
"aspiration",
|
||||
"une aspiration douloureuse"
|
||||
]
|
||||
],
|
||||
"measure.count": "~2 000 milliards de galaxies"
|
||||
},
|
||||
"cosmos_orion": {
|
||||
"feel.wonder": [
|
||||
@@ -312,6 +313,18 @@
|
||||
"planète tellurique",
|
||||
"planète tellurique · 12 742 km de diamètre"
|
||||
],
|
||||
"detected.cloud_band": [
|
||||
"nuages",
|
||||
"systèmes météo",
|
||||
"systèmes nuageux",
|
||||
"systèmes nuageux planétaires · les nuages couvrent ~67% de la Terre"
|
||||
],
|
||||
"detected.starfield": [
|
||||
"étoiles",
|
||||
"champ d’étoiles",
|
||||
"étoiles d’arrière-plan",
|
||||
"champ d’étoiles de fond · un décor rendu, hors échelle"
|
||||
],
|
||||
"feel.serenity": [
|
||||
"calme",
|
||||
"sérénité",
|
||||
@@ -529,6 +542,12 @@
|
||||
"strates rocheuses",
|
||||
"bancs sédimentaires",
|
||||
"strates · des époques de dépôt empilées"
|
||||
],
|
||||
"detected.butte": [
|
||||
"tour",
|
||||
"butte-témoin",
|
||||
"temple rocheux",
|
||||
"‘temple’ rocheux · une butte-témoin laissée par l’érosion"
|
||||
]
|
||||
},
|
||||
"sky_greenland_landice": {
|
||||
@@ -567,6 +586,12 @@
|
||||
"champ de neige",
|
||||
"névé",
|
||||
"névé · vieille neige se compactant en glace glaciaire"
|
||||
],
|
||||
"detected.crevasse": [
|
||||
"fissures",
|
||||
"crevasses",
|
||||
"crevasses glaciaires",
|
||||
"crevasses · la glace se fracture en s’écoulant"
|
||||
]
|
||||
},
|
||||
"sky_greenland_suture": {
|
||||
@@ -605,6 +630,12 @@
|
||||
"chenal",
|
||||
"chenal libre",
|
||||
"chenal · une fracture d'eau libre entre les floes"
|
||||
],
|
||||
"detected.floe": [
|
||||
"plaques",
|
||||
"floes",
|
||||
"floes de banquise",
|
||||
"floes · plaques de mer gelée à la dérive"
|
||||
]
|
||||
},
|
||||
"sky_jungle_amazon": {
|
||||
@@ -643,6 +674,12 @@
|
||||
"grand arbre",
|
||||
"arbre émergent",
|
||||
"émergent · un géant perçant au-dessus de la canopée"
|
||||
],
|
||||
"detected.mist": [
|
||||
"brume",
|
||||
"brume de canopée",
|
||||
"brume de transpiration",
|
||||
"brume de transpiration · la forêt exhalant sa vapeur"
|
||||
]
|
||||
},
|
||||
"sky_jungle_waterfall": {
|
||||
@@ -681,6 +718,12 @@
|
||||
"canopée de jungle",
|
||||
"canopée de forêt tropicale",
|
||||
"canopée · forêt dense pressée autour de la gorge"
|
||||
],
|
||||
"detected.spray": [
|
||||
"embruns",
|
||||
"embruns",
|
||||
"embruns de chute",
|
||||
"embruns de chute · la rivière pulvérisée à l’impact"
|
||||
]
|
||||
},
|
||||
"sky_coast_cliffspain": {
|
||||
@@ -719,6 +762,12 @@
|
||||
"ressac",
|
||||
"houle déferlante",
|
||||
"houle déferlante · l'océan à la rencontre de la pierre"
|
||||
],
|
||||
"detected.headland": [
|
||||
"pointe",
|
||||
"promontoire",
|
||||
"promontoire rocheux",
|
||||
"promontoire · un bras de terre escarpé dans la mer"
|
||||
]
|
||||
},
|
||||
"sky_mtn_castlecrags": {
|
||||
@@ -757,6 +806,12 @@
|
||||
"forêt de conifères",
|
||||
"forêt montagnarde",
|
||||
"forêt montagnarde · habillant les pentes sous les pics"
|
||||
],
|
||||
"detected.talus": [
|
||||
"éboulis",
|
||||
"talus",
|
||||
"talus d’éboulis",
|
||||
"éboulis · roches gélifractées entassées sous les aiguilles"
|
||||
]
|
||||
},
|
||||
"sky_mtn_rocky": {
|
||||
@@ -795,6 +850,12 @@
|
||||
"toundra alpine",
|
||||
"toundra alpine",
|
||||
"toundra alpine · plantes en coussin basses au-dessus des arbres"
|
||||
],
|
||||
"detected.snowfield": [
|
||||
"neige",
|
||||
"névé",
|
||||
"névé alpin",
|
||||
"névé alpin · neige persistante de haute altitude"
|
||||
]
|
||||
},
|
||||
"coast_birdrock": {
|
||||
@@ -833,6 +894,12 @@
|
||||
"mélancolie",
|
||||
"langueur",
|
||||
"une douce langueur tournée vers le large"
|
||||
],
|
||||
"detected.seabirds": [
|
||||
"oiseaux",
|
||||
"oiseaux marins",
|
||||
"oiseaux marins nicheurs",
|
||||
"colonie d’oiseaux marins · la rookerie qui nomme le rocher"
|
||||
]
|
||||
},
|
||||
"coast_surfgrass": {
|
||||
@@ -871,6 +938,12 @@
|
||||
"mélancolie",
|
||||
"langueur",
|
||||
"une douce langueur tournée vers le large"
|
||||
],
|
||||
"detected.tidepool": [
|
||||
"mare",
|
||||
"cuvette de marée",
|
||||
"mare intertidale",
|
||||
"cuvette de marée · une mer de poche découverte à marée basse"
|
||||
]
|
||||
},
|
||||
"coast_kelp": {
|
||||
@@ -909,6 +982,12 @@
|
||||
"lame",
|
||||
"fronde de varech",
|
||||
"fronde · des flotteurs remplis de gaz la maintiennent droite"
|
||||
],
|
||||
"detected.pneumatocyst": [
|
||||
"flotteurs",
|
||||
"vésicules à gaz",
|
||||
"pneumatocystes",
|
||||
"pneumatocystes · des flotteurs à gaz qui hissent les lames vers la lumière"
|
||||
]
|
||||
},
|
||||
"coast_otters": {
|
||||
@@ -947,6 +1026,12 @@
|
||||
"estuaire",
|
||||
"chenal de marée",
|
||||
"chenal de marée · eaux de nourricerie abritées"
|
||||
],
|
||||
"detected.fur": [
|
||||
"fourrure",
|
||||
"fourrure dense",
|
||||
"la fourrure la plus dense",
|
||||
"la fourrure la plus dense au monde · ~1 M de poils/in², sans graisse"
|
||||
]
|
||||
},
|
||||
"coast_kalaloch": {
|
||||
@@ -985,6 +1070,12 @@
|
||||
"aiguille marine",
|
||||
"aiguille côtière",
|
||||
"aiguille marine · roche résiduelle taillée par les vagues"
|
||||
],
|
||||
"detected.sunset": [
|
||||
"lueur",
|
||||
"coucher de soleil",
|
||||
"heure dorée",
|
||||
"heure dorée · le soleil bas rougi par plus d’atmosphère"
|
||||
]
|
||||
},
|
||||
"coast_seals": {
|
||||
@@ -1023,6 +1114,12 @@
|
||||
"rocher reposoir",
|
||||
"rocher intertidal",
|
||||
"reposoir · une corniche de repos balayée par la marée"
|
||||
],
|
||||
"detected.whiskers": [
|
||||
"moustaches",
|
||||
"vibrisses",
|
||||
"moustaches sensorielles",
|
||||
"vibrisses · des moustaches qui sentent les proies en eau trouble"
|
||||
]
|
||||
},
|
||||
"coast_mist": {
|
||||
@@ -1061,6 +1158,12 @@
|
||||
"rocher de rivage",
|
||||
"rocher côtier",
|
||||
"rocher côtier · le bord dressé de la terre"
|
||||
],
|
||||
"detected.swell": [
|
||||
"vagues",
|
||||
"houle",
|
||||
"houle océanique",
|
||||
"houle · des vagues nées de tempêtes lointaines"
|
||||
]
|
||||
},
|
||||
"reef_lionfish": {
|
||||
@@ -1172,6 +1275,12 @@
|
||||
"immersion",
|
||||
"absorption",
|
||||
"une absorption retenue, à couper le souffle"
|
||||
],
|
||||
"detected.coral": [
|
||||
"corail",
|
||||
"corail récifal",
|
||||
"corail dur",
|
||||
"corail dur · le récif que ce poisson broie en sable"
|
||||
]
|
||||
},
|
||||
"reef_coralspacific": {
|
||||
@@ -1210,6 +1319,12 @@
|
||||
"immersion",
|
||||
"absorption",
|
||||
"une absorption retenue, à couper le souffle"
|
||||
],
|
||||
"detected.polyp": [
|
||||
"polypes",
|
||||
"polypes coralliens",
|
||||
"polypes vivants",
|
||||
"polypes · chacun un animal minuscule, bâtisseurs de la colonie"
|
||||
]
|
||||
},
|
||||
"reef_redsea": {
|
||||
@@ -1399,6 +1514,12 @@
|
||||
"effroi",
|
||||
"pressentiment",
|
||||
"un pressentiment lent et glacé"
|
||||
],
|
||||
"detected.marinesnow": [
|
||||
"points",
|
||||
"neige marine",
|
||||
"détritus tombants",
|
||||
"neige marine · débris organiques descendant des couches supérieures"
|
||||
]
|
||||
},
|
||||
"abyss_bigfin": {
|
||||
@@ -1432,7 +1553,13 @@
|
||||
"Magnapinna",
|
||||
"Magnapinna · des bras coudés traînant sur des mètres dans l'obscurité"
|
||||
],
|
||||
"measure.depth": "−2 000 m"
|
||||
"measure.depth": "−2 000 m",
|
||||
"detected.arms": [
|
||||
"bras",
|
||||
"bras coudés",
|
||||
"filaments traînants",
|
||||
"bras coudés · tendus puis traînant des mètres de fil"
|
||||
]
|
||||
},
|
||||
"abyss_dandelion": {
|
||||
"feel.unease": [
|
||||
@@ -1465,7 +1592,13 @@
|
||||
"siphonophore pissenlit",
|
||||
"Rhodaliidae · une colonie de clones amarrée au fond marin"
|
||||
],
|
||||
"measure.depth": "−2 500 m"
|
||||
"measure.depth": "−2 500 m",
|
||||
"detected.tentacle": [
|
||||
"fils",
|
||||
"tentacules",
|
||||
"tentacules de capture",
|
||||
"tentacules de capture · un filet dérivant pour les proies"
|
||||
]
|
||||
},
|
||||
"abyss_octopus": {
|
||||
"feel.unease": [
|
||||
@@ -1498,7 +1631,13 @@
|
||||
"Graneledone",
|
||||
"Graneledone boreopacifica · couve ses œufs pendant ~4,5 ans"
|
||||
],
|
||||
"measure.depth": "−2 500 m"
|
||||
"measure.depth": "−2 500 m",
|
||||
"detected.arms": [
|
||||
"bras",
|
||||
"huit bras",
|
||||
"bras à ventouses",
|
||||
"huit bras · garnis de ventouses qui goûtent ce qu’elles touchent"
|
||||
]
|
||||
},
|
||||
"abyss_seapig": {
|
||||
"feel.unease": [
|
||||
@@ -1531,6 +1670,12 @@
|
||||
"Enypniastes eximia",
|
||||
"Enypniastes · un concombre de mer nageur, le “poulet sans tête”"
|
||||
],
|
||||
"measure.depth": "−2 700 m"
|
||||
"measure.depth": "−2 700 m",
|
||||
"detected.veil": [
|
||||
"voile",
|
||||
"voile oral",
|
||||
"voile natatoire",
|
||||
"voile oral · balaie le sédiment et bat pour nager"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+150
-5
@@ -69,7 +69,8 @@
|
||||
"憧れ",
|
||||
"切望",
|
||||
"胸を締めつける切望"
|
||||
]
|
||||
],
|
||||
"measure.count": "約2兆個の銀河"
|
||||
},
|
||||
"cosmos_orion": {
|
||||
"feel.wonder": [
|
||||
@@ -312,6 +313,18 @@
|
||||
"岩石惑星",
|
||||
"岩石惑星 · 直径12,742 km"
|
||||
],
|
||||
"detected.cloud_band": [
|
||||
"雲",
|
||||
"気象システム",
|
||||
"雲システム",
|
||||
"地球規模の雲システム · 雲は地表の約67%を覆う"
|
||||
],
|
||||
"detected.starfield": [
|
||||
"星",
|
||||
"星野",
|
||||
"背景の星",
|
||||
"背景の星野 · 描画された背景で実寸ではない"
|
||||
],
|
||||
"feel.serenity": [
|
||||
"穏やか",
|
||||
"静けさ",
|
||||
@@ -529,6 +542,12 @@
|
||||
"岩層",
|
||||
"堆積層",
|
||||
"地層 · 積み重なった堆積の時代"
|
||||
],
|
||||
"detected.butte": [
|
||||
"塔",
|
||||
"残丘",
|
||||
"岩の神殿",
|
||||
"岩の‘神殿’ · 浸食に取り残された残丘"
|
||||
]
|
||||
},
|
||||
"sky_greenland_landice": {
|
||||
@@ -567,6 +586,12 @@
|
||||
"雪原",
|
||||
"フィルン",
|
||||
"フィルン · 氷河の氷へと締まっていく古い雪"
|
||||
],
|
||||
"detected.crevasse": [
|
||||
"亀裂",
|
||||
"クレバス",
|
||||
"氷河の裂け目",
|
||||
"クレバス · 流れる氷が割れてできた裂け目"
|
||||
]
|
||||
},
|
||||
"sky_greenland_suture": {
|
||||
@@ -605,6 +630,12 @@
|
||||
"水路",
|
||||
"リード",
|
||||
"リード · 氷盤の間に開いた水の裂け目"
|
||||
],
|
||||
"detected.floe": [
|
||||
"板状氷",
|
||||
"氷盤",
|
||||
"流氷の氷盤",
|
||||
"氷盤 · 漂う凍った海の板"
|
||||
]
|
||||
},
|
||||
"sky_jungle_amazon": {
|
||||
@@ -643,6 +674,12 @@
|
||||
"高い木",
|
||||
"突出木",
|
||||
"突出木 · 樹冠の天井を突き抜ける巨木"
|
||||
],
|
||||
"detected.mist": [
|
||||
"もや",
|
||||
"林冠のもや",
|
||||
"蒸散のもや",
|
||||
"蒸散のもや · 森が吐き出す水蒸気"
|
||||
]
|
||||
},
|
||||
"sky_jungle_waterfall": {
|
||||
@@ -681,6 +718,12 @@
|
||||
"ジャングルの樹冠",
|
||||
"熱帯雨林の樹冠",
|
||||
"樹冠 · 峡谷に密集する濃い森"
|
||||
],
|
||||
"detected.spray": [
|
||||
"水しぶき",
|
||||
"しぶき",
|
||||
"落下のしぶき",
|
||||
"落下のしぶき · 衝突で霧化した川"
|
||||
]
|
||||
},
|
||||
"sky_coast_cliffspain": {
|
||||
@@ -719,6 +762,12 @@
|
||||
"打ち寄せる波",
|
||||
"砕けるうねり",
|
||||
"砕けるうねり · 海が岩に出会う"
|
||||
],
|
||||
"detected.headland": [
|
||||
"岬",
|
||||
"岬",
|
||||
"岩の岬",
|
||||
"岬 · 海へ突き出す断崖の地"
|
||||
]
|
||||
},
|
||||
"sky_mtn_castlecrags": {
|
||||
@@ -757,6 +806,12 @@
|
||||
"針葉樹林",
|
||||
"山地林",
|
||||
"山地林 · 岩峰の下の斜面を覆う"
|
||||
],
|
||||
"detected.talus": [
|
||||
"岩屑",
|
||||
"タルス",
|
||||
"岩屑の斜面",
|
||||
"タルス · 凍結破砕した岩が岩峰の下に堆積"
|
||||
]
|
||||
},
|
||||
"sky_mtn_rocky": {
|
||||
@@ -795,6 +850,12 @@
|
||||
"ツンドラ",
|
||||
"高山ツンドラ",
|
||||
"高山ツンドラ · 樹林の上に育つ低いクッション状の植物"
|
||||
],
|
||||
"detected.snowfield": [
|
||||
"雪",
|
||||
"雪原",
|
||||
"高山の雪原",
|
||||
"高山の雪原 · 標高の高い場所に残る雪"
|
||||
]
|
||||
},
|
||||
"coast_birdrock": {
|
||||
@@ -833,6 +894,12 @@
|
||||
"憂い",
|
||||
"恋しさ",
|
||||
"海へと向かう柔らかな恋しさ"
|
||||
],
|
||||
"detected.seabirds": [
|
||||
"鳥",
|
||||
"海鳥",
|
||||
"営巣する海鳥",
|
||||
"海鳥のコロニー · 岩の名の由来となる集団繁殖地"
|
||||
]
|
||||
},
|
||||
"coast_surfgrass": {
|
||||
@@ -871,6 +938,12 @@
|
||||
"憂い",
|
||||
"恋しさ",
|
||||
"海へと向かう柔らかな恋しさ"
|
||||
],
|
||||
"detected.tidepool": [
|
||||
"潮だまり",
|
||||
"タイドプール",
|
||||
"潮間帯の池",
|
||||
"潮だまり · 干潮で現れる小さな海"
|
||||
]
|
||||
},
|
||||
"coast_kelp": {
|
||||
@@ -909,6 +982,12 @@
|
||||
"葉状体",
|
||||
"ケルプの葉",
|
||||
"葉状部 · ガスを含んだ浮き袋が直立させる"
|
||||
],
|
||||
"detected.pneumatocyst": [
|
||||
"浮き",
|
||||
"気胞",
|
||||
"気胞(ニューマトシスト)",
|
||||
"気胞 · 葉を光へ持ち上げるガスの浮き"
|
||||
]
|
||||
},
|
||||
"coast_otters": {
|
||||
@@ -947,6 +1026,12 @@
|
||||
"河口",
|
||||
"潮汐の入り江",
|
||||
"潮の入り江 · 守られた育成の水域"
|
||||
],
|
||||
"detected.fur": [
|
||||
"毛皮",
|
||||
"密な毛皮",
|
||||
"世界一密な毛皮",
|
||||
"世界一密な毛皮 · 約100万本/in²、脂肪層なし"
|
||||
]
|
||||
},
|
||||
"coast_kalaloch": {
|
||||
@@ -985,6 +1070,12 @@
|
||||
"海食柱",
|
||||
"海岸の海食柱",
|
||||
"海食柱 · 波に削り残された岩"
|
||||
],
|
||||
"detected.sunset": [
|
||||
"輝き",
|
||||
"夕焼け",
|
||||
"ゴールデンアワー",
|
||||
"ゴールデンアワー · 大気を長く通り赤らむ低い太陽"
|
||||
]
|
||||
},
|
||||
"coast_seals": {
|
||||
@@ -1023,6 +1114,12 @@
|
||||
"上陸岩",
|
||||
"潮間帯の岩",
|
||||
"上陸地 · 潮に洗われる休息の岩棚"
|
||||
],
|
||||
"detected.whiskers": [
|
||||
"ひげ",
|
||||
"触毛",
|
||||
"感覚のひげ",
|
||||
"触毛 · 濁った水中で獲物を感じ取るひげ"
|
||||
]
|
||||
},
|
||||
"coast_mist": {
|
||||
@@ -1061,6 +1158,12 @@
|
||||
"岸の岩",
|
||||
"海岸の岩",
|
||||
"海岸の岩 · 陸の立つ縁"
|
||||
],
|
||||
"detected.swell": [
|
||||
"波",
|
||||
"うねり",
|
||||
"海のうねり",
|
||||
"うねり · 遠くの嵐が生んだ波"
|
||||
]
|
||||
},
|
||||
"reef_lionfish": {
|
||||
@@ -1172,6 +1275,12 @@
|
||||
"没入",
|
||||
"没頭",
|
||||
"息を呑む没頭"
|
||||
],
|
||||
"detected.coral": [
|
||||
"サンゴ",
|
||||
"礁サンゴ",
|
||||
"造礁サンゴ",
|
||||
"造礁サンゴ · この魚が削って砂にする礁"
|
||||
]
|
||||
},
|
||||
"reef_coralspacific": {
|
||||
@@ -1210,6 +1319,12 @@
|
||||
"没入",
|
||||
"没頭",
|
||||
"息を呑む没頭"
|
||||
],
|
||||
"detected.polyp": [
|
||||
"ポリプ",
|
||||
"サンゴのポリプ",
|
||||
"生きたポリプ",
|
||||
"ポリプ · 一つ一つが小さな動物、群体の建設者"
|
||||
]
|
||||
},
|
||||
"reef_redsea": {
|
||||
@@ -1399,6 +1514,12 @@
|
||||
"恐れ",
|
||||
"胸騒ぎ",
|
||||
"ゆっくりと冷たい胸騒ぎ"
|
||||
],
|
||||
"detected.marinesnow": [
|
||||
"粒子",
|
||||
"マリンスノー",
|
||||
"落下する有機物",
|
||||
"マリンスノー · 上層から沈む有機物の屑"
|
||||
]
|
||||
},
|
||||
"abyss_bigfin": {
|
||||
@@ -1432,7 +1553,13 @@
|
||||
"Magnapinna",
|
||||
"Magnapinna · 肘のように曲がる腕を闇へ何メートルも垂らす"
|
||||
],
|
||||
"measure.depth": "−2,000 m"
|
||||
"measure.depth": "−2,000 m",
|
||||
"detected.arms": [
|
||||
"腕",
|
||||
"肘状の腕",
|
||||
"垂れ下がる糸",
|
||||
"肘状の腕 · 横に張り出し、数メートルの糸を引く"
|
||||
]
|
||||
},
|
||||
"abyss_dandelion": {
|
||||
"feel.unease": [
|
||||
@@ -1465,7 +1592,13 @@
|
||||
"タンポポクダクラゲ",
|
||||
"Rhodaliidae · 海底につながれたクローンの群体"
|
||||
],
|
||||
"measure.depth": "−2,500 m"
|
||||
"measure.depth": "−2,500 m",
|
||||
"detected.tentacle": [
|
||||
"糸",
|
||||
"触手",
|
||||
"捕食の触手",
|
||||
"捕食の触手 · 獲物を捕らえる漂う網"
|
||||
]
|
||||
},
|
||||
"abyss_octopus": {
|
||||
"feel.unease": [
|
||||
@@ -1498,7 +1631,13 @@
|
||||
"Graneledone",
|
||||
"Graneledone boreopacifica · 卵を約4.5年抱き続ける"
|
||||
],
|
||||
"measure.depth": "−2,500 m"
|
||||
"measure.depth": "−2,500 m",
|
||||
"detected.arms": [
|
||||
"腕",
|
||||
"八本の腕",
|
||||
"吸盤の並ぶ腕",
|
||||
"八本の腕 · 触れたものを味わう吸盤付き"
|
||||
]
|
||||
},
|
||||
"abyss_seapig": {
|
||||
"feel.unease": [
|
||||
@@ -1531,6 +1670,12 @@
|
||||
"Enypniastes eximia",
|
||||
"Enypniastes · 泳ぐナマコ、「頭のない鶏」"
|
||||
],
|
||||
"measure.depth": "−2,700 m"
|
||||
"measure.depth": "−2,700 m",
|
||||
"detected.veil": [
|
||||
"ヴェール",
|
||||
"口前のヴェール",
|
||||
"遊泳用のヴェール",
|
||||
"口前のヴェール · 堆積物を掃き、羽ばたいて泳ぐ"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user