From e3182f2759a582606a1b7d05b88944338c05ea2d Mon Sep 17 00:00:00 2001 From: 8to16 Date: Fri, 9 Jan 2026 21:52:04 +0000 Subject: [PATCH 01/18] mesh is here, kinda --- extensions/8to16/mesh.js | 78 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 extensions/8to16/mesh.js diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js new file mode 100644 index 0000000000..8a1046063c --- /dev/null +++ b/extensions/8to16/mesh.js @@ -0,0 +1,78 @@ +// Name: Mesh +// ID: eightxtwoMesh +// Description: Communicate with other projects, without the need for a network. +// By: 8to16 +// License: MPL-2.0 + +(function (Scratch) { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("Mesh extension must run unsandboxed"); + } + + const bc = new BroadcastChannel("extensions.turbowarp.org/8to16/mesh"); + + // taken from local-storage + const session = (() => { + // doesn't need to be cryptographically secure and doesn't need to have excessive length + // this has 16^16 = 18446744073709551616 possible session IDs which is plenty + const soup = "0123456789abcdef"; + let id = ""; + for (let i = 0; i < 16; i++) { + id += soup[Math.floor(Math.random() * soup.length)]; + } + return id; + })(); + + bc.onmessage = ({ data }) => { + console.log(data); + if (data.session === session) { + console.log("Hi"); + return; + } + Scratch.vm.runtime.startHats("eightxtwoMesh_when", { + BROADCAST: data.name, + }); + }; + + class Mesh { + getInfo() { + return { + id: "eightxtwoMesh", + name: Scratch.translate("Mesh"), + blocks: [ + { + opcode: "broadcast", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate("broadcast [BROADCAST]"), + arguments: { + BROADCAST: { + type: Scratch.ArgumentType.STRING, + defaultValue: "dango clicked", + }, + }, + }, + { + opcode: "when", + blockType: Scratch.BlockType.EVENT, + text: Scratch.translate("when I receive [BROADCAST]"), + isEdgeActivated: false, + arguments: { + BROADCAST: { + type: Scratch.ArgumentType.STRING, + defaultValue: "dango clicked", + }, + }, + }, + ], + }; + } + + broadcast({ BROADCAST }) { + bc.postMessage({ name: BROADCAST, session: session }); + } + } + + Scratch.extensions.register(new Mesh()); +})(Scratch); From 3b0b33896b31dcdcfdb7d653804b69f44453ca10 Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 02:10:39 +0000 Subject: [PATCH 02/18] 8to16/mesh: mvp --- docs/8to16/mesh.md | 26 ++++++++ extensions/8to16/mesh.js | 128 +++++++++++++++++++++++++++++-------- extensions/extensions.json | 1 + images/8to16/mesh.svg | 91 ++++++++++++++++++++++++++ 4 files changed, 219 insertions(+), 27 deletions(-) create mode 100644 docs/8to16/mesh.md create mode 100644 images/8to16/mesh.svg diff --git a/docs/8to16/mesh.md b/docs/8to16/mesh.md new file mode 100644 index 0000000000..a6ce7c0dd8 --- /dev/null +++ b/docs/8to16/mesh.md @@ -0,0 +1,26 @@ +# Mesh + +Mesh lets you communicate with other projects running in the same browser, using a special type of message +called a mesh. + +## Setup + +To setup a connection between a project, create a mesh in one project, and then create another mesh with the +same name in another project. + +## Blocks + +```scratch +broadcast mesh ( v) :: #00acff +``` +This block lets you send a mesh to all other projects that have the extension running. It can be received +with the hat block below. + +```scratch +when I receive mesh ( v) :: #00acff hat +``` +This block will activate when another project sends you a mesh with the same name. + +> [!IMPORTANT] +> Projects cannot access variables or lists stored in each other, even if they have the same name and they +> are being used in a mesh. \ No newline at end of file diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index 8a1046063c..a8d0751e58 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -1,6 +1,6 @@ // Name: Mesh // ID: eightxtwoMesh -// Description: Communicate with other projects, without the need for a network. +// Description: Send messages to other projects. // By: 8to16 // License: MPL-2.0 @@ -10,9 +10,32 @@ if (!Scratch.extensions.unsandboxed) { throw new Error("Mesh extension must run unsandboxed"); } + const vm = Scratch.vm; const bc = new BroadcastChannel("extensions.turbowarp.org/8to16/mesh"); + vm.runtime.on("RUNTIME_DISPOSED", () => { + vm.runtime.extensionStorage.meshages = []; + }); + vm.runtime.on("PROJECT_LOADED", () => { + vm.runtime.extensionManager.refreshBlocks(); + }); + + // Spaghetti ahead! + const getMeshages = () => vm.runtime.extensionStorage?.meshages ?? []; + const addMeshage = (name) => { + if (getMeshages().includes(name)) return; + if (name === "") return; + vm.runtime.extensionStorage.meshages = [...getMeshages(), name].sort(); + vm.extensionManager.refreshBlocks(); + }; + const delMeshage = (name) => { + vm.runtime.extensionStorage.meshages = getMeshages().filter( + (n) => n !== name + ); + vm.extensionManager.refreshBlocks(); + }; + // taken from local-storage const session = (() => { // doesn't need to be cryptographically secure and doesn't need to have excessive length @@ -27,45 +50,96 @@ bc.onmessage = ({ data }) => { console.log(data); - if (data.session === session) { - console.log("Hi"); - return; - } - Scratch.vm.runtime.startHats("eightxtwoMesh_when", { + if (data.session === session) return; + vm.runtime.startHats("eightxtwoMesh_when", { BROADCAST: data.name, }); }; class Mesh { + getMeshages() { + const meshages = getMeshages(); + return meshages.length === 0 ? [""] : meshages; + } + new() { + // taken from SharkPool/Camera + // in a Button Context, ScratchBlocks always exists + ScratchBlocks.prompt( + Scratch.translate("New mesh name:"), + "", + (value) => addMeshage(value), + Scratch.translate("Make a Mesh"), + "broadcast_msg" + ); + } + remove() { + // taken from SharkPool/Camera + // in a Button Context, ScratchBlocks always exists + ScratchBlocks.prompt( + Scratch.translate("Remove mesh named:"), + "", + (value) => delMeshage(value), + Scratch.translate("Remove a Mesh"), + "broadcast_msg" + ); + } getInfo() { return { id: "eightxtwoMesh", name: Scratch.translate("Mesh"), + // todo: pick better colours + color1: "#00acff", + color2: "#0088cc", + color3: "#0078b4ff", blocks: [ { - opcode: "broadcast", - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("broadcast [BROADCAST]"), - arguments: { - BROADCAST: { - type: Scratch.ArgumentType.STRING, - defaultValue: "dango clicked", - }, - }, - }, - { - opcode: "when", - blockType: Scratch.BlockType.EVENT, - text: Scratch.translate("when I receive [BROADCAST]"), - isEdgeActivated: false, - arguments: { - BROADCAST: { - type: Scratch.ArgumentType.STRING, - defaultValue: "dango clicked", - }, - }, + func: "new", + blockType: Scratch.BlockType.BUTTON, + text: Scratch.translate("Make a Mesh"), }, + ...(getMeshages().length !== 0 + ? [ + { + func: "remove", + blockType: Scratch.BlockType.BUTTON, + text: Scratch.translate("Remove a Mesh"), + }, + { + opcode: "broadcast", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate("broadcast mesh [BROADCAST]"), + arguments: { + BROADCAST: { + type: Scratch.ArgumentType.STRING, + menu: "MESHES_ACCEPT", + }, + }, + }, + { + opcode: "when", + blockType: Scratch.BlockType.EVENT, + text: Scratch.translate("when I receive mesh [BROADCAST]"), + isEdgeActivated: false, + arguments: { + BROADCAST: { + type: Scratch.ArgumentType.STRING, + menu: "MESHES_NOACCEPT", + }, + }, + }, + ] + : []), ], + menus: { + MESHES_ACCEPT: { + acceptReporters: true, + items: "getMeshages", + }, + MESHES_NOACCEPT: { + acceptReporters: false, + items: "getMeshages", + }, + }, }; } diff --git a/extensions/extensions.json b/extensions/extensions.json index 442f300d59..f9c324c9f2 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -94,6 +94,7 @@ "Lily/CommentBlocks", "veggiecan/LongmanDictionary", "CubesterYT/TurboHook", + "8to16/mesh", "Alestore/nfcwarp", "steamworks", "itchio", diff --git a/images/8to16/mesh.svg b/images/8to16/mesh.svg new file mode 100644 index 0000000000..0ad873334a --- /dev/null +++ b/images/8to16/mesh.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + From 99b68878b8e73b0d71b5f2adf940bd8bc8e0b9af Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 02:25:41 +0000 Subject: [PATCH 03/18] 8to16/mesh: tweaks --- docs/8to16/mesh.md | 7 ++++--- extensions/8to16/mesh.js | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/8to16/mesh.md b/docs/8to16/mesh.md index a6ce7c0dd8..46983c3732 100644 --- a/docs/8to16/mesh.md +++ b/docs/8to16/mesh.md @@ -13,13 +13,14 @@ same name in another project. ```scratch broadcast mesh ( v) :: #00acff ``` -This block lets you send a mesh to all other projects that have the extension running. It can be received -with the hat block below. +This block sends a mesh to all other projects that have the extension running. It triggers the hat block below +in projects that listen for the mesh. ```scratch when I receive mesh ( v) :: #00acff hat ``` -This block will activate when another project sends you a mesh with the same name. +This block will activate when another project sends you a mesh with the same name. It is triggered using the block +above in any other project. > [!IMPORTANT] > Projects cannot access variables or lists stored in each other, even if they have the same name and they diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index a8d0751e58..0f3fc5ec88 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -1,6 +1,6 @@ // Name: Mesh // ID: eightxtwoMesh -// Description: Send messages to other projects. +// Description: Send and receive messages between other projects. // By: 8to16 // License: MPL-2.0 @@ -105,25 +105,25 @@ text: Scratch.translate("Remove a Mesh"), }, { - opcode: "broadcast", - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("broadcast mesh [BROADCAST]"), + opcode: "when", + blockType: Scratch.BlockType.EVENT, + text: Scratch.translate("when I receive mesh [BROADCAST]"), + isEdgeActivated: false, arguments: { BROADCAST: { type: Scratch.ArgumentType.STRING, - menu: "MESHES_ACCEPT", + menu: "MESHES_NOACCEPT", }, }, }, { - opcode: "when", - blockType: Scratch.BlockType.EVENT, - text: Scratch.translate("when I receive mesh [BROADCAST]"), - isEdgeActivated: false, + opcode: "broadcast", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate("broadcast mesh [BROADCAST]"), arguments: { BROADCAST: { type: Scratch.ArgumentType.STRING, - menu: "MESHES_NOACCEPT", + menu: "MESHES_ACCEPT", }, }, }, From 86a5044cff9655c9d2feac47fac0f54878703fbc Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 02:37:52 +0000 Subject: [PATCH 04/18] 8to16/mesh: docs tweak --- docs/8to16/mesh.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/8to16/mesh.md b/docs/8to16/mesh.md index 46983c3732..5889cb6e76 100644 --- a/docs/8to16/mesh.md +++ b/docs/8to16/mesh.md @@ -19,8 +19,8 @@ in projects that listen for the mesh. ```scratch when I receive mesh ( v) :: #00acff hat ``` -This block will activate when another project sends you a mesh with the same name. It is triggered using the block -above in any other project. +This block will activate when another project sends *this* project a mesh with the same name. It is triggered +using the block above in any other project. > [!IMPORTANT] > Projects cannot access variables or lists stored in each other, even if they have the same name and they From d4bd9771f2befdc6d0e084fff8dbcacb20db017d Mon Sep 17 00:00:00 2001 From: 8to16 <197376797+8to16@users.noreply.github.com> Date: Sat, 10 Jan 2026 11:39:35 +0000 Subject: [PATCH 05/18] 8to16/mesh: remove old debugging log --- extensions/8to16/mesh.js | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index 0f3fc5ec88..20de63555d 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -49,7 +49,6 @@ })(); bc.onmessage = ({ data }) => { - console.log(data); if (data.session === session) return; vm.runtime.startHats("eightxtwoMesh_when", { BROADCAST: data.name, From 253f9d2b1ae1d328b6d9bac19798dcb43d7cc37e Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 12:07:10 +0000 Subject: [PATCH 06/18] 8to16/mesh: change image --- images/8to16/mesh.svg | 92 +------------------------------------------ images/README.md | 5 ++- 2 files changed, 5 insertions(+), 92 deletions(-) diff --git a/images/8to16/mesh.svg b/images/8to16/mesh.svg index 0ad873334a..e1390d2246 100644 --- a/images/8to16/mesh.svg +++ b/images/8to16/mesh.svg @@ -1,91 +1 @@ - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/images/README.md b/images/README.md index fef54d00ef..c3344c7b7a 100644 --- a/images/README.md +++ b/images/README.md @@ -323,4 +323,7 @@ All images in this folder are licensed under the [GNU General Public License ver - Created by [@SharkPool-SP](https://github.com/SharkPool-SP/) ## DogeisCut/FormatNumbers.png - - Created by [@Dillon](https://github.com/DillonRGaming) \ No newline at end of file + - Created by [@Dillon](https://github.com/DillonRGaming) + + ## 8to16/mesh.svg + - Creatde by [@kx1bx1](https://github.com/kx1bx1) in https://github.com/TurboWarp/extensions/pull/2382#issuecomment-3731596025 \ No newline at end of file From 2737b5cc04114f2b66266bdf51969f36e4e44664 Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 12:08:17 +0000 Subject: [PATCH 07/18] 8to16/mesh: newlines are poisonous --- docs/8to16/mesh.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/8to16/mesh.md b/docs/8to16/mesh.md index 5889cb6e76..f3abc0be12 100644 --- a/docs/8to16/mesh.md +++ b/docs/8to16/mesh.md @@ -1,27 +1,22 @@ # Mesh -Mesh lets you communicate with other projects running in the same browser, using a special type of message -called a mesh. +Mesh lets you communicate with other projects running in the same browser, using a special type of message called a mesh. ## Setup -To setup a connection between a project, create a mesh in one project, and then create another mesh with the -same name in another project. +To setup a connection between a project, create a mesh in one project, and then create another mesh with the same name in another project. ## Blocks ```scratch broadcast mesh ( v) :: #00acff ``` -This block sends a mesh to all other projects that have the extension running. It triggers the hat block below -in projects that listen for the mesh. +This block sends a mesh to all other projects that have the extension running. It triggers the hat block below in projects that listen for the mesh. ```scratch when I receive mesh ( v) :: #00acff hat ``` -This block will activate when another project sends *this* project a mesh with the same name. It is triggered -using the block above in any other project. +This block will activate when another project sends *this* project a mesh with the same name. It is triggered using the block above in any other project. > [!IMPORTANT] -> Projects cannot access variables or lists stored in each other, even if they have the same name and they -> are being used in a mesh. \ No newline at end of file +> Projects cannot access variables or lists stored in each other, even if they have the same name and they are being used in a mesh. \ No newline at end of file From 1f7c9b475bef820fd75d9c795557aa6bd3867973 Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 12:09:54 +0000 Subject: [PATCH 08/18] 8to16/mesh: works in turbowarp desktop --- docs/8to16/mesh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/8to16/mesh.md b/docs/8to16/mesh.md index f3abc0be12..6bc95dfec9 100644 --- a/docs/8to16/mesh.md +++ b/docs/8to16/mesh.md @@ -1,6 +1,6 @@ # Mesh -Mesh lets you communicate with other projects running in the same browser, using a special type of message called a mesh. +Mesh lets you communicate with other projects running in the same browser or in TurboWarp Desktop, using a special type of message called a mesh. ## Setup From ca4ec739f7888f0f910493d37329f28d8df6a708 Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 12:11:29 +0000 Subject: [PATCH 09/18] 8to16/mesh: minor docs update --- docs/8to16/mesh.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/8to16/mesh.md b/docs/8to16/mesh.md index 6bc95dfec9..edcdf8ea05 100644 --- a/docs/8to16/mesh.md +++ b/docs/8to16/mesh.md @@ -9,12 +9,12 @@ To setup a connection between a project, create a mesh in one project, and then ## Blocks ```scratch -broadcast mesh ( v) :: #00acff +broadcast mesh (mesh name v) :: #00acff ``` This block sends a mesh to all other projects that have the extension running. It triggers the hat block below in projects that listen for the mesh. ```scratch -when I receive mesh ( v) :: #00acff hat +when I receive mesh [mesh name v] :: #00acff hat ``` This block will activate when another project sends *this* project a mesh with the same name. It is triggered using the block above in any other project. From e15c55cec1a121fd1a64100f32b63a135a273287 Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 14:19:27 +0000 Subject: [PATCH 10/18] 8to16/mesh: WIP enhancements --- docs/8to16/mesh.md | 45 +++++- extensions/8to16/mesh.js | 333 ++++++++++++++++++++++++++++++--------- 2 files changed, 299 insertions(+), 79 deletions(-) diff --git a/docs/8to16/mesh.md b/docs/8to16/mesh.md index edcdf8ea05..4b9b13fe30 100644 --- a/docs/8to16/mesh.md +++ b/docs/8to16/mesh.md @@ -1,22 +1,51 @@ # Mesh -Mesh lets you communicate with other projects running in the same browser or in TurboWarp Desktop, using a special type of message called a mesh. +Mesh lets you communicate with other projects running in the same browser or in TurboWarp Desktop, using special versions of variables and broadcasts. This allows you to for example, create a game that talks to another project running at the same time. + +> [!NOTE] +> Only projects running in the same browser as another will receive messages. However, projects running in a different window will work. ## Setup -To setup a connection between a project, create a mesh in one project, and then create another mesh with the same name in another project. +To setup a connection between a project, create a broadcast or variable in one project, and then create another of the same type and name in another project. ## Blocks +The blocks below are equivalent to other blocks in Scratch, but are received by other projects too. + +### Broadcasts + +```scratch +when I receive [message v] :: #4cdab2 hat +``` +This block will activate when another project sends *this* project a message with the same name. It is triggered using the block above in any other project. + +```scratch +broadcast (message v) :: #4cdab2 +``` +This block sends a message to all other projects that have the extension running. It triggers the hat block below in projects that listen for the message. + ```scratch -broadcast mesh (mesh name v) :: #00acff +broadcast (message v) and wait :: #4cdab2 ``` -This block sends a mesh to all other projects that have the extension running. It triggers the hat block below in projects that listen for the mesh. +This is similar to the block above, but waits for the broadcast to end before continuing to run the script. + +### Variables + +```scratch +(get (variable v) :: #4cdab2) +``` + +This block gets the value of a mesh variable. + +```scratch +set (variable v) to [value] :: #4cdab2 +``` + +This block sets the value of a mesh variable in all currently connected projects. ```scratch -when I receive mesh [mesh name v] :: #00acff hat +change (variable v) by (1) :: #4cdab2 ``` -This block will activate when another project sends *this* project a mesh with the same name. It is triggered using the block above in any other project. -> [!IMPORTANT] -> Projects cannot access variables or lists stored in each other, even if they have the same name and they are being used in a mesh. \ No newline at end of file +This block increments the value of a mesh variable in all currently connected projects by the specified number. \ No newline at end of file diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index 20de63555d..c1264bf1b6 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -13,72 +13,142 @@ const vm = Scratch.vm; const bc = new BroadcastChannel("extensions.turbowarp.org/8to16/mesh"); + let lastSuccessfulMeshage = ""; + if (!vm.runtime.extensionStorage.mesh) + vm.runtime.extensionStorage.mesh = { messages: [], variables: {} }; vm.runtime.on("RUNTIME_DISPOSED", () => { - vm.runtime.extensionStorage.meshages = []; + vm.runtime.extensionStorage.mesh = { messages: [], variables: {} }; }); vm.runtime.on("PROJECT_LOADED", () => { vm.runtime.extensionManager.refreshBlocks(); }); // Spaghetti ahead! - const getMeshages = () => vm.runtime.extensionStorage?.meshages ?? []; + const getMeshages = () => vm.runtime.extensionStorage?.mesh.messages ?? []; const addMeshage = (name) => { if (getMeshages().includes(name)) return; if (name === "") return; - vm.runtime.extensionStorage.meshages = [...getMeshages(), name].sort(); + vm.runtime.extensionStorage.mesh.messages = [...getMeshages(), name]; vm.extensionManager.refreshBlocks(); }; const delMeshage = (name) => { - vm.runtime.extensionStorage.meshages = getMeshages().filter( + if (!getMeshages().includes(name)) return; + vm.runtime.extensionStorage.mesh.messages = getMeshages().filter( (n) => n !== name ); vm.extensionManager.refreshBlocks(); }; - // taken from local-storage - const session = (() => { - // doesn't need to be cryptographically secure and doesn't need to have excessive length - // this has 16^16 = 18446744073709551616 possible session IDs which is plenty - const soup = "0123456789abcdef"; - let id = ""; - for (let i = 0; i < 16; i++) { - id += soup[Math.floor(Math.random() * soup.length)]; - } - return id; - })(); + const getMeshVars = () => vm.runtime.extensionStorage?.mesh.variables ?? {}; + const addMeshVar = (name) => { + if (Object.keys(getMeshVars()).includes(name)) return; + if (name === "") return; + vm.runtime.extensionStorage.mesh.variables = { + ...getMeshVars(), + [name]: "", + }; + vm.extensionManager.refreshBlocks(); + }; + const delMeshVar = (name) => { + if (Object.keys(getMeshVars()).includes(name)) return; + vm.runtime.extensionStorage.mesh.variables = Object.fromEntries( + Object.entries(getMeshVars()).filter((v) => v[0] !== name) + ); + vm.extensionManager.refreshBlocks(); + }; - bc.onmessage = ({ data }) => { - if (data.session === session) return; - vm.runtime.startHats("eightxtwoMesh_when", { - BROADCAST: data.name, - }); + bc.onmessage = async ({ data }) => { + switch (data.type) { + case "broadcast": { + let hats = vm.runtime.startHats("eightxtwoMesh_when", { + BROADCAST: data.name, + }); + if (data.willWait) { + await new Promise((resolve) => { + const poll = () => { + if ( + hats.filter( + (thread) => vm.runtime.threads.indexOf(thread) === -1 + ).length === 0 + ) + resolve(); + else setTimeout(poll, 25); + }; + poll(); + }); + bc.postMessage({ + type: "waitdone", + name: data.name, + }); + } + break; + } + case "var": { + vm.runtime.extensionStorage.mesh.variables[data.key] = data.value; + break; + } + case "waitdone": { + lastSuccessfulMeshage = data.name; + break; + } + } }; + // dummy message to activate the broadcastchannel + bc.postMessage({ type: "hello" }); class Mesh { - getMeshages() { - const meshages = getMeshages(); + getMeshagesForMenu() { + const meshages = getMeshages().sort(); return meshages.length === 0 ? [""] : meshages; } - new() { + getMeshVarsForMenu() { + const meshvars = Object.keys(getMeshVars()).sort(); + return meshvars.length === 0 ? [""] : meshvars; + } + newMsg() { + // taken from SharkPool/Camera + // in a Button Context, ScratchBlocks always exists + ScratchBlocks.prompt( + Scratch.translate("New message name:"), + "", + addMeshage, + Scratch.translate("Mesh Manager"), + "broadcast_msg" + ); + } + removeMsg() { // taken from SharkPool/Camera // in a Button Context, ScratchBlocks always exists ScratchBlocks.prompt( - Scratch.translate("New mesh name:"), + Scratch.translate("Remove message named:"), "", - (value) => addMeshage(value), - Scratch.translate("Make a Mesh"), + delMeshage, + Scratch.translate("Mesh Manager"), "broadcast_msg" ); } - remove() { + newVar() { // taken from SharkPool/Camera // in a Button Context, ScratchBlocks always exists ScratchBlocks.prompt( - Scratch.translate("Remove mesh named:"), + Scratch.translate( + "New variable name (this variable will be available to all sprites):" + ), "", - (value) => delMeshage(value), - Scratch.translate("Remove a Mesh"), + addMeshVar, + Scratch.translate("Mesh Manager"), + "broadcast_msg" + ); + } + removeVar() { + // taken from SharkPool/Camera + // in a Button Context, ScratchBlocks always exists + ScratchBlocks.prompt( + Scratch.translate("Remove variable named:"), + "", + delMeshVar, + Scratch.translate("Mesh Manager"), "broadcast_msg" ); } @@ -86,64 +156,185 @@ return { id: "eightxtwoMesh", name: Scratch.translate("Mesh"), - // todo: pick better colours - color1: "#00acff", - color2: "#0088cc", - color3: "#0078b4ff", + docsURI: "https://extensions.turbowarp.org/8to16/mesh", + color1: "#4cdab2", + color2: "#44cda5", + color3: "#3dc099", blocks: [ { - func: "new", + func: "newMsg", + blockType: Scratch.BlockType.BUTTON, + text: Scratch.translate("Make a Message"), + }, + { + func: "removeMsg", blockType: Scratch.BlockType.BUTTON, - text: Scratch.translate("Make a Mesh"), + text: Scratch.translate("Remove a Message"), + hideFromPalette: getMeshages().length === 0, + }, + { + opcode: "when", + blockType: Scratch.BlockType.EVENT, + text: Scratch.translate("when I receive [BROADCAST]"), + isEdgeActivated: false, + hideFromPalette: getMeshages().length === 0, + arguments: { + BROADCAST: { + type: Scratch.ArgumentType.STRING, + menu: "MESHES_NOACCEPT", + }, + }, + }, + { + opcode: "broadcast", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate("broadcast [BROADCAST]"), + hideFromPalette: getMeshages().length === 0, + arguments: { + BROADCAST: { + type: Scratch.ArgumentType.STRING, + menu: "MESHES_ACCEPT", + }, + }, + }, + { + opcode: "broadcastWait", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate("broadcast [BROADCAST] and wait"), + hideFromPalette: getMeshages().length === 0, + arguments: { + BROADCAST: { + type: Scratch.ArgumentType.STRING, + menu: "MESHES_ACCEPT", + }, + }, + }, + ...(getMeshages().length === 0 ? [] : ["---"]), + { + func: "newVar", + blockType: Scratch.BlockType.BUTTON, + text: Scratch.translate("Make a Variable"), + }, + { + func: "removeVar", + blockType: Scratch.BlockType.BUTTON, + text: Scratch.translate("Remove a Variable"), + hideFromPalette: Object.keys(getMeshVars()).length === 0, + }, + { + opcode: "getVar", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate("get [VAR]"), + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + menu: "VARS", + }, + }, + hideFromPalette: Object.keys(getMeshVars()).length === 0, + }, + { + opcode: "setVar", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate("set [VAR] to [VALUE]"), + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + menu: "VARS", + }, + VALUE: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + hideFromPalette: Object.keys(getMeshVars()).length === 0, + }, + { + opcode: "changeVar", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate("change [VAR] by [VALUE]"), + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + menu: "VARS", + }, + VALUE: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + hideFromPalette: Object.keys(getMeshVars()).length === 0, }, - ...(getMeshages().length !== 0 - ? [ - { - func: "remove", - blockType: Scratch.BlockType.BUTTON, - text: Scratch.translate("Remove a Mesh"), - }, - { - opcode: "when", - blockType: Scratch.BlockType.EVENT, - text: Scratch.translate("when I receive mesh [BROADCAST]"), - isEdgeActivated: false, - arguments: { - BROADCAST: { - type: Scratch.ArgumentType.STRING, - menu: "MESHES_NOACCEPT", - }, - }, - }, - { - opcode: "broadcast", - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("broadcast mesh [BROADCAST]"), - arguments: { - BROADCAST: { - type: Scratch.ArgumentType.STRING, - menu: "MESHES_ACCEPT", - }, - }, - }, - ] - : []), ], menus: { MESHES_ACCEPT: { acceptReporters: true, - items: "getMeshages", + items: "getMeshagesForMenu", }, MESHES_NOACCEPT: { acceptReporters: false, - items: "getMeshages", + items: "getMeshagesForMenu", + }, + VARS: { + acceptReporters: true, + items: "getMeshVarsForMenu", }, }, }; } broadcast({ BROADCAST }) { - bc.postMessage({ name: BROADCAST, session: session }); + vm.runtime.startHats("eightxtwoMesh_when", { + BROADCAST: data.name, + }); + bc.postMessage({ + type: "broadcast", + name: BROADCAST, + willWait: false, + }); + } + async broadcastWait({ BROADCAST }) { + vm.runtime.startHats("eightxtwoMesh_when", { + BROADCAST: data.name, + }); + bc.postMessage({ + type: "broadcast", + name: BROADCAST, + willWait: true, + }); + let isDone = false; + while (!isDone) { + if (lastSuccessfulMeshage !== BROADCAST) { + await new Promise((resolve) => resolve()); + } + } + } + + getVar({ VAR }) { + return vm.runtime.extensionStorage.mesh.variables[VAR]; + } + setVar({ VAR, VALUE }) { + vm.runtime.extensionStorage.mesh.variables[VAR] = VALUE; + bc.postMessage({ + type: "var", + key: VAR, + value: VALUE, + }); + } + changeVar({ VAR, VALUE }) { + vm.runtime.extensionStorage.mesh.variables[VAR] = + vm.runtime.extensionStorage.mesh.variables[VAR] = + Scratch.Cast.toNumber( + vm.runtime.extensionStorage.mesh.variables[VAR] + ) + Scratch.Cast.toNumber(VALUE); + bc.postMessage({ + type: "var", + key: VAR, + value: + +Scratch.Cast.toNumber( + vm.runtime.extensionStorage.mesh.variables[VAR] + ) + Scratch.Cast.toNumber(VALUE), + }); } } From b414770a2edf48aa85f0d0f0f627fbc531abdeef Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 14:46:44 +0000 Subject: [PATCH 11/18] 8to16/mesh: stabilised mostly --- extensions/8to16/mesh.js | 70 ++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index c1264bf1b6..33118d001c 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -12,8 +12,10 @@ } const vm = Scratch.vm; + // Init the broadcastchannel const bc = new BroadcastChannel("extensions.turbowarp.org/8to16/mesh"); - let lastSuccessfulMeshage = ""; + let connectedUsers = 0; + let finishedWaits = {}; if (!vm.runtime.extensionStorage.mesh) vm.runtime.extensionStorage.mesh = { messages: [], variables: {} }; @@ -24,7 +26,7 @@ vm.runtime.extensionManager.refreshBlocks(); }); - // Spaghetti ahead! + // Message utilities const getMeshages = () => vm.runtime.extensionStorage?.mesh.messages ?? []; const addMeshage = (name) => { if (getMeshages().includes(name)) return; @@ -40,6 +42,7 @@ vm.extensionManager.refreshBlocks(); }; + // Var utilities const getMeshVars = () => vm.runtime.extensionStorage?.mesh.variables ?? {}; const addMeshVar = (name) => { if (Object.keys(getMeshVars()).includes(name)) return; @@ -58,29 +61,28 @@ vm.extensionManager.refreshBlocks(); }; + // Handle messages on the broadcastchannel bc.onmessage = async ({ data }) => { + console.log(data); switch (data.type) { case "broadcast": { let hats = vm.runtime.startHats("eightxtwoMesh_when", { BROADCAST: data.name, }); + // broadcast and wait handling if (data.willWait) { await new Promise((resolve) => { const poll = () => { if ( - hats.filter( - (thread) => vm.runtime.threads.indexOf(thread) === -1 - ).length === 0 + hats.filter((thread) => vm.runtime.threads.includes(thread)) + .length === 0 ) resolve(); - else setTimeout(poll, 25); + else setTimeout(poll, 5); }; poll(); }); - bc.postMessage({ - type: "waitdone", - name: data.name, - }); + bc.postMessage({ type: "done", name: data.name }); } break; } @@ -88,14 +90,24 @@ vm.runtime.extensionStorage.mesh.variables[data.key] = data.value; break; } - case "waitdone": { - lastSuccessfulMeshage = data.name; + case "done": { + console.log(finishedWaits); + ++finishedWaits[data.name]; + console.log(finishedWaits); + break; + } + case "ping": { + connectedUsers = 0; + bc.postMessage({ type: "pong" }); + break; + } + case "pong": { + connectedUsers += 1; break; } } }; - // dummy message to activate the broadcastchannel - bc.postMessage({ type: "hello" }); + setTimeout(() => bc.postMessage({ type: "ping" }), 50); class Mesh { getMeshagesForMenu() { @@ -284,9 +296,7 @@ } broadcast({ BROADCAST }) { - vm.runtime.startHats("eightxtwoMesh_when", { - BROADCAST: data.name, - }); + vm.runtime.startHats("eightxtwoMesh_when", { BROADCAST }); bc.postMessage({ type: "broadcast", name: BROADCAST, @@ -294,20 +304,20 @@ }); } async broadcastWait({ BROADCAST }) { - vm.runtime.startHats("eightxtwoMesh_when", { - BROADCAST: data.name, - }); + finishedWaits[BROADCAST] = 0; + vm.runtime.startHats("eightxtwoMesh_when", { BROADCAST }); // TODO: add wait here bc.postMessage({ type: "broadcast", name: BROADCAST, willWait: true, }); - let isDone = false; - while (!isDone) { - if (lastSuccessfulMeshage !== BROADCAST) { - await new Promise((resolve) => resolve()); - } - } + await new Promise((resolve) => { + const poll = () => { + if (finishedWaits[BROADCAST] >= connectedUsers) resolve(); + else setTimeout(poll, 25); + }; + poll(); + }); } getVar({ VAR }) { @@ -322,11 +332,9 @@ }); } changeVar({ VAR, VALUE }) { - vm.runtime.extensionStorage.mesh.variables[VAR] = - vm.runtime.extensionStorage.mesh.variables[VAR] = - Scratch.Cast.toNumber( - vm.runtime.extensionStorage.mesh.variables[VAR] - ) + Scratch.Cast.toNumber(VALUE); + vm.runtime.extensionStorage.mesh.variables[VAR] += + Scratch.Cast.toNumber(vm.runtime.extensionStorage.mesh.variables[VAR]) + + Scratch.Cast.toNumber(VALUE); bc.postMessage({ type: "var", key: VAR, From 38fe1e18b45064273cd51645ba90737fc5b399bc Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 14:50:29 +0000 Subject: [PATCH 12/18] 8to16/mesh: ready for review? --- extensions/8to16/mesh.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index 33118d001c..bc5d8202ef 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -63,7 +63,6 @@ // Handle messages on the broadcastchannel bc.onmessage = async ({ data }) => { - console.log(data); switch (data.type) { case "broadcast": { let hats = vm.runtime.startHats("eightxtwoMesh_when", { @@ -91,9 +90,7 @@ break; } case "done": { - console.log(finishedWaits); ++finishedWaits[data.name]; - console.log(finishedWaits); break; } case "ping": { @@ -318,6 +315,7 @@ }; poll(); }); + delete finishedWaits[BROADCAST]; } getVar({ VAR }) { From fcf0e388f16dedb1f96da94a7654860c5954f1d1 Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 14:51:49 +0000 Subject: [PATCH 13/18] 8to16/mesh: bugfix with change var --- extensions/8to16/mesh.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index bc5d8202ef..3ed5e74a5c 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -336,10 +336,7 @@ bc.postMessage({ type: "var", key: VAR, - value: - +Scratch.Cast.toNumber( - vm.runtime.extensionStorage.mesh.variables[VAR] - ) + Scratch.Cast.toNumber(VALUE), + value: vm.runtime.extensionStorage.mesh.variables[VAR], }); } } From 51fc2dc617891aa3bf977988f4ee73e4d2e0c599 Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 14:52:36 +0000 Subject: [PATCH 14/18] 8to16/mesh: standards --- extensions/8to16/mesh.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index 3ed5e74a5c..7c53e4549b 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -17,37 +17,37 @@ let connectedUsers = 0; let finishedWaits = {}; - if (!vm.runtime.extensionStorage.mesh) - vm.runtime.extensionStorage.mesh = { messages: [], variables: {} }; + if (!vm.runtime.extensionStorage["mesh"]) + vm.runtime.extensionStorage["mesh"] = { messages: [], variables: {} }; vm.runtime.on("RUNTIME_DISPOSED", () => { - vm.runtime.extensionStorage.mesh = { messages: [], variables: {} }; + vm.runtime.extensionStorage["mesh"] = { messages: [], variables: {} }; }); vm.runtime.on("PROJECT_LOADED", () => { vm.runtime.extensionManager.refreshBlocks(); }); // Message utilities - const getMeshages = () => vm.runtime.extensionStorage?.mesh.messages ?? []; + const getMeshages = () => vm.runtime.extensionStorage["mesh"].messages ?? []; const addMeshage = (name) => { if (getMeshages().includes(name)) return; if (name === "") return; - vm.runtime.extensionStorage.mesh.messages = [...getMeshages(), name]; + vm.runtime.extensionStorage["mesh"].messages = [...getMeshages(), name]; vm.extensionManager.refreshBlocks(); }; const delMeshage = (name) => { if (!getMeshages().includes(name)) return; - vm.runtime.extensionStorage.mesh.messages = getMeshages().filter( + vm.runtime.extensionStorage["mesh"].messages = getMeshages().filter( (n) => n !== name ); vm.extensionManager.refreshBlocks(); }; // Var utilities - const getMeshVars = () => vm.runtime.extensionStorage?.mesh.variables ?? {}; + const getMeshVars = () => vm.runtime.extensionStorage["mesh"].variables ?? {}; const addMeshVar = (name) => { if (Object.keys(getMeshVars()).includes(name)) return; if (name === "") return; - vm.runtime.extensionStorage.mesh.variables = { + vm.runtime.extensionStorage["mesh"].variables = { ...getMeshVars(), [name]: "", }; @@ -55,7 +55,7 @@ }; const delMeshVar = (name) => { if (Object.keys(getMeshVars()).includes(name)) return; - vm.runtime.extensionStorage.mesh.variables = Object.fromEntries( + vm.runtime.extensionStorage["mesh"].variables = Object.fromEntries( Object.entries(getMeshVars()).filter((v) => v[0] !== name) ); vm.extensionManager.refreshBlocks(); @@ -86,7 +86,7 @@ break; } case "var": { - vm.runtime.extensionStorage.mesh.variables[data.key] = data.value; + vm.runtime.extensionStorage["mesh"].variables[data.key] = data.value; break; } case "done": { @@ -319,10 +319,10 @@ } getVar({ VAR }) { - return vm.runtime.extensionStorage.mesh.variables[VAR]; + return vm.runtime.extensionStorage["mesh"].variables[VAR]; } setVar({ VAR, VALUE }) { - vm.runtime.extensionStorage.mesh.variables[VAR] = VALUE; + vm.runtime.extensionStorage["mesh"].variables[VAR] = VALUE; bc.postMessage({ type: "var", key: VAR, @@ -330,13 +330,14 @@ }); } changeVar({ VAR, VALUE }) { - vm.runtime.extensionStorage.mesh.variables[VAR] += - Scratch.Cast.toNumber(vm.runtime.extensionStorage.mesh.variables[VAR]) + - Scratch.Cast.toNumber(VALUE); + vm.runtime.extensionStorage["mesh"].variables[VAR] += + Scratch.Cast.toNumber( + vm.runtime.extensionStorage["mesh"].variables[VAR] + ) + Scratch.Cast.toNumber(VALUE); bc.postMessage({ type: "var", key: VAR, - value: vm.runtime.extensionStorage.mesh.variables[VAR], + value: vm.runtime.extensionStorage["mesh"].variables[VAR], }); } } From 57be6d496c1a37e78bfe229768b9dd6c99ed7b5b Mon Sep 17 00:00:00 2001 From: 8to16 Date: Sat, 10 Jan 2026 14:53:08 +0000 Subject: [PATCH 15/18] 8to16/mesh: make extensionstorage less ambiguous --- extensions/8to16/mesh.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index 7c53e4549b..ac752a0c59 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -17,37 +17,42 @@ let connectedUsers = 0; let finishedWaits = {}; - if (!vm.runtime.extensionStorage["mesh"]) - vm.runtime.extensionStorage["mesh"] = { messages: [], variables: {} }; + if (!vm.runtime.extensionStorage["8to16mesh"]) + vm.runtime.extensionStorage["8to16mesh"] = { messages: [], variables: {} }; vm.runtime.on("RUNTIME_DISPOSED", () => { - vm.runtime.extensionStorage["mesh"] = { messages: [], variables: {} }; + vm.runtime.extensionStorage["8to16mesh"] = { messages: [], variables: {} }; }); vm.runtime.on("PROJECT_LOADED", () => { vm.runtime.extensionManager.refreshBlocks(); }); // Message utilities - const getMeshages = () => vm.runtime.extensionStorage["mesh"].messages ?? []; + const getMeshages = () => + vm.runtime.extensionStorage["8to16mesh"].messages ?? []; const addMeshage = (name) => { if (getMeshages().includes(name)) return; if (name === "") return; - vm.runtime.extensionStorage["mesh"].messages = [...getMeshages(), name]; + vm.runtime.extensionStorage["8to16mesh"].messages = [ + ...getMeshages(), + name, + ]; vm.extensionManager.refreshBlocks(); }; const delMeshage = (name) => { if (!getMeshages().includes(name)) return; - vm.runtime.extensionStorage["mesh"].messages = getMeshages().filter( + vm.runtime.extensionStorage["8to16mesh"].messages = getMeshages().filter( (n) => n !== name ); vm.extensionManager.refreshBlocks(); }; // Var utilities - const getMeshVars = () => vm.runtime.extensionStorage["mesh"].variables ?? {}; + const getMeshVars = () => + vm.runtime.extensionStorage["8to16mesh"].variables ?? {}; const addMeshVar = (name) => { if (Object.keys(getMeshVars()).includes(name)) return; if (name === "") return; - vm.runtime.extensionStorage["mesh"].variables = { + vm.runtime.extensionStorage["8to16mesh"].variables = { ...getMeshVars(), [name]: "", }; @@ -55,7 +60,7 @@ }; const delMeshVar = (name) => { if (Object.keys(getMeshVars()).includes(name)) return; - vm.runtime.extensionStorage["mesh"].variables = Object.fromEntries( + vm.runtime.extensionStorage["8to16mesh"].variables = Object.fromEntries( Object.entries(getMeshVars()).filter((v) => v[0] !== name) ); vm.extensionManager.refreshBlocks(); @@ -86,7 +91,8 @@ break; } case "var": { - vm.runtime.extensionStorage["mesh"].variables[data.key] = data.value; + vm.runtime.extensionStorage["8to16mesh"].variables[data.key] = + data.value; break; } case "done": { @@ -319,10 +325,10 @@ } getVar({ VAR }) { - return vm.runtime.extensionStorage["mesh"].variables[VAR]; + return vm.runtime.extensionStorage["8to16mesh"].variables[VAR]; } setVar({ VAR, VALUE }) { - vm.runtime.extensionStorage["mesh"].variables[VAR] = VALUE; + vm.runtime.extensionStorage["8to16mesh"].variables[VAR] = VALUE; bc.postMessage({ type: "var", key: VAR, @@ -330,14 +336,14 @@ }); } changeVar({ VAR, VALUE }) { - vm.runtime.extensionStorage["mesh"].variables[VAR] += + vm.runtime.extensionStorage["8to16mesh"].variables[VAR] += Scratch.Cast.toNumber( - vm.runtime.extensionStorage["mesh"].variables[VAR] + vm.runtime.extensionStorage["8to16mesh"].variables[VAR] ) + Scratch.Cast.toNumber(VALUE); bc.postMessage({ type: "var", key: VAR, - value: vm.runtime.extensionStorage["mesh"].variables[VAR], + value: vm.runtime.extensionStorage["8to16mesh"].variables[VAR], }); } } From b1b16c9a08355fe719ebc895cfa6a886f194cc05 Mon Sep 17 00:00:00 2001 From: 8to16 <197376797+8to16@users.noreply.github.com> Date: Sat, 10 Jan 2026 15:38:01 +0000 Subject: [PATCH 16/18] 8to16/mesh: typo docs --- docs/8to16/mesh.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/8to16/mesh.md b/docs/8to16/mesh.md index 4b9b13fe30..09d45e26f1 100644 --- a/docs/8to16/mesh.md +++ b/docs/8to16/mesh.md @@ -18,12 +18,12 @@ The blocks below are equivalent to other blocks in Scratch, but are received by ```scratch when I receive [message v] :: #4cdab2 hat ``` -This block will activate when another project sends *this* project a message with the same name. It is triggered using the block above in any other project. +This block will activate when another project sends *this* project a message with the same name. It is triggered using the block below in any other project. ```scratch broadcast (message v) :: #4cdab2 ``` -This block sends a message to all other projects that have the extension running. It triggers the hat block below in projects that listen for the message. +This block sends a message to all other projects that have the extension running. It triggers the hat block above in projects that listen for the message. ```scratch broadcast (message v) and wait :: #4cdab2 @@ -48,4 +48,4 @@ This block sets the value of a mesh variable in all currently connected projects change (variable v) by (1) :: #4cdab2 ``` -This block increments the value of a mesh variable in all currently connected projects by the specified number. \ No newline at end of file +This block increments the value of a mesh variable in all currently connected projects by the specified number. From 49cf14519b0ff8a19f604d2cb2e1ef7a6490bede Mon Sep 17 00:00:00 2001 From: 8to16 <197376797+8to16@users.noreply.github.com> Date: Sat, 10 Jan 2026 15:43:50 +0000 Subject: [PATCH 17/18] fix typo in image attribution --- images/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/README.md b/images/README.md index c3344c7b7a..15054fff9c 100644 --- a/images/README.md +++ b/images/README.md @@ -326,4 +326,4 @@ All images in this folder are licensed under the [GNU General Public License ver - Created by [@Dillon](https://github.com/DillonRGaming) ## 8to16/mesh.svg - - Creatde by [@kx1bx1](https://github.com/kx1bx1) in https://github.com/TurboWarp/extensions/pull/2382#issuecomment-3731596025 \ No newline at end of file + - Created by [@kx1bx1](https://github.com/kx1bx1) in https://github.com/TurboWarp/extensions/pull/2382#issuecomment-3731596025 From f14c47d3d996392cfe776496c2cdf46e323f5855 Mon Sep 17 00:00:00 2001 From: 8to16 <197376797+8to16@users.noreply.github.com> Date: Sat, 10 Jan 2026 19:17:22 +0000 Subject: [PATCH 18/18] 8to16/mesh: fix bug --- extensions/8to16/mesh.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/8to16/mesh.js b/extensions/8to16/mesh.js index ac752a0c59..41e0152a35 100644 --- a/extensions/8to16/mesh.js +++ b/extensions/8to16/mesh.js @@ -337,9 +337,7 @@ } changeVar({ VAR, VALUE }) { vm.runtime.extensionStorage["8to16mesh"].variables[VAR] += - Scratch.Cast.toNumber( - vm.runtime.extensionStorage["8to16mesh"].variables[VAR] - ) + Scratch.Cast.toNumber(VALUE); + Scratch.Cast.toNumber(VALUE); bc.postMessage({ type: "var", key: VAR,