Skip to content

fix: Path2D arc continuity, Rect geometry, loader asset hygiene (audit mediums)#1540

Merged
obiot merged 1 commit into
masterfrom
fix/bug-hunt-mediums-loader-geometry
Jul 5, 2026
Merged

fix: Path2D arc continuity, Rect geometry, loader asset hygiene (audit mediums)#1540
obiot merged 1 commit into
masterfrom
fix/bug-hunt-mediums-loader-geometry

Conversation

@obiot

@obiot obiot commented Jul 5, 2026

Copy link
Copy Markdown
Member

Batch 2 of the bug-hunt fixes — the loader/geometry mediums, all test-first (8 of 11 new tests red on the old code, plus one updated existing test).

Path2D.arc()/arcTo()/ellipse() started a hole instead of connecting

Per the native Path2D spec, an arc appended to a non-empty path is connected to the current point with a straight linearcTo's own JSDoc even promises it. All three methods instead called moveTo() unconditionally. Under this release's multi-sub-path fill semantics (#1253), that stray sub-path is a hole: an arc appended to a filled outline punched a hole in the shape. Worth landing before 19.9 ships. (The SVG A parser had been quietly working around this — its comment even said "not ellipse/arc which call moveTo and break path continuity".)

Fix: a connectTo helper — lineTo when the path has content or the pen was explicitly placed, moveTo only on a virgin path. A new penMoved flag (reset by beginPath) distinguishes "virgin path" from "pen parked at (0,0)", so moveTo(P); arc(...) keeps its native connecting line. Preserved behaviors, each with a test: virgin-path arc() starts cleanly (no spurious origin line), the canonical circle-hole idiom (moveTo before arc()) still registers a hole via lineTo's gap detection, roundRect() stays a single sub-path, explicit moveTo holes still register.

One existing #1253 adversarial test ("a far disjoint arc() mid-path records a pen-up boundary") codified the old auto-detach accident — updated to assert native connect semantics, with a new companion test for the moveTo hole idiom. (rect() mid-path correctly keeps its boundary — native rect() does open a sub-path.)

Rect geometry

  • right/bottom: this.left + w || w — an edge sitting exactly at coordinate 0 is falsy, so the getter returned the width/height. Now plain arithmetic.
  • toPolygon(): passed the rect's live Vector2d[] into the pooled polygon — Polygon.setVertices stores vector arrays by reference, so transforming the "new" polygon corrupted the rectangle. Vertices are now cloned.

Loader asset hygiene

  • load() mutated the caller's asset descriptor: the resolved URL (fontface url() unwrap + baseURL prefix) was written back into asset.src, so loader.reload() — which re-loads the same stored object — or simply load()ing the same manifest entry twice fetched base + base + src. The URL is now resolved into a shallow copy handed to the parser; caches, LOADER_ERROR payloads and failure tracking all keep the caller's original src (which also makes reload(src) keys consistent).
  • unload() of a never-loaded fontface threw: FontFaceSet.delete(undefined) is a WebIDL TypeError, not a false return — the fontface case was the only one missing the membership guard every other type has.

Test plan

  • 3 new spec files (11 tests) written failing-first: 8 red on old code, all green after
  • 1 stale adversarial test updated to native semantics (+1 new hole-idiom companion)
  • Full suite: 4609 passed / 0 failed / 15 skipped; eslint + biome + build clean

🤖 Generated with Claude Code

https://claude.ai/code/session_019t8kP8vp58ZrvaD2FqJU6A

…metry, loader hygiene

Path2D (geometries/path2d.ts): arc()/arcTo()/ellipse() appended to an
open path called moveTo() unconditionally, silently starting a new
sub-path — which the 19.9 multi-sub-path fill semantics treat as a HOLE
punched in the shape. Per the native Path2D spec they now CONNECT to
the current point with a straight line (arcTo's own JSDoc promised it);
a new penMoved flag distinguishes a virgin path (arc starts fresh) from
an explicit moveTo (native connecting-line behavior), so the canonical
circle-hole idiom (moveTo before arc) still registers a hole and
roundRect stays a single sub-path. The SVG `A` parser had been working
around this internally. One #1253 adversarial test codified the old
auto-detach accident — updated to assert native semantics plus the
moveTo hole idiom.

Rect (geometries/rectangle.ts):
- right/bottom computed `this.left + w || w` — an edge exactly at
  coordinate 0 is falsy and returned the size instead
- toPolygon() passed the rect's live Vector2d array into the pooled
  polygon (setVertices stores Vector2d[] by reference) — transforming
  the polygon corrupted the rectangle; vertices are now cloned

Loader (loader/loader.js):
- load() wrote the resolved URL (fontface url() unwrap + baseURL
  prefix) back into asset.src, so loader.reload() — or load()ing the
  same manifest object twice — fetched base+base+src. The URL is now
  resolved into a copy handed to the parser; caches, events and
  failure tracking keep the caller's original src
- unload() of a never-loaded fontface threw (FontFaceSet.delete is
  WebIDL-typed; undefined is a TypeError, not false) — now guarded
  like every other asset type

Specs written failing-first: 8 red on the old code across
path2d-arc-connect / rect-geometry / loader-asset-src, all green after.
Full suite 4609 passed / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019t8kP8vp58ZrvaD2FqJU6A
Copilot AI review requested due to automatic review settings July 5, 2026 08:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 several correctness issues uncovered during a “bug-hunt” audit across Path2D arc continuity, Rect geometry utilities, and loader asset lifecycle behavior, and adds/updates tests to lock in the corrected semantics (notably aligning Path2D arc/arcTo/ellipse behavior with native Path2D continuity rules).

Changes:

  • Fix Path2D arc(), arcTo(), and ellipse() to connect to the current point (via a new connectTo helper + penMoved tracking) instead of incorrectly starting new sub-paths.
  • Fix Rect.right/Rect.bottom origin-edge correctness and ensure Rect.toPolygon() returns an independent polygon (no shared live vertex array).
  • Prevent loader.load() from mutating caller asset descriptors (avoids double-prepending baseURL on retries) and make loader.unload() for never-loaded fontface return false instead of throwing.

Reviewed changes

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

Show a summary per file
File Description
packages/melonjs/src/geometries/path2d.ts Implements native-style arc/ellipse/arcTo continuity via connectTo + penMoved, preventing unintended hole sub-paths.
packages/melonjs/tests/path2d.spec.js Updates an adversarial test to assert native arc connection semantics and adds a companion hole-idiom test.
packages/melonjs/tests/path2d-arc-connect.spec.ts Adds focused spec coverage for arc/ellipse/arcTo continuity, virgin-path behavior, and hole/sub-path expectations.
packages/melonjs/src/geometries/rectangle.ts Fixes right/bottom arithmetic and prevents toPolygon() from sharing rect vertices by reference.
packages/melonjs/tests/rect-geometry.spec.ts Adds regression tests for right/bottom at coordinate 0 and toPolygon() vertex independence.
packages/melonjs/src/loader/loader.js Resolves effective src without mutating the caller asset object; adds fontface unload membership guard.
packages/melonjs/tests/loader-asset-src.spec.js Adds regression tests for load() not mutating asset.src and safe fontface unload behavior.
packages/melonjs/CHANGELOG.md Documents the fixed behaviors for Path2D, Rect, and loader.

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

@obiot obiot merged commit b367646 into master Jul 5, 2026
7 checks passed
@obiot obiot deleted the fix/bug-hunt-mediums-loader-geometry branch July 5, 2026 08:57
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.

2 participants