Skip to content

fix(scratch-vm): create missing variable and broadcast definitions on import#554

Merged
cwillisf merged 1 commit intohotfix/scratch-blocks-release-triagefrom
fix/backpack-missing-definitions
Apr 30, 2026
Merged

fix(scratch-vm): create missing variable and broadcast definitions on import#554
cwillisf merged 1 commit intohotfix/scratch-blocks-release-triagefrom
fix/backpack-missing-definitions

Conversation

@cwillisf
Copy link
Copy Markdown
Contributor

Resolves

Part of #533. Fixes four related items in the spork tracker:

  • Backpack variable corruption (variables blank after paste). Reported by @Joeclinton1 (comment) and replicated by @redspacecat (comment).
  • Backpack broadcast non-firing (broadcast not defined in target project). Diagnosed by @nimeratus (comment).
  • Broadcasts dropdown empties after sprite import (forum 878352, 878407).
  • Backpacked sprites can't talk to others (forum 878434, 879069).

Proposed Changes

Two gaps in scratch-vm had the same shape:

  1. VirtualMachine.shareBlocksToTarget only ran reference reconciliation when called with a source target ID. The backpack-paste path passes no source target, so pasted scripts referenced ids that weren't defined anywhere in the receiving project.
  2. Target.fixUpVariableReferences walked variable and list references but skipped broadcasts, because getAllVariableAndListReferences was called without optIncludeBroadcast. The sprite-import path (installTargets with wholeProject=false) calls fixUpVariableReferences, so imported sprites left broadcast references undefined.

This PR:

  • Extends fixUpVariableReferences to walk broadcast references and to run safely on the stage. The local-vs-global rename branches that don't apply on the global scope are guarded with !this.isStage. Updates the JSDoc.
  • Wires shareBlocksToTarget to call target.fixUpVariableReferences() after creating the new blocks when there is no source target.

Reason for Changes

Pre-spork, the forked scratch-blocks auto-created Blockly variable-model entries for unknown ids referenced by blocks added to the workspace. Those auto-creations fired var_create events that scratch-vm's Blocks.blocklyListen consumed, materializing the missing definitions on the right target. Upstream Blockly does not auto-create from unknown ids the same way, so the unfork removed that implicit path and the references began to dangle.

Doing the reconciliation in scratch-vm itself makes it self-sufficient: the authoritative project state lives there, the existing fixUpVariableReferences already handles the merge / create / remap cases for variables and lists, and including broadcasts is a small extension to that logic. The change is also idempotent against any future scratch-blocks behavior.

Test Coverage

  • packages/scratch-vm/test/unit/engine_target.js: 5 new Tap tests covering broadcast handling in fixUpVariableReferences (undefined ref creates a stage broadcast, same-name remap, idempotency) and stage-as-target (no rename of existing stage variables, broadcast creation when stage is the receiving target).
  • packages/scratch-vm/test/unit/virtual-machine.js: 4 new Tap tests for shareBlocksToTarget without a source target (variable creation, broadcast creation, name-collision remap, stage as target) and 1 for installTargets covering the sprite-import broadcast path.
  • npm test --workspace=packages/scratch-vm passes (3698 / 3698).
  • npm run build and npm test from the repo root pass on every workspace.

Manual Testing

  • Sprite import: imported a .sprite3 that references broadcasts; broadcasts dropdown is populated on the receiving project.
  • Sprite import cross-sprite messaging: imported a sprite that broadcasts; another sprite with when I receive actually fires at runtime.
  • Stage as paste / import target: existing stage variables are not renamed.
  • Spot-check existing behavior: regular cross-sprite drag, normal sprite-import without broadcasts, regular workspace use; nothing visibly regressed.
  • Backpack paste with a variable (needs a backpack host).
  • Backpack paste with a broadcast (needs a backpack host).
  • Backpacked sprite cross-sprite messaging (needs a backpack host).

Pasting blocks from the backpack (no source target) and importing
sprites both rely on `Target.fixUpVariableReferences` to reconcile
variable references against the project. Two gaps caused references
to dangle: `shareBlocksToTarget` only ran reconciliation when called
with a source target, and `fixUpVariableReferences` walked variable
and list references but not broadcast references.

`fixUpVariableReferences` now includes broadcasts via
`getAllVariableAndListReferences(null, true)` and is safe to run on
the stage; the local-vs-global rename branches that don't apply to
the global scope are guarded with `!this.isStage`.
`shareBlocksToTarget` calls it after creating the new blocks when no
source target was provided.

Refs #533
@cwillisf cwillisf requested a review from a team as a code owner April 30, 2026 16:01
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

Test Results

    4 files   -   4    973 suites   - 113   12m 46s ⏱️ -32s
2 395 tests  - 348  2 387 ✅  - 348   8 💤 ±0  0 ❌ ±0 
5 763 runs   - 538  5 725 ✅  - 537  38 💤  - 1  0 ❌ ±0 

Results for commit 673e355. ± Comparison against base commit 4eb7f74.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes missing variable/list/broadcast definitions when blocks/sprites are imported into a project (notably via backpack paste and sprite import), by reconciling referenced IDs against project state in scratch-vm and creating/remapping the needed stage-scoped definitions.

Changes:

  • Extend Target.fixUpVariableReferences() to include broadcast references and to safely run when the receiving target is the stage.
  • Update VirtualMachine.shareBlocksToTarget() to call target.fixUpVariableReferences() when there is no source target (e.g., blocks originating outside the project such as backpack paste).
  • Add unit tests covering broadcast reconciliation, stage-as-target behavior, backpack-paste-like sharing without a source target, and the sprite-import broadcast path.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
packages/scratch-vm/src/engine/target.js Reconciles broadcast references alongside variables/lists and allows reconciliation to run on stage targets while guarding rename-only logic.
packages/scratch-vm/src/virtual-machine.js Runs reference reconciliation after block sharing when blocks come from outside the project (no source target).
packages/scratch-vm/test/unit/engine_target.js Adds targeted tests for broadcast reconciliation behavior and stage-as-target behavior in fixUpVariableReferences().
packages/scratch-vm/test/unit/virtual-machine.js Adds tests for shareBlocksToTarget without a source target and for sprite-import broadcast reconciliation via installTargets.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@cwillisf cwillisf merged commit f687340 into hotfix/scratch-blocks-release-triage Apr 30, 2026
17 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 30, 2026
@cwillisf cwillisf deleted the fix/backpack-missing-definitions branch April 30, 2026 17:42
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants