Skip to content

Phase 1 (1/5): Package scaffold, CI, and core math layer - #1

Merged
ronaldmannak merged 3 commits into
mainfrom
claude/picomanim-phase1-01-scaffold
Jul 5, 2026
Merged

Phase 1 (1/5): Package scaffold, CI, and core math layer#1
ronaldmannak merged 3 commits into
mainfrom
claude/picomanim-phase1-01-scaffold

Conversation

@ronaldmannak

Copy link
Copy Markdown
Contributor

Summary

Bootstraps the PicoManim Swift package (Swift 6.2, macOS 15+ / iOS 18+) with CI and the geometry primitives every later layer builds on:

  • Vec2SIMD2<Double> alias with rotation, lerp, and unit-direction helpers.
  • Transform2D — affine transform kept decomposed (scale → rotate → translate) so animations can interpolate rotation and scale without shearing.
  • ManimColor — RGBA color with the classic Manim palette (.blue, .red, .yellow, …).
  • CI — GitHub Actions: swift build + swift test on macos-26 and on Linux (swift:6.2 container). The Linux job exists to keep the core platform-neutral (SwiftUI code in later PRs is fenced behind #if canImport(SwiftUI)). The pull_request trigger deliberately has no base-branch filter so the stacked PRs in this series get CI too.

Stack (Phase 1: shapes + animation + ManimView)

This is PR 1/5, base main. The stack, in review/merge order:

  1. → this PR — scaffold, CI, core math
  2. Bézier engine (CubicCurve, BezierPath)
  3. Mobject model + shape factories
  4. Animation timeline (RateFunction, ManimAnimation, ManimScene)
  5. ManimView SwiftUI preview + demo scene + full docs

Each later PR is based on its parent branch; they will be retargeted to main as parents merge.

Intentionally not included

Paths/shapes, animation, and rendering — they arrive in PRs 2–5 so each diff stays reviewable on its own.

Test strategy

Exact-value math tests (rotation quarter-turn, lerp midpoints, transform composition order, hex color round-trip) — plus this PR proves out the CI pipeline itself for the rest of the stack.

Concurrency

All types are Sendable value types. No @unchecked Sendable anywhere in this stack.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YGb3ukB5dQy4vgz7Z1hags


Generated by Claude Code

Bootstrap the PicoManim Swift package (Swift 6.2, macOS 15+/iOS 18+)
with the geometry primitives everything else builds on:

- Vec2: SIMD2<Double> alias with rotation, lerp, and direction helpers.
- Transform2D: scale -> rotate -> translate affine transform kept in
  decomposed form so animations can interpolate components directly.
- ManimColor: RGBA color with the classic Manim palette.
- GitHub Actions CI: swift build + swift test on macos-26 and on Linux
  (swift:6.2 container) to keep the core platform-neutral. The
  pull_request trigger has no base-branch filter so stacked PRs get CI.

Part 1/5 of the Phase 1 stack (shapes -> animation -> ManimView).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YGb3ukB5dQy4vgz7Z1hags

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the core math layer for the PicoManim animation library, adding basic structures such as Vec2, Transform2D, and ManimColor along with their corresponding unit tests. The review feedback suggests several improvements to robustness and code reuse: using hypot for safer vector length calculations, clamping color components within the 0...1 range during interpolation to handle overshooting animations, and leveraging the existing lerp helper function in Transform2D to avoid manual interpolation logic.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +10 to +12
public var length: Double {
(x * x + y * y).squareRoot()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using (x * x + y * y).squareRoot() to calculate the Euclidean length of a vector can lead to overflow or underflow for very large or very small values of x or y. Using hypot(x, y) is more robust as it is specifically designed to avoid intermediate overflow and underflow.

Suggested change
public var length: Double {
(x * x + y * y).squareRoot()
}
public var length: Double {
hypot(x, y)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d6e9dfa. Vec2.length now uses Foundation.hypot(x, y), avoiding intermediate overflow/underflow. Existing length tests cover the common cases; no behavior change for in-range values. No impact on later PRs in the stack (they consume length unchanged).


Generated by Claude Code

Comment on lines +34 to +41
public static func lerp(_ a: ManimColor, _ b: ManimColor, _ t: Double) -> ManimColor {
ManimColor(
red: a.red + (b.red - a.red) * t,
green: a.green + (b.green - a.green) * t,
blue: a.blue + (b.blue - a.blue) * t,
alpha: a.alpha + (b.alpha - a.alpha) * t
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The lerp function does not clamp the interpolated color components to the 0...1 range. During animations with overshooting rate functions (such as back or elastic easing), t can go outside 0...1, causing color components to go out of bounds. This violates the documented invariant "components in the 0...1 range" and can cause rendering issues. Using the clamp and lerp helper functions defined in Vec2.swift ensures the components remain valid.

Suggested change
public static func lerp(_ a: ManimColor, _ b: ManimColor, _ t: Double) -> ManimColor {
ManimColor(
red: a.red + (b.red - a.red) * t,
green: a.green + (b.green - a.green) * t,
blue: a.blue + (b.blue - a.blue) * t,
alpha: a.alpha + (b.alpha - a.alpha) * t
)
}
public static func lerp(_ a: ManimColor, _ b: ManimColor, _ t: Double) -> ManimColor {
ManimColor(
red: clamp(lerp(a.red, b.red, t), 0...1),
green: clamp(lerp(a.green, b.green, t), 0...1),
blue: clamp(lerp(a.blue, b.blue, t), 0...1),
alpha: clamp(lerp(a.alpha, b.alpha, t), 0...1)
)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d6e9dfa, with a required correction in 65d2984: the suggestion as written doesn't compile — inside the static lerp member, the unqualified lerp(a.red, ...) call resolves to the enclosing member itself (type scope shadows the module-level scalar helper), which broke CI. The final version module-qualifies the helper (PicoManim.lerp) and clamps every component to 0...1, preserving the documented invariant under overshooting rate functions. A source comment explains the qualification so it doesn't get "simplified" back. Later PRs benefit automatically (PR #4's transform color interpolation goes through this function).


Generated by Claude Code

Comment on lines +28 to +34
public static func lerp(_ a: Transform2D, _ b: Transform2D, _ t: Double) -> Transform2D {
Transform2D(
translation: Vec2.lerp(a.translation, b.translation, t),
rotation: a.rotation + (b.rotation - a.rotation) * t,
scale: Vec2.lerp(a.scale, b.scale, t)
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The lerp function manually computes linear interpolation for the rotation angle, duplicating the logic of the scalar lerp helper function defined in Vec2.swift. Using the helper function improves code reuse and maintainability.

Suggested change
public static func lerp(_ a: Transform2D, _ b: Transform2D, _ t: Double) -> Transform2D {
Transform2D(
translation: Vec2.lerp(a.translation, b.translation, t),
rotation: a.rotation + (b.rotation - a.rotation) * t,
scale: Vec2.lerp(a.scale, b.scale, t)
)
}
public static func lerp(_ a: Transform2D, _ b: Transform2D, _ t: Double) -> Transform2D {
Transform2D(
translation: Vec2.lerp(a.translation, b.translation, t),
rotation: lerp(a.rotation, b.rotation, t),
scale: Vec2.lerp(a.scale, b.scale, t)
)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d6e9dfa. Transform2D.lerp now reuses the shared scalar helper for rotation, module-qualified (PicoManim.lerp) because the unqualified name resolves to the enclosing static member rather than the global helper. No functional change; the existing transformLerpEndpoints test still pins the behavior.


Generated by Claude Code

claude added 2 commits July 5, 2026 14:39
- Vec2.length uses hypot to avoid intermediate overflow/underflow.
- ManimColor.lerp clamps components to 0...1 so overshooting rate
  functions can't violate the documented component range.
- Transform2D.lerp reuses the shared scalar lerp helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YGb3ukB5dQy4vgz7Z1hags
Inside the static lerp member, unqualified lerp(a.red, ...) resolved
to the enclosing member instead of the module-level scalar helper and
failed to type-check. Qualify with the module name, matching
Transform2D.lerp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YGb3ukB5dQy4vgz7Z1hags

Copy link
Copy Markdown
Contributor Author

@codex review


Generated by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 65d29847d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Contributor Author

Ready for human review/merge.

Checklist:

  • ✅ CI green on latest commit 65d2984 (test-macos + test-linux, swift build + swift test) — this PR also bootstraps the CI both jobs run on
  • ✅ Codex reviewed latest commit: "Didn't find any major issues"
  • ✅ All 3 Gemini review comments addressed and individually answered: hypot for Vec2.length, clamped ManimColor.lerp components, shared scalar-lerp helper reuse — fixed in d6e9dfa + 65d2984 (the follow-up fixed a member-shadowing compile error from applying the lerp suggestion verbatim; explained in the thread and in a source comment)
  • ✅ Stacked PRs current: Phase 1 (2/5): Cubic Bézier engine — curves, paths, partials, morph alignment #2Phase 1 (5/5): ManimView SwiftUI preview, demo scene, and docs #5 all rebased on this branch's latest

Remaining notes:


Generated by Claude Code

@ronaldmannak
ronaldmannak merged commit 254ed60 into main Jul 5, 2026
2 checks passed
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