SOURCE / PINNED RELEASE
Made of little things.
Napcraft
- Release
- ee47ac6a25df…
- Author-recorded commit
- 410dea87e109…
- License
- LICENSE
- Author’s source reference
- nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-52f9e22f5ce
Archive hash verified: 9e6e0cabb18d0d46…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
# Responsive shared editing — soyLI 0.23.1
## Tooling update and preservation
Ran `soyli update`: **0.23.1 is up to date**. The first sandboxed release check
could not reach GitHub; the authorized network-enabled retry succeeded.
Ran `soyli skills update`: **0 files updated, 0 edited files preserved**.
The corrected build/backend skills and supporting documents were already present
as working-tree changes on entry. Reviewed and retained those exact changes,
including `docs/examples/backend-context.d.ts`; no conflict needed resolution.
Hashes verified source, backend, napplet.json, package.json and pnpm-lock.yaml
were unchanged by the update. No scaffold, dependency reinstall, account switch,
provider change, or world migration occurred. Only verification scripts were later
added to package.json; the dependency lockfile and build configuration are unchanged.
The native multiplayer runner now copies backend sources and supplies disposable
`connectIdentity` / `approveBackendAccount` fixtures. All new runner tests use
those fixtures with real account proofs and permissions. `soyli browser path --json`
replaced a hard-coded browser-cache path in the persistent-preview test helper.
`soyli run type-check` now succeeds in the agent sandbox.
## Diagnosis and changes
The original game drew a block only after its authoritative save returned. A
shared busy flag disabled Build/Erase during background reads, an 800 ms cooldown
rejected additional input, and other players discovered edits through full-world
queries every 2.6 seconds. WebRTC carried presence but no refresh hints.
`src/shared-editor.ts` now owns confirmed state, pending predictions, the command
queue and a single CVM request lane. The canvas changes on the next frame, with a
dashed pale outline and explicit pending status. Touch actions start on pointerdown
without generating a duplicate click. Network reads never disable building.
- At most **24 accepted pending edits**; capacity exhaustion is visible.
- New worlds save up to **8 edits atomically** per command. A failed batch saves
none. The original expected cell, foundation, support, top-block and owner-lock
rules remain enforced by the provider.
- Every in-flight command retains its exact ID, expiry and payload. The queue and
intent are journaled through optional NAP-STORAGE before submission. Reconnects
retry that same intent with 1–8 second backoff; a lost acknowledgement cannot
manufacture a duplicate command. Without storage, live recovery still works,
but pending work cannot survive closing/reloading the app.
- Recovery journals also retain the transport actor returned by the documented
`soy_session` service. Provider receipts are principal-scoped (verified against
the local request-store key). If a fresh guest transport restores an old journal,
it refreshes and cancels that pending work instead of replaying it under another
principal. This protects the ABA case: a committed block was removed by a friend
before the original player recovered. Legacy journals without an actor are
reviewed through a snapshot, not blindly retried.
- A definitive conflict triggers an authoritative refresh. If every original
cell/support/permission precondition still holds, an independent edit may get
a **new** intent, at most twice. Stale or dependent predictions are cancelled
visibly. Expired uncertain intents are refreshed and reviewed, never replayed
automatically with a new ID.
- Snapshots and receipts cannot move confirmed revision backwards. Reload
recovery can receive an old cached receipt after a newer join snapshot without
rolling the world back. Session disposal ignores late completions.
- New backend operations return bounded deltas from a **128-edit journal**. Old
cursors request a full resync. Unchanged polling uses `soy_backend_changes`;
full snapshots are reserved for joining/recovery and legacy releases.
- A confirmed save broadcasts a bounded WebRTC refresh hint. Peers supply **no
trusted blocks or revisions**. The receiver performs its own authorized read.
Hints are coalesced to at most one refresh trigger per 500 ms, including a
trailing refresh for the last save in a burst. Conservative polling remains
available when peers disconnect, and refresh catches up after tab suspension.
- Existing codes retain their original release. The client inspects that pinned
schema and uses legacy single-edit/full-read operations where required; old
worlds are neither migrated nor stranded.
## Responsiveness measurements
Measured actual rendered canvas state, provider acknowledgement, and rendered
state in the second independent browser. The editor is a guest and the observer
is a separate signed-in owner. Eight build/erase samples per build: four desktop,
four touch at an exact 390 × 844 iframe. The touch run uses CDP touch events.
Both builds use soyLI 0.23.1, its actual local backend and authenticated local
TURN/TCP. Add **150 ms before and after every CVM call**, independently of
**50 ms ±15 ms per outgoing WebRTC hop**. The original app was rebuilt in an
isolated temporary checkout of commit `368abc2`; no current source was replaced.
The same final benchmark waits for an idle CVM lane in both versions. Separate
fault tests deliberately overlap input with background reads.
| Input / observation | Before, median (range) | After, median (range) |
| --- | --- | --- |
| Desktop: local rendered block | 488 ms (486–490) | 7 ms (3–10) |
| Desktop: provider confirmation | 482 ms (478–487) | 484 ms (474–498) |
| Desktop: other player rendered block | 1681 ms (1002–1704) | 1011 ms (1004–1022) |
| Touch: local rendered block | 513 ms (509–515) | 13 ms (9–14) |
| Touch: provider confirmation | 503 ms (499–508) | 475 ms (471–482) |
| Touch: other player rendered block | 1680 ms (1642–1681) | 1014 ms (981–1093) |
Single-edit result payload: **7,841 → 98 bytes** (application result JSON,
excluding protocol envelopes). Four samples per input style are a regression
comparison, not a statistical public-network latency distribution.
Reference artifact: `1838b6c34ee5b63f660f0f455dfaf0103dfff7a4a2c1fabfe97e0ea0876b6796`.
Final artifact: `447b6b5d91a7b26f135d659ab8db814f90b6efdb96aef72d88ac0c026400d14b`.
Budgets: local feedback <80 ms; remote visibility <1,300 ms for the idle-lane
scenario. These are local regression budgets, not public service guarantees.
A separate overlap run observed a 1,623 ms remote update: the editor rendered in
7 ms but confirmation waited for an outstanding read. An exploratory 4× CPU
throttle run kept local feedback at 4–9 ms, while remote delivery reached
1,669 ms and exceeded the 1,300 ms target. Those slower paths are retained as
limitations, not reported as passing the idle-lane performance target.
## Correctness and coverage
- Unit suite: immediate feedback, input accepted during reads, queue capacity,
batching, exact retries, dependent rollback, expiry, restored old receipts,
disposal, untrusted hint throttling, trailing refresh and revalidated conflicts.
- Backend tests: access policy, legacy edit rules, ownership/lock checks, atomic
multi-edit failure, compact deltas and bounded-journal recovery.
- Real native two-player scenario: **22 assertions** covering rapid inputs,
batching, input during a held read, one winner for simultaneous same-cell
edits, both independent columns saving, outage queues, exact reconnect retries,
acknowledgement lost after durable commit, storage/reload recovery, peer outage
with polling fallback, guest denial, owner pause/resume, everyone leaving and
changed-transport recovery after another player undoes the committed edit.
Fault injection delays or loses SDK results around real provider operations;
it does not substitute a fake database or grant guest ownership.
- Existing original-release world was edited with legacy operations and retained
all six confirmed edits through backend restart. A new-release world was also
checked through the persistent preview and retained all four confirmed edits
after a full process restart.
- Exact iframe layouts: 200×160, 320×560, 900×600, 1440×900, 390×844, 360×640,
844×390. Touch D-pad/action overlap, release without stuck movement, palette in
both color schemes, and absent optional domains were checked. Test-only host
CSS removes preview chrome/min-height; actual inner dimensions are asserted.
- Refreshed real two-player landscape/portrait images and seven-second gameplay
clip. Selected clip decoded and played to completion in soyLI Listing.
## Remaining platform and environment limits
The host exposes no backend streams/watch API or CEP-41 subscriptions. A peer
message is not proof of a durable commit. A viewer therefore pays for save
confirmation plus an authorized refresh, and an input arriving during a read
waits for that read before submission. Safely removing those round trips needs
platform-supported committed change delivery or cancellable/prioritized calls.
TURN helps establish the peer connection; it does not speed up CVM transactions.
The provider still checks revisions across an entire instance, including queries.
Edits to independent cells may conflict. The game mitigates this with one local
request lane, batches, and bounded refresh/revalidation; cross-player contention
and provider capacity/rate limits remain. Legacy pinned worlds benefit from
immediate feedback and refresh hints but cannot acquire new batch/delta handlers
without an explicit platform migration capability.
Measurements use local services with injected delay. Actual iOS/Android hardware,
separate public networks, deployed TURN, and sustained multi-hour/many-player load
are not certified by these tests. No prediction is labelled durable until the
provider confirms it. Browser suspension or a failed optional storage write can
also delay recovery; no browser can guarantee flushing a new journal after an
abrupt process termination.
## Reproduce
```sh
soyli update
soyli skills update
pnpm verify
soyli backend check
pnpm test:conformance
soyli check
pnpm test:edit-latency --turn-binary /opt/homebrew/bin/turnserver
pnpm test:edit-correctness --turn-binary /opt/homebrew/bin/turnserver
```
The TURN argument is optional elsewhere; direct local ICE required this relay on
this machine. Each native scenario uses a disposable backend and independent
browser contexts. `NAPCRAFT_CPU_RATE=4` adds the separate CPU stress condition.
`NAPCRAFT_BASELINE=1` labels output for a reference build; it does not suppress
failed budgets. Reports go under ignored `.napplet-space/`.
For persisted preview checks, start `soyli dev --no-open --port 0`, use its printed
URL with `NAPCRAFT_URL`, run `tests/shared-world.mjs`, stop/restart the same preview
project, then run `tests/persistence.mjs`. `tests/legacy-world.mjs <fixture.json>`
checks an original-release `{code,edits}` fixture and updates its expected count.
Run `tests/responsive.mjs` for layouts and `tests/capture.mjs` for real gameplay
media. Stop the owned preview afterward. Do not run a standalone build concurrently
with its watcher: both write the same dist files.
## Final evidence
- `pnpm verify`: **21 tests passed** (6 guidance, 5 backend, 10 synchronization),
type-check and build passed. `soyli run type-check` passed separately.
- `soyli backend check`: compiled all **6 operations**.
- `pnpm test:conformance`: **5 passed, 0 failed, 5 skipped**. Three manifest-event
checks, wire-envelope evidence and lifecycle measurement remain skipped.
- `soyli check`: **checked**, profile `space-playback-4`; final artifact 47,500 bytes.
This confirms the earlier missing-backend snapshot blocker is fixed for this app.
The check correctly identifies gameplay as requiring separate scenario evidence.
- Native scenarios: **22 shared-edit assertions**, **3 latency assertions**, and
**9 original multiplayer assertions** passed. The baseline intentionally fails
the local-feedback budget; the CPU stress run exceeds the remote idle-lane budget.
- Captures: `preview-responsive.png`, `preview-responsive-portrait.png`,
`preview-responsive.webm`. The clip is seven seconds, 960×600, silent VP8;
previous captures are retained. Selected media matches the final artifact hash.
- Public provider status: reachable, advertises `soy.backends.v1`, the supported
QuickJS execution profile, polling updates, and **allowlist admission**. This
read-only probe does not prove creator deployment admission or public gameplay.
- `soyli publish --dry-run`: inspected the existing public destinations and source;
no account or provider was changed. Actual release/deployment status is recorded
separately below when attempted.
## Publication outcome
Actual `soyli publish` reached sandbox checking and source freezing, then stopped
with `SOURCE_SECRET` because Git history retains the old public generated backend
context, despite its removal from the current tree. `soyli status --json` reports
`not_started`. No release was published and no backend release was activated.
See [the precise history-migration blocker](soyli-history-publish-blocker.md).
No history rewrite, account switch, provider substitution or disabled check was
used to bypass it. All owned preview and disposable relay sessions were stopped.
