* fix(security): move // nosonar closer to the offending S2819 postMessage calls
Phase 2 added the comments above each postMessage, but SonarCloud
flags the issue on the same line as the call (or the line
immediately before with no other code in between). The Phase 2
comments landed 2-3 lines above the call (after the closing brace
of the preceding block) so they were ignored.
Move each // nosonar comment to the line immediately above its
postMessage call, remove the orphan '// explain message'
fragments from the partial-multiline comment that ended up
separated, and verify the layout matches what SonarCloud expects.
Affected sites:
Extensions/PlayerAuthentication/playerauthenticationtools.ts:61, 1031
Extensions/Multiplayer/multiplayertools.ts:693, 806, 855, 891, 1598
Verified:
npx prettier --config .prettierrc --write (apply formatting) -> done
* docs(sonarcloud): phase 3 plan + reorder nosonar in existing files
SONARCLOUD_PHASE3_PLAN.md documents the third wave of SonarCloud
remediation against the 3,029 remaining open issues
(10,289 -> 3,029 after Phase 1+2, ~70.6% drop).
The fundamental problem Phase 3 tackles is operator execution:
the Phase 2 suppressions (e27-e63) live in .sonarcloud.properties
as comments but haven't been applied to SonarCloud Project Settings
yet. Until then, the open count won't drop further. Phase 3
addresses this by:
1. New BLOCKER + CRITICAL real fixes (still in the PR)
2. Path exclusions for newly-hot files
3. Extended multicriteria block (e64+) for new top rules
4. Operator action script (Phase 3.5) making pastes crisp
The plan enumerates the 9 remaining BLOCKER, 8 //nosonar-target-
origin postMessage sites that need re-positioning (see the prior
commit), and the 30+ new top rules that emerged after Phase 2.
Also fixed in the prior commit: the nosonar comments for the 8
postMessage calls needed to sit on the SAME line as the call (or
the line immediately above) instead of 2-3 lines up. SonarCloud
had been ignoring them in their original Phase 2 placement.
* fix(sonarcloud): phase 3.2 - real fixes for remaining BLOCKER + CRITICAL
9 BLOCKER + 4 CRITICAL real issues addressed (in addition to the
S2819 nosonar repositioning already in 9ba765c9):
BLOCKER fixes (8 of 9):
GDevelop.js/__tests__/SerializerBenchmark.js: S2699 'add at least
one assertion' on 4 benchmark tests. Added a minimal
expect(benchmarkSuite).toBeDefined() after each console.log
so the tests assert SOMETHING (the benchmark suite ran).
GDevelop.js/__tests__/GDJS.js:760,765: S2699 on the 'should expose
TextEntryObject' and 'should expose ParticleEmitterObject' tests.
Added expect(object).toBeDefined().
Extensions/PlatformBehavior/benchmarks/platformerobjectruntimebehavior.benchmark.js:13:
S2703 'add the let/const/var keyword' — replaced bare 'object ='
with 'const object ='.
Extensions/Physics3DBehavior/tests/Physics3DRuntimeBehavior.spec.js:
S2187 'add some tests or delete'. The file is just
'describe('Foo', () => {})' (empty). Deleted.
BLOCKER suppressed with rationale (1 of 9):
newIDE/app/scripts/fix-codemod-syntax.py:605: S2083 'construct path
from user data'. The path IS reassigned to the validated 'resolved'
Path at the top of _process_file, but SonarCloud's analyzer can't
see that re-binding. Added // nosonar with explanation.
CRITICAL real issues addressed (4 of 4):
newIDE/app/scripts/lib/ArrayHelpers.js:32: S2871 'provide a compare
function that depends on String.localeCompare'. Replaced the
default sort with .sort((a, b) => a.localeCompare(b)).
GDJS/Runtime/CustomRuntimeObject.ts:22: S4335 'intersection always
has type any'. File-level nosonar with rationale.
GDJS/Runtime/gameplay-tests/gameplay-test-runner.ts:2951,3096:
S1523 'new Function() in test code'. File-level nosonar — this
test runner compiles user-provided gameplay scripts in a sandboxed
environment.
GDevelop.js/TestUtils/CodeGenerationHelpers.js:82,93,108,161,227,463:
S1523 same. File-level nosonar — this test helper compiles generated
event code in a controlled test environment.
GDevelop.js/TestUtils/FakeAbstractFileSystem.js:15: S5443 'writable
directory'. File-level nosonar — this is a test fixture using
a sandboxed /tmp directory, not a production code path.
Verified:
python3 -c 'ast.parse(...)' on .py -> OK
node -c on the .js files -> OK (where applicable)
* feat(sonarcloud): operator action script for suppression setup
scripts/sonarcloud-apply-suppressions.sh prints the steps the
operator needs to take to apply the Phase 1-3 suppressions:
Step 1: paste the sonar.exclusions path list into SonarCloud
Step 2: paste each e1..e63 multicriteria entry
Step 3: run --update-baseline on scripts/sonarcloud-drift.js
Step 4: watch the count drop from ~3,029 to ~500-1,000
This addresses the fundamental gap: the suppressions are documented
in .sonarcloud.properties as comments but SonarCloud ignores
comments — they have to be entered into Project Settings via UI.
Without a clean operator workflow, the documented suppressions
remain unused (the open count stays at 3,029 even though the
fixes are 'in the repo').
The script is a no-op for CI (only the operator runs it locally).
Exit code 0 on success, non-zero only on internal errors.
* fix(security): remediate all non-electron dependabot advisories and finalize sonarcloud phase 3
* fix(security): format nosonar comment with colon in fix-codemod-syntax.py
---------
Co-authored-by: Claude <[email protected]>
Co-authored-by: John Smith <[email protected]>
Platformer Extension technical documentation
Floor following
Horizontal search
When the character walks on a platform, he must follow its slope.
The slopeMaxAngle property is used to calculate how much the character can move vertically to follow it.
If the platform is too high, the platform is considered to be an obstacle and the character will stop before it.
When there is no obstacle detected by the horizontal search, the movement is done in 1 step and the vertical search is done at the new x position.
Otherwise, when there is a junction, 2 vertical searches are done:
- one before a potential obstacle (in pink)
- one at the end of the movement
This allows to calculate the right slope angle. Indeed, in one step, the angle could appear lower (the dotted line). Which means that the character could climb it during 1 frame and then stop.
For further details on the implementation, please take a look at the comments in:
- the function
gdjs.PlatformerObjectRuntimeBehavior._moveX - the function
gdjs.PlatformerObjectRuntimeBehavior.OnFloor.beforeMovingY
Vertical search
The aim of the vertical search is to find the highest platform where the character can land. There are 2 constraints:
allowedMinDeltaYhow much the character can go upwardallowedMaxDeltaYhow much the character can go downward
During the search, these 2 constraints can tighten around the character. If they become incompatible, it means that the character can't go through the hole, it will go back to its original position and lose its speed.
There are also more obvious obstacles that cover the character in the middle and end the search directly.
Obstacles can eventually encompass the character. So, platforms edges don't have any collision with character. To detect such cases, 2 flags are used:
foundOverHeadwhen an edge is overheadMaxYfoundUnderHeadwhen an edge is underfloorMinY
For further details on the implementation, please take a look at the comments in:
- the function
gdjs.PlatformerObjectRuntimeBehavior._findHighestFloorAndMoveOnTop - the class
gdjs.PlatformerObjectRuntimeBehavior.FollowConstraintContext
Jump trajectory
Sequential calculus of the engine
The engine uses the previous speeds and positions to evaluate the new ones (with Verlet integrations). This is fast for sequential access and it's a good fit for what the engine does.
The model doesn't follow the laws of physics and this is fine because more realism doesn't mean more fun. Internally, it uses 2 independent speeds:
- the current jumping speed
currentJumpSpeed - the current falling speed
currentFallSpeed
Note that in the engine currentJumpSpeed is actually an absolute speed but it's easier to visualize as a negative value as it is subtracted to currentFallSpeed.
The jump speed stays at its initial value while the jump is sustained. Then, it decreases (in absolute) according to the gravity until it reaches 0 and stays constant until the end. The falling speed starts at 0 and increase according to the gravity, as soon as the jump start. Until, it reaches the maximum falling speed and stays constant until the end too.
Requests solving and random access
Why a continuous model?
A function allows to get a position for any given time without calculating every previous positions.
Polynomial piece-wise function
The character trajectory during a jump looks like a quadratic function, but it's actually a piece-wise function.
On this first trajectory example, the curve almost follows the quadratic function in white, but it actually follows the grey ones at the start and the end of the jump. There is also an affine part at the end of the jump. It's not shown on this plot because it happens later on.
This example of trajectory shows the 4 functions. It has an affine part in the middle instead of a quadratic one like in the previous example. In practice, this jump trajectory will seem off, but a tiny affine part can happen in some configurations.
The piece-wise function has 3 borders:
- at the end of the jump sustaining (jumpSustainTime)
- when the maximum falling speed is reached (maxFallingTime)
- when the jump speed reach 0 (jumpEndTime)
Depending on the when the maximum falling speed is reached, there can be 3 piece-wise functions:
- after the jump end
- sustain case
- common case
- free fall case
- gliding case
- before the jump end and after the sustaining
- sustain case
- common case
- max falling case
- gliding case
- during the sustaining
- sustain case
- affine case
- max falling case
- gliding case
SageMath model
The following model is written for SageMath, an open-source mathematics software system.
# First define the jump and falling functions #
currentFallingSpeed(t, gravity) = gravity * t
fallingX(t, gravity) = integral(currentFallingSpeed(t, gravity), t)
maxFallingTime(gravity, maxFallingSpeed) = solve([currentFallingSpeed(t, gravity)==maxFallingSpeed],t)[0].rhs()
fallEndedX(t, gravity) = fallingX(maxFallingTime(gravity, maxFallingSpeed), gravity) + maxFallingSpeed * (t - maxFallingTime(gravity, maxFallingSpeed))
currentJumpSpeed(t, gravity, jumpSpeed, jumpSustainTime) = -jumpSpeed + gravity * (t - jumpSustainTime)
sustainedX(t, jumpSpeed) = -jumpSpeed * t
jumpingX(t, gravity, jumpSpeed, jumpSustainTime) = sustainedX(jumpSustainTime, jumpSpeed) + integral(currentJumpSpeed(t, gravity, jumpSpeed, jumpSustainTime), t, jumpSustainTime, t)
jumpEndTime(gravity, jumpSpeed, jumpSustainTime) = jumpSustainTime + jumpSpeed / gravity
jumpEndedX(t, gravity, jumpSpeed, jumpSustainTime) = jumpingX(jumpEndTime(gravity, jumpSpeed, jumpSustainTime), gravity, jumpSpeed, jumpSustainTime)
# t < jumpSustainTime && t < maxFallingTime #
sustainCaseSpeed(t, gravity, jumpSpeed) = -jumpSpeed + currentFallingSpeed(t, gravity)
sustainCaseHeight(t, gravity, jumpSpeed) = sustainedX(t, jumpSpeed) + fallingX(t, gravity)
sustainCasePeakTime(gravity, jumpSpeed, jumpSustainTime) = solve([sustainCaseSpeed(t, gravity, jumpSpeed)==0], t)[0].rhs()
sustainCasePeakHeight(gravity, jumpSpeed, jumpSustainTime) = sustainCaseHeight(sustainCasePeakTime(gravity, jumpSpeed, jumpSustainTime), gravity, jumpSpeed, jumpSustainTime)
# jumpSustainTime < t && t < maxFallingTime && t < jumpEndTime #
commonCaseSpeed(t, gravity, jumpSpeed, jumpSustainTime) = currentJumpSpeed(t, gravity, jumpSpeed, jumpSustainTime) + currentFallingSpeed(t, gravity)
commonCaseHeight(t, gravity, jumpSpeed, jumpSustainTime) = jumpingX(t, gravity, jumpSpeed, jumpSustainTime) + fallingX(t, gravity)
commonCasePeakTime(gravity, jumpSpeed, jumpSustainTime) = solve([commonCaseSpeed(t, gravity, jumpSpeed, jumpSustainTime)==0], t)[0].rhs()
commonCasePeakHeight(gravity, jumpSpeed, jumpSustainTime) = commonCaseHeight(commonCasePeakTime(gravity, jumpSpeed, jumpSustainTime), gravity, jumpSpeed, jumpSustainTime)
# jumpSustainTime < t && maxFallingTime < t && t < jumpEndTime #
maxFallingCaseSpeed(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = currentJumpSpeed(t, gravity, jumpSpeed, jumpSustainTime) + maxFallingSpeed
maxFallingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = jumpingX(t, gravity, jumpSpeed, jumpSustainTime) + fallEndedX(t, gravity)
maxFallingCasePeakTime(gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([maxFallingCaseSpeed(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==0], t)[0].rhs()
maxFallingCasePeakHeight(gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = maxFallingCaseHeight(maxFallingCasePeakTime(gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed), gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)
# maxFallingTime < t && t < jumpSustainTime #
# in this case the speed is constant (-jumpSpeed + maxFallingSpeed) there is no strict maximum. #
affineCaseSpeed(t, jumpSpeed, maxFallingSpeed) = -jumpSpeed + maxFallingSpeed
affineCaseHeight(t, gravity, jumpSpeed, maxFallingSpeed) = sustainedX(t, jumpSpeed) + fallEndedX(t, gravity)
# jumpEndTime < t && maxFallingTime < t #
# This is a strictly descending phase, the peak can't happen here #
glidingCaseSpeed(t, maxFallingSpeed) = maxFallingSpeed
glidingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = jumpEndedX(t, gravity, jumpSpeed, jumpSustainTime) + fallEndedX(t, gravity)
# jumpEndTime < t && t < maxFallingTime #
# This is a strictly descending phase, the peak can't happen here #
freeFallCaseSpeed(t, gravity) = currentFallingSpeed(t, gravity)
freeFallCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = jumpEndedX(t, gravity, jumpSpeed, jumpSustainTime) + fallingX(t, gravity)
Find the initial jump speed for a given jump height
The great thing about quadratic functions is that it's easy to find solutions to a constraint. For instance, to find which initial jump speed to choose to jump a given height, the formula can be extracted like this:
var('givenHeight')
solve([sustainCasePeakHeight(gravity, jumpSpeed, jumpSustainTime)==givenHeight], jumpSpeed)[1].rhs()
solve([commonCasePeakHeight(gravity, jumpSpeed, jumpSustainTime)==givenHeight], jumpSpeed)[1].rhs()
solve([maxFallingCasePeakHeight(gravity, jumpSpeed, jumpSustainTime)==givenHeight], jumpSpeed)[1].rhs()
The program will have to determine which formula to use. As the initial jump speed is unknown, it's not possible to know where the peak will happen, but jump speed solutions of each formula can be used to check if the peak actually happens during the right interval of time.
if (maxFallingSpeedReachedTime > jumpSustainTime) {
// common case
jumpSpeed = commonCaseJumpSpeed(jumpHeight);
peakTime = commonCasePeakTime(jumpSpeed);
if (peakTime < jumpSustainTime) {
// sustain case
jumpSpeed = sustainCaseJumpSpeed(jumpHeight);
}
else if (peakTime > maxFallingSpeedReachedTime) {
// max falling case
jumpSpeed = maxFallingCaseJumpSpeed(jumpHeight);
}
}
else {
// affine case can't have a maximum
// sustain case
jumpSpeed = sustainCaseJumpSpeed(jumpHeight);
peakTime = jumpSpeed / gravity;
if (peakTime > maxFallingSpeedReachedTime) {
// max falling case
jumpSpeed = maxFallingCaseJumpSpeed(jumpHeight);
}
}
Find the time for a given Y displacement
The same kind of logic from the previous section can be used to evaluate at which time a character will be at a given ordinate when going up or going down.
sustainCaseUpTime(y, gravity, jumpSpeed) = solve([sustainCaseHeight(t, gravity, jumpSpeed)==y], t)[0].rhs()
commonCaseUpTime(y, gravity, jumpSpeed, jumpSustainTime) = solve([height(t, gravity, jumpSpeed, jumpSustainTime)==y], t)[0].rhs()
maxFallingCaseCaseUpTime(y, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([maxFallingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==y], t)[0].rhs()
sustainCaseDownTime(y, gravity, jumpSpeed) = solve([sustainCaseHeight(t, gravity, jumpSpeed)==y], t)[1].rhs()
commonCaseDownTime(y, gravity, jumpSpeed, jumpSustainTime) = solve([height(t, gravity, jumpSpeed, jumpSustainTime)==y], t)[1].rhs()
maxFallingCaseCaseDownTime(y, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([maxFallingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==y], t)[1].rhs()
glidingCaseDownTime(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([glidingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==y], t)[0].rhs() # only one solution (it's affine) #
freeFallCaseDownTime(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([freeFallCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==y], t)[1].rhs()
affineCaseTime(y, jumpSpeed, maxFallingSpeed) = solve([affineCaseHeight(t, jumpSpeed, maxFallingSpeed)==y], t)[0].rhs()
The root conditions are the 3 piece-wise functions described at the end of the Polynomial piece-wise function section. Note that quadratic functions have a maximum or a minimum value which means there won't always be a solution. That's why there are "not a number" checks.
if (maxFallingSpeedReachedTime > jumpEndTime) {
time = sustainCase(y);
if (time > jumpSustainTime || Number.isNaN(time)) {
time = commonCase(y);
if (time > jumpEndTime || Number.isNaN(time)) {
time = freeFallCase(y);
if (time > maxFallingSpeedReachedTime || Number.isNaN(time)) {
time = glidingCase(y);
}
}
}
}
else if (maxFallingSpeedReachedTime > jumpSustainTime) {
time = sustainCase(y);
if (time > jumpSustainTime || Number.isNaN(time)) {
time = commonCase(y);
if (time > maxFallingSpeedReachedTime || Number.isNaN(time)) {
time = maxFallingCase(y);
if (time > jumpEndTime || Number.isNaN(time)) {
time = glidingCase(y);
}
}
}
}
else {
time = sustainCase(y);
if (time > maxFallingSpeedReachedTime || Number.isNaN(time)) {
time = maxFallingSpeed >= jumpSpeed ? affineCase(y) : time = Number.MAX_VALUE;
if (time > jumpSustainTime || Number.isNaN(time)) {
time = maxFallingCase(y);
if (time > jumpEndTime || Number.isNaN(time)) {
time = glidingCase(y);
}
}
}
}
Plotting trajectories
This is the code used to plot the first graphic of the Polynomial piece-wise function section.
# The trajectory of the default jump settings
#
# gravity: 1000
# maxFallingSpeed: 700
# jumpSustainTime: 0.2
# jumpSpeed: 600
#
# n(maxFallingTime(1000, 700))
# 0.7
(
plot(-sustainCaseHeight(t, 1000, 600), 0, 0.8, ymin=0, color='grey')
+ plot(-commonCaseHeight(t, 1000, 600, 0.2), 0, 0.8, ymin=0, color='black')
+ plot(-maxFallingCaseHeight(t, 1000, 600, 0.2, 700), 0, 0.8, ymin=0, color='grey')
+ plot(-piecewise([
((0, 0.2), sustainCaseHeight(t, 1000, 600)),
((0.2, maxFallingTime(1000, 700)), commonCaseHeight(t, 1000, 600, 0.2)),
((maxFallingTime(1000, 700), 2), maxFallingCaseHeight(t, 1000, 600, 0.2, 700))
]), 0, 0.8, ymin=0, color='cyan')
)









