Skip to content

Fix realtime terrain and scene collaboration primitives - #573

Merged
Aymericr merged 2 commits into
mainfrom
fix/realtime-collaboration-resilience
Aug 3, 2026
Merged

Fix realtime terrain and scene collaboration primitives#573
Aymericr merged 2 commits into
mainfrom
fix/realtime-collaboration-resilience

Conversation

@Aymericr

@Aymericr Aymericr commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • expose the server-safe retired drawing-sheet migration for hosted scene authority
  • add bounded height-patch encoding and a remote-only live terrain layer so sculpt previews render without contaminating local history
  • keep terrain commits based on persisted state and expose the canonical camera-pose subscription for hosted awareness

Automated verification

  • bun test packages/core/src packages/editor/src/lib/terrain-sculpt.test.ts — 949 passed
  • bun run check-types — 9 tasks passed
  • Biome and git diff --check passed

Manual verification

  • In two independent sessions, hold a raise/lower terrain stroke and confirm it renders remotely before pointer-up
  • Cancel a stroke and confirm the remote preview disappears without persisting
  • Release a stroke and confirm the final terrain persists in both sessions
  • Refresh/reconnect an observer during a stroke and confirm the active preview replays cleanly
  • Move the remote 3D camera and confirm its camera cone updates

Note

Medium Risk
Changes live terrain resolution and sculpt commit baselines (easy to get wrong vs. persisted state), plus new decode paths for patch payloads; scene migration is isolated but runs on every load.

Overview
Adds collaboration primitives so remote terrain sculpting can render in-session without polluting local undo/history.

Live terrain gains a separate remote preview layer (previewRemote, endRemote, endRemoteSource): observers see in-progress strokes, local strokes override remote previews, and sculpt starts/commits from persisted ground via new persistedTerrainFieldOf (while terrainFieldOf still merges local + remote for rendering/raycasts).

Wire format: bounded height patches are encoded/decoded as base64 int16 samples (encodeHeightPatch / decodeHeightPatch), sharing helpers with full-field terrain codec.

Scene load: retired drawing-sheet removal moves to immutable removeRetiredDrawingSheetNodes and is exposed as @pascal-app/core/scene-migrations for server-side authority.

Editor re-exports subscribeCameraPose for hosted camera awareness; terrain sculpt baseline reads persisted terrain only.

Reviewed by Cursor Bugbot for commit f65686e. Bugbot is set up for automated code reviews on this repo. Configure here.

@Aymericr
Aymericr marked this pull request as ready for review August 3, 2026 18:27
@Aymericr
Aymericr merged commit b060d6c into main Aug 3, 2026
2 checks passed
@Aymericr
Aymericr deleted the fix/realtime-collaboration-resilience branch August 3, 2026 18:27

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f65686e. Configure here.

set((state) => {
const next = new Map(state.remoteStrokes)
next.set(siteId, { sourceId, field, lastPatch: patch })
return { remoteStrokes: next }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remote previews skip terrain mesh

High Severity

The new remoteStrokes are exposed via fieldOf, but downstream rendering and persistence logic isn't fully updated. This causes remote sculpts to appear flat on the grid, prevents mesh updates from remote lastPatches, and allows remote previews to incorrectly override persisted terrain after updates.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f65686e. Configure here.

...node,
children: children.filter(
(childId) => !(typeof childId === 'string' && removedNodeIds.has(childId)),
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration keeps non-string children

Low Severity

When stripping retired drawing-sheet ids from parent children, the new helper removes only string entries in the retired set, while the inlined migrateNodes logic normalized via getStringArray, dropping non-string entries whenever a parent was rewritten.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f65686e. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant