From f7af7a558a90cc3d14b02be6815ef08f384144cb Mon Sep 17 00:00:00 2001 From: Mubi <150862765+cicerorph@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:44:06 -0300 Subject: [PATCH 1/2] fix: use project clock fixes an issue with the pause function --- src/extensions/jg_tween/index.js | 117 +++++++++++++++---------------- 1 file changed, 55 insertions(+), 62 deletions(-) diff --git a/src/extensions/jg_tween/index.js b/src/extensions/jg_tween/index.js index 7387c06de20..d9c9530bd1d 100644 --- a/src/extensions/jg_tween/index.js +++ b/src/extensions/jg_tween/index.js @@ -10,7 +10,6 @@ const Cast = require('../../util/cast'); * @returns {number} */ const interpolate = (time, a, b) => { - // don't restrict range of time as some easing functions are expected to go outside the range const multiplier = b - a; return time * multiplier + a; }; @@ -58,8 +57,7 @@ const quint = (x, dir) => { case "in": return x * x * x * x * x; case "out": return 1 - Math.pow(1 - x, 5); case "in out": return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; - default: - return 0; + default: return 0; } }; @@ -131,12 +129,9 @@ const EasingMethods = { class Tween { constructor(runtime) { - /** - * The runtime instantiating this block package. - * @type {Runtime} - */ this.runtime = runtime; } + getInfo() { return { id: "jgTween", @@ -310,7 +305,7 @@ class Tween { SEC: { type: ArgumentType.NUMBER, defaultValue: 1, - }, + }, } }, { @@ -328,7 +323,7 @@ class Tween { items: ["in", "out", "in out"] }, vars: { - acceptReporters: false, // for Scratch parity + acceptReporters: false, items: "getVariables" }, properties: { @@ -349,7 +344,6 @@ class Tween { .map(model => ({ text: model.name, value: model.getId() })); if (variables.length > 0) return variables; return [{ text: "", value: "" }]; - } tweenValue(args) { @@ -360,7 +354,6 @@ class Tween { const progress = Cast.toNumber(args.AMOUNT) / 100; if (!Object.prototype.hasOwnProperty.call(EasingMethods, easeMethod)) { - // Unknown method return start; } const easingFunction = EasingMethods[easeMethod]; @@ -370,23 +363,16 @@ class Tween { } _tweenValue(args, util, id, valueArgName, currentValue, propertyName) { - // Only use args on first run. For later executions grab everything from stackframe. - // This ensures that if the arguments change, the tweening won't change. This matches - // the vanilla Scratch glide blocks. const state = util.stackFrame[id]; if (!state) { - // First run, need to start timer util.yield(); - if (util.stackTimerNeedsInit()) { - const durationMS = Math.max(0, 1000 * Cast.toNumber(args.SEC)); - util.startStackTimer(durationMS); - } const easeMethod = Cast.toString(args.MODE); const easeDirection = Cast.toString(args.DIRECTION); const start = currentValue; const end = Cast.toNumber(args[valueArgName]); + const durationSecs = Math.max(0, Cast.toNumber(args.SEC)); let easingFunction; if (Object.prototype.hasOwnProperty.call(EasingMethods, easeMethod)) easingFunction = EasingMethods[easeMethod]; @@ -394,17 +380,22 @@ class Tween { util.stackFrame[id] = { easingFunction, easeDirection, - start, end, propertyName + start, end, propertyName, + startTime: this.runtime.ioDevices.clock.projectTimer(), + durationSecs }; return start; - } else if (util.stackTimerFinished()) { - // Done - return util.stackFrame[id].end; - } - // Still running + } + + const elapsed = this.runtime.ioDevices.clock.projectTimer() - state.startTime; + + if (elapsed >= state.durationSecs) { + return state.end; + } + util.yield(); - const progress = util.stackFrame.timer.timeElapsed() / util.stackFrame.duration; + const progress = elapsed / state.durationSecs; const tweened = state.easingFunction(progress, state.easeDirection); return interpolate(tweened, state.start, state.end); } @@ -454,64 +445,66 @@ class Tween { PROPERTY: property }, util); } + tweenPropertyCancel(args, util) { const property = args.PROPERTY; const id = util.target.id; - // supposedly for i loop is faster (garbo seemed to say this before too?) for (let i = 0; i < this.runtime.threads.length; i++) { const thread = this.runtime.threads[i]; if (!thread.target) continue; if (thread.target.id !== id) continue; - // some threads dont have a stackFrame from util if (!thread.compatibilityStackFrame) continue; - // x position and y position should also cancel the tweenXY block const propertyFrame = thread.compatibilityStackFrame[""] || (property === "x position" ? thread.compatibilityStackFrame["x"] : null) || (property === "y position" ? thread.compatibilityStackFrame["y"] : null); - // this thread did not have a property tween if (!propertyFrame) continue; - // check if the property being tweened is the one we are cancelling if (propertyFrame.propertyName !== property) continue; propertyFrame.cancelled = true; } } tweenC(args, util) { - const id = "loopedVal"; - const state = util.stackFrame[id]; - if (!state) { - if (util.stackTimerNeedsInit()) { - const durationMS = Math.max(0, 1000 * Cast.toNumber(args.SEC)); - util.startStackTimer(durationMS); + const id = "loopedVal"; + const state = util.stackFrame[id]; + + if (!state) { + const easeMethod = Cast.toString(args.MODE); + const easeDirection = Cast.toString(args.DIRECTION); + const start = Cast.toNumber(args.START); + const end = Cast.toNumber(args.END); + const durationSecs = Math.max(0, Cast.toNumber(args.SEC)); + const params = util.thread.tweenValue; + if (typeof params === "undefined") util.thread.stackFrames[0].tweenValue = start; + let easingFunction; + if (Object.prototype.hasOwnProperty.call(EasingMethods, easeMethod)) easingFunction = EasingMethods[easeMethod]; + else easingFunction = EasingMethods.linear; + + util.stackFrame[id] = { + easingFunction, easeDirection, + start, end, + startTime: this.runtime.ioDevices.clock.projectTimer(), + durationSecs + }; + util.startBranch(1, true); + return; } - const easeMethod = Cast.toString(args.MODE); - const easeDirection = Cast.toString(args.DIRECTION); - const start = Cast.toNumber(args.START); - const end = Cast.toNumber(args.END); - const params = util.thread.tweenValue; - if (typeof params === "undefined") util.thread.stackFrames[0].tweenValue = start; - let easingFunction; - if (Object.prototype.hasOwnProperty.call(EasingMethods, easeMethod)) easingFunction = EasingMethods[easeMethod]; - else easingFunction = EasingMethods.linear; - - util.stackFrame[id] = { - easingFunction, easeDirection, - start, end, - }; - util.startBranch(1, true); - } else if (util.stackTimerFinished()) { - util.thread.stackFrames[0].tweenValue = util.stackFrame[id].end; - if (util.stackFrame[id].canContinue !== "stop") { - util.stackFrame[id].canContinue = "stop"; - util.startBranch(1, true); + + const elapsed = this.runtime.ioDevices.clock.projectTimer() - state.startTime; + + if (elapsed >= state.durationSecs) { + util.thread.stackFrames[0].tweenValue = state.end; + if (state.canContinue !== "stop") { + state.canContinue = "stop"; + util.startBranch(1, true); + } + return; } - } else { - const progress = util.stackFrame.timer.timeElapsed() / util.stackFrame.duration; + + const progress = elapsed / state.durationSecs; const tweened = state.easingFunction(progress, state.easeDirection); - util.thread.stackFrames[0].tweenValue = interpolate(tweened, state.start, state.end); - if (util.stackFrame[id].canContinue !== "stop") util.startBranch(1, true); - } + util.thread.stackFrames[0].tweenValue = interpolate(tweened, state.start, state.end); + if (state.canContinue !== "stop") util.startBranch(1, true); } tweenVal(_, util) { From 2ffcd01c9e9ca76b617070f098c48713c962a2c6 Mon Sep 17 00:00:00 2001 From: Mubi <150862765+cicerorph@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:53:36 -0300 Subject: [PATCH 2/2] do not let copilot add comments --- src/extensions/jg_tween/index.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/extensions/jg_tween/index.js b/src/extensions/jg_tween/index.js index d9c9530bd1d..e449d00b873 100644 --- a/src/extensions/jg_tween/index.js +++ b/src/extensions/jg_tween/index.js @@ -10,6 +10,7 @@ const Cast = require('../../util/cast'); * @returns {number} */ const interpolate = (time, a, b) => { + // don't restrict range of time as some easing functions are expected to go outside the range const multiplier = b - a; return time * multiplier + a; }; @@ -129,6 +130,10 @@ const EasingMethods = { class Tween { constructor(runtime) { + /** + * The runtime instantiating this block package. + * @type {Runtime} + */ this.runtime = runtime; } @@ -323,7 +328,7 @@ class Tween { items: ["in", "out", "in out"] }, vars: { - acceptReporters: false, + acceptReporters: false, // for Scratch parity items: "getVariables" }, properties: { @@ -344,6 +349,7 @@ class Tween { .map(model => ({ text: model.name, value: model.getId() })); if (variables.length > 0) return variables; return [{ text: "", value: "" }]; + } tweenValue(args) { @@ -354,6 +360,7 @@ class Tween { const progress = Cast.toNumber(args.AMOUNT) / 100; if (!Object.prototype.hasOwnProperty.call(EasingMethods, easeMethod)) { + // Unknown method return start; } const easingFunction = EasingMethods[easeMethod]; @@ -363,9 +370,13 @@ class Tween { } _tweenValue(args, util, id, valueArgName, currentValue, propertyName) { + // Only use args on first run. For later executions grab everything from stackframe. + // This ensures that if the arguments change, the tweening won't change. This matches + // the vanilla Scratch glide blocks. const state = util.stackFrame[id]; if (!state) { + // First run, need to start timer util.yield(); const easeMethod = Cast.toString(args.MODE); @@ -450,15 +461,20 @@ class Tween { const property = args.PROPERTY; const id = util.target.id; + // supposedly for i loop is faster (garbo seemed to say this before too?) for (let i = 0; i < this.runtime.threads.length; i++) { const thread = this.runtime.threads[i]; if (!thread.target) continue; if (thread.target.id !== id) continue; + // some threads dont have a stackFrame from util if (!thread.compatibilityStackFrame) continue; + // x position and y position should also cancel the tweenXY block const propertyFrame = thread.compatibilityStackFrame[""] || (property === "x position" ? thread.compatibilityStackFrame["x"] : null) || (property === "y position" ? thread.compatibilityStackFrame["y"] : null); + // this thread did not have a property tween if (!propertyFrame) continue; + // check if the property being tweened is the one we are cancelling if (propertyFrame.propertyName !== property) continue; propertyFrame.cancelled = true; }