diff --git a/test/CoordinateSystem/README.md b/test/CoordinateSystem/README.md new file mode 100644 index 000000000..28e393e3b --- /dev/null +++ b/test/CoordinateSystem/README.md @@ -0,0 +1,30 @@ +# SPX coordinate-system regression demo + +This demo exercises the coordinate ownership introduced by: + +- goplus/spx#1702 +- goplus/godot#327 +- goplus/spx#1703 + +Expected behavior: + +1. The stage center is SPX `(0, 0)`, `+X` points right, and `+Y` points up. +2. The black cross is the sprite's logical root. Pressing `Space` cycles the + same SVG through three costume centers. The selected colored anchor must + stay on the black cross while the rendered SVG moves around it. + The SVG source dimensions are declared explicitly as `120 × 80`, so each + center is interpreted in the same source-image pixel coordinate space. +3. `R` rotates around the logical root. `F` flips left/right around the same + root. `N` restores normal rotation. +4. `P` draws a cyan pen segment in SPX `+Y`, so the segment must go upward. +5. `S` stamps the current rendered costume and then moves the live sprite to + the right. The stamp must remain exactly where the rendered costume was. +6. Arrow keys move both the logical root and its rendering together. `C` + resets the demo and `E` clears pen drawings and stamps. + +Run with the native SPX runtime: + +```sh +cd test/CoordinateSystem +spx runnative +``` diff --git a/test/CoordinateSystem/RootMarker.spx b/test/CoordinateSystem/RootMarker.spx new file mode 100644 index 000000000..c33bd7a9a --- /dev/null +++ b/test/CoordinateSystem/RootMarker.spx @@ -0,0 +1,2 @@ +// The marker is a separate sprite at Subject's logical SPX position. It must +// not inherit Subject's costume render offset, rotation, or horizontal flip. diff --git a/test/CoordinateSystem/Subject.spx b/test/CoordinateSystem/Subject.spx new file mode 100644 index 000000000..58e7bd0ac --- /dev/null +++ b/test/CoordinateSystem/Subject.spx @@ -0,0 +1,143 @@ +import "fmt" +import "math" + +const coordinateStep = 40.0 + +var ( + expectedX = 0.0 + expectedY = 100.0 + flipped bool +) + +func syncLogicalRoot() { + RootMarker.SetXYpos xpos, ypos +} + +func acceptLogicalPosition() { + // Motion can be clamped by the stage fence. Use the resulting logical SPX + // position as the baseline for later costume/rotation invariants. + expectedX, expectedY = xpos, ypos +} + +func verifyLogicalPosition(action string) { + if math.Abs(xpos-expectedX) > 0.001 || math.Abs(ypos-expectedY) > 0.001 { + msg := fmt.Sprintf("FAIL %s: got (%.1f, %.1f), want (%.1f, %.1f)", action, xpos, ypos, expectedX, expectedY) + println msg + say msg + return + } + println fmt.Sprintf("PASS %s: logical SPX position = (%.1f, %.1f)", action, xpos, ypos) +} + +func reportState(action string) { + verifyLogicalPosition action + syncLogicalRoot + say fmt.Sprintf("%s\nanchor=%s\nSPX=(%.0f, %.0f)", action, costumeName, xpos, ypos) +} + +func resetCoordinateDemo() { + expectedX, expectedY = 0, 100 + setXYpos expectedX, expectedY + setRotationStyle Normal + setHeading 90 + setCostume 0 + flipped = false + reportState "reset" +} + +onStart => { + setPenSize 4 + setPenColor HSB(52, 80, 90) + resetCoordinateDemo + + // Exercise each asset-space center once. Only RenderRoot should move. + for i := 0; i < 3; i++ { + setCostume i + reportState "startup center conversion" + wait 0.8s + } + setCostume 0 + reportState "ready" +} + +onClick => { + setCostume Next + reportState "click: next costume center" +} + +onKey KeySpace, => { + setCostume Next + reportState "next costume center" +} + +onKey KeyUp, => { + changeYpos coordinateStep + acceptLogicalPosition + reportState "move +Y" +} + +onKey KeyDown, => { + changeYpos -coordinateStep + acceptLogicalPosition + reportState "move -Y" +} + +onKey KeyLeft, => { + changeXpos -coordinateStep + acceptLogicalPosition + reportState "move -X" +} + +onKey KeyRight, => { + changeXpos coordinateStep + acceptLogicalPosition + reportState "move +X" +} + +onKey KeyR, => { + setRotationStyle Normal + turn 30 + reportState "rotate around logical root" +} + +onKey KeyF, => { + setRotationStyle LeftRight + if flipped { + setHeading 90 + } else { + setHeading -90 + } + flipped = !flipped + reportState "flip around logical root" +} + +onKey KeyN, => { + setRotationStyle Normal + setHeading 90 + flipped = false + reportState "normal rotation" +} + +onKey KeyP, => { + penDown + changeYpos 80 + penUp + acceptLogicalPosition + reportState "pen moved +Y" +} + +onKey KeyS, => { + stamp + changeXpos 140 + acceptLogicalPosition + reportState "stamp then move +X" +} + +onKey KeyC, => { + resetCoordinateDemo +} + +onKey KeyE, => { + eraseAll + reportState "erase pen and stamps" +} diff --git a/test/CoordinateSystem/assets/coordinate-grid.svg b/test/CoordinateSystem/assets/coordinate-grid.svg new file mode 100644 index 000000000..270f95c74 --- /dev/null +++ b/test/CoordinateSystem/assets/coordinate-grid.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + +X + +Y + -Y + SPX (0, 0) + start: (0, 100) + + + + + Coordinate regression controls + Space/click: center · Arrows: logical position + R/F/N: rotate/flip/normal · P: pen +Y + S: stamp+move · C: reset · E: erase + + + + + Costume center + geometric (60, 40) + asset (20, 20) + asset (100, 60) + + + + diff --git a/test/CoordinateSystem/assets/index.json b/test/CoordinateSystem/assets/index.json new file mode 100644 index 000000000..76309492a --- /dev/null +++ b/test/CoordinateSystem/assets/index.json @@ -0,0 +1,28 @@ +{ + "run": { + "title": "SPX Coordinate System", + "width": 640, + "height": 480 + }, + "backdrops": [ + { + "name": "coordinate-grid", + "path": "coordinate-grid.svg" + } + ], + "backdropIndex": 0, + "zorder": [ + { + "type": "sprite", + "target": "Subject", + "x": 0, + "y": 100 + }, + { + "type": "sprite", + "target": "RootMarker", + "x": 0, + "y": 100 + } + ] +} diff --git a/test/CoordinateSystem/assets/sprites/RootMarker/index.json b/test/CoordinateSystem/assets/sprites/RootMarker/index.json new file mode 100644 index 000000000..aa71246af --- /dev/null +++ b/test/CoordinateSystem/assets/sprites/RootMarker/index.json @@ -0,0 +1,21 @@ +{ + "costumes": [ + { + "bitmapResolution": 1, + "imageWidth": 24, + "imageHeight": 24, + "name": "logical-root", + "path": "root-marker.svg", + "x": 12, + "y": 12 + } + ], + "costumeIndex": 0, + "heading": 90, + "isDraggable": false, + "rotationStyle": "none", + "size": 1, + "visible": true, + "x": 0, + "y": 100 +} diff --git a/test/CoordinateSystem/assets/sprites/RootMarker/root-marker.svg b/test/CoordinateSystem/assets/sprites/RootMarker/root-marker.svg new file mode 100644 index 000000000..ea4378ea1 --- /dev/null +++ b/test/CoordinateSystem/assets/sprites/RootMarker/root-marker.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/test/CoordinateSystem/assets/sprites/Subject/index.json b/test/CoordinateSystem/assets/sprites/Subject/index.json new file mode 100644 index 000000000..563441507 --- /dev/null +++ b/test/CoordinateSystem/assets/sprites/Subject/index.json @@ -0,0 +1,42 @@ +{ + "costumes": [ + { + "bitmapResolution": 1, + "imageWidth": 120, + "imageHeight": 80, + "name": "geometric-center", + "path": "subject.svg", + "faceRight": 0, + "x": 60, + "y": 40 + }, + { + "bitmapResolution": 1, + "imageWidth": 120, + "imageHeight": 80, + "name": "asset-20-20", + "path": "subject.svg", + "faceRight": 0, + "x": 20, + "y": 20 + }, + { + "bitmapResolution": 1, + "imageWidth": 120, + "imageHeight": 80, + "name": "asset-100-60", + "path": "subject.svg", + "faceRight": 0, + "x": 100, + "y": 60 + } + ], + "costumeIndex": 0, + "heading": 90, + "isDraggable": false, + "rotationStyle": "normal", + "size": 1, + "visible": true, + "x": 0, + "y": 100 +} diff --git a/test/CoordinateSystem/assets/sprites/Subject/subject.svg b/test/CoordinateSystem/assets/sprites/Subject/subject.svg new file mode 100644 index 000000000..874a9c948 --- /dev/null +++ b/test/CoordinateSystem/assets/sprites/Subject/subject.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/test/CoordinateSystem/main.spx b/test/CoordinateSystem/main.spx new file mode 100644 index 000000000..a9647c284 --- /dev/null +++ b/test/CoordinateSystem/main.spx @@ -0,0 +1,4 @@ +onStart => { + println "CoordinateSystem demo ready" + println "Space: costume center | arrows: move | R/F/N: rotate/flip/reset | P: +Y pen | S: stamp | C: reset | E: erase" +}