IMPORTANT (AI-AUTHORITATIVE SPEC)
- This file (`val.txt`) is the authoritative VAL JS API specification intended for AI models.
- When the user requests API/doc changes, update this document so AI-generated VAL scripts stay valid.

------------------------------------------------------------
INTRODUCTION
VAL (Visual Animation Language) is a powerful engine and scripting language designed for creating interactive, high-quality math and science animations. It allows you to build complex visual explanations—like a moving projectile, a rotating 2D shape, or an evolving graph—using simple code.

------------------------------------------------------------
VAL API SPEC (AI-ORIENTED)
Use this documentation as the authoritative reference when generating VAL Script.

VAL Script is a JavaScript (ES6-ish) subset. Scripts use JavaScript-like syntax, but run in a restricted runtime with a custom VAL API (no DOM, no browser/Node APIs).

------------------------------------------------------------
Coordinate system and units (VERY IMPORTANT):
- The center of each slide is the zero vector: Vector(0, 0).
- All VAL dimensions (positions, widths, heights, radii, stroke widths, etc.) are in pixels.
- All rotations are in radians (not degrees).
- Rotation sign convention follows the standard mathematical “unit circle” rule:
  - Positive angles ( + ) rotate counter-clockwise.
  - Negative angles ( - ) rotate clockwise.
- When you use Vector(x, y), x and y are pixel offsets from the slide center (0, 0).


GEOMETRY-DERIVED ROTATIONS (AI RULE, VERY IMPORTANT):
- When aligning/rotating shapes to match **what is actually drawn**, do NOT hardcode
  angles from symbolic constants using `asin/acos/atan(...)` on side lengths.
  Those often become the wrong (complementary) angle once objects are rotated or
  placed with anchors/gaps.
- When aligning an object to a direction vector `(dx, dy)`, use:
  - `theta = Math.atan2(dy, dx)`
- Instead, compute the angle from the actual geometry you want to align to using:
  - `theta = Math.atan2(dy, dx)` where `(dx, dy)` is the direction vector of the
    edge/segment you are aligning with.
- Always compute alignment angles **after transforms** (after `moveTo/rotate/scale`)
  using the object’s geometry (edge points / bounds / measured points), not the
  intended design constants.
- Rotation APIs:
  - `obj.rotate(delta)` / `obj.animatedRotate(delta, ...)` are **relative** rotations.
  - Use `obj.rotation` (read-only) to read the current absolute rotation angle (radians).

EXAMPLE (align a right triangle’s +x leg with “up”):
- const tri = new Polygon([Vector(0,0), Vector(120,0), Vector(0,160)]);
- const up = Vector(0, 1);
- const theta = Math.atan2(up.y, up.x); // = +PI/2
- tri.absoluteRotate(theta);

------------------------------------------------------------
FLOW SCENE (MOBILE-FIRST, VERY IMPORTANT)

DEFAULT RUNTIME ASSUMPTION FOR AI:
VAL scripts run in **FlowScene** UX by default.

What FlowScene IS (critical mental model):
- FlowScene is a **single, continuous, vertically scrollable document** that expands as you add content.
- There are **no “pages”**. Do NOT treat FlowScene like page-by-page slides.

FOCUS INVARIANT (critical UX rule):
- The viewer must always be looking at the changes that are happening.
- Do **not** scroll just to “start a new page”.
- Do **not** animate/change objects that are currently off-screen/hidden.
  - If you must animate an existing object that might be off-screen, you have two options:
    - Use an `animated*` method (it will auto-focus first).
    - Or explicitly: `await Scene.ensureVisible(obj, padding?: number, duration?: Duration)`

How to author FlowScene scripts (AI guidance):
- Treat x≈0 as the horizontal center; keep important content inside the visible width.
- Use narration with **`Scene.narrate("...")`** and schedule word-synced cues as you reveal content:
  - `const n = Scene.narrate("...");`
  - `n.atWord("someWord", () => { /* animate */ });`
  - `await n.start();`
- Use **`await Scene.moveOriginTo(Vector(x, y), duration?: Duration)`** only as a **focus move** (camera navigation), not as pagination.
- After a focus move, immediately create/animate the content that the viewer is currently looking at (near Vector(0,0)).
- To continue with a new section below the current one, move focus down by (about) one viewport height and then build the next section *there*:
  - `const top = Scene.visibleTop(); const bottom = Scene.visibleBottom();`
  - `const vh = bottom - top; const cy = (top + bottom) / 2;`
  - `await Scene.moveOriginTo(Vector(0, cy - vh - spacingPixels), duration?: Duration)`
- IMPORTANT: `Scene.moveOriginTo(...)` rebases a virtual origin so earlier objects keep their **world** position, but their `.location` values can shift.
  - Do not treat cached numeric Y values from older objects as “absolute document coordinates”.
  - AUTO-FOCUS NOTE (ENGINE BEHAVIOR):
    - In FlowScene, **all `animated*` methods auto-focus** before animating.
    - Auto-focus is **camera-only** (no virtual-origin rebase). Object `.location` values stay stable.
    - Focus is **batched**: if multiple `animated*` calls start together (e.g. inside `await Promise.all([...])`),
      the engine performs **one** camera focus move for the whole batch to avoid focus fighting/jitter.
      If multiple targets cannot all fit in view, the engine focuses **the first** target in the batch.
  - `Scene.ensureVisible(...)` is also camera-only (no virtual-origin rebase; `.location` stays stable).

------------------------------------------------------------
ANIMATION FEEL (MANIM-LIKE DEFAULTS)

- All `animated*` methods use a smooth ease-in-out style curve by default (Manim-like feel).
- You can override easing per animation using a **real-JS style options object** as the last argument:
  - `{ rateFunc: "linear" | "smooth" | "there_and_back" }`
  - Default: `"smooth"`

------------------------------------------------------------
CLASS ValObject
DESCRIPTION: Abstract base for all drawable/interactive objects.

READABLE_PROPERTIES:
- color: Color                 // stroke/outline color
- fillColor: Color             // interior color
- opacity: number              // 0..1, overall opacity
- fillOpacity: number          // 0..1, fill opacity only
- stroke: number               // stroke width
- rotation: number             // current absolute rotation angle in radians
- location: Vector             // main reference position (usually center)
- bound: Bounds                // bounding box (engine-defined)
- path: PathData               // geometric path (engine-defined)
- width: number                // visual width in pixels
- height: number               // visual height in pixels
- center: Vector
- top: Vector
- bottom: Vector
- left: Vector
- right: Vector
- topLeft: Vector
- topRight: Vector
- bottomLeft: Vector
- bottomRight: Vector
- onMoveEvent: Handler | null  // move handler
- onClickEvent: Handler | null // click/tap handler

WRITABLE_PROPERTIES:
- color: Color
- fillColor: Color
- opacity: number
- fillOpacity: number
- stroke: number
- onMoveEvent: Handler | null
- onClickEvent: Handler | null

METHODS (available on any ValObject):
- copy(): ValObject
  EFFECT: Return a cloned copy of this object.
          VAL supports BOTH `obj.copy` (property) and `obj.copy()` (method) for compatibility.

- animatedToState(target: ValObject, duration?: Duration): Promise<ValObject>
  EFFECT: Animate this object so its transform/style matches the provided target object.
          This is the supported public API for “animate to another state”.

- nextTo(obj: ValObject | Vector, direction: Vector, spacing?: number): ValObject
  EFFECT: Instantly place this object next to an anchor along direction with spacing.
          The anchor can be a ValObject OR a Vector point (e.g. axes.right).
  RETURNS: This object (for chaining).

- animatedNextTo(obj: ValObject | Vector, direction: Vector, spacing?: number, duration?: Duration): Promise<ValObject>
  EFFECT: Animate position until it is next to an anchor (ValObject or Vector).
  RETURNS: This object (for chaining).

  EXAMPLE (Vector anchor):
  - const axes = new Axis(new Range(0, 12, 1), new Range(0, 8, 1));
  - const xLabel = new MathTex("x").nextTo(axes.right, Scene.R, 10);
  - const yLabel = new MathTex("y").nextTo(axes.top, Scene.U, 10);

- moveTo(position: Vector): ValObject
  EFFECT: Instantly set center/location to position.
  RETURNS: This object (for chaining).

- alignTo(target: Vector, anchor: Vector, direction?: Vector): ValObject
  EFFECT: Instantly translate this object so that anchor (a world-space point on this object)
          coincides with target.
          If direction is provided, only axes where direction is non-zero are aligned (Manim-style).
  RETURNS: This object (for chaining).

- animatedAlignTo(target: Vector, anchor: Vector, direction?: Vector, duration?: Duration, options?: object): Promise<ValObject>
  EFFECT: Animate translation so that anchor (a world-space point on this object) coincides with target.
          options.rateFunc controls easing: "linear" | "smooth" | "there_and_back" (default "smooth").
          If direction is provided, only axes where direction is non-zero are aligned (Manim-style).
  RETURNS: This object (for chaining).

- animatedMoveTo(position: Vector, duration?: Duration, options?: object): Promise<ValObject>
  EFFECT: Animate center from current position to position.
          options.rateFunc controls easing: "linear" | "smooth" | "there_and_back" (default "smooth").
  RETURNS: This object (for chaining).

  EXAMPLE:
  - await obj.animatedMoveTo(Vector(200, 0), Duration(1), { rateFunc: "linear" })

- shift(offset: Vector): ValObject
  EFFECT: Instantly add offset to current location.
  RETURNS: This object (for chaining).

- animatedShift(offset: Vector, duration?: Duration): Promise<ValObject>
  EFFECT: Animate shifting by offset.
  RETURNS: This object (for chaining).

AI GUIDANCE (VERY IMPORTANT): Snap with anchors, don't guess centers
- If you are trying to place shapes so that they *touch/align* cleanly, avoid hard-coded center math.
- Prefer `alignTo(...)` / `animatedAlignTo(...)` and prefer absolute rotation APIs for determinism:
  - `absoluteRotate(...)` / `animatedAbsoluteRotate(...)` (absolute)
  - `rotate(...)` / `animatedRotate(...)` (relative; easy to drift in long scripts)

EXAMPLE (snap bottom-left bbox corner to a target point):
- const p = Vector(-200, -100);
- const r = new Rectangle(120, 80);
- r.alignTo(p, r.bottomLeft);

EXAMPLE (Manim-style axis masking):
- // Align left edge x only; keep y unchanged:
- r.alignTo(other.left, r.left, Scene.L);
- // Align top edge y only; keep x unchanged:
- r.alignTo(other.top, r.top, Scene.U);

EXAMPLE (polygons): snap a true vertex (recommended):
- const p = Vector(-200, -100);
- const tri = new Polygon([Vector(0,0), Vector(120,0), Vector(0,160)]);
- tri.alignTo(p, tri.getVertices()[0]);

- rotate(angleDelta: number, pivot?: Vector): ValObject
  EFFECT: Instantly rotate the object by angleDelta radians **relative** to its
          current orientation. Positive angles rotate counter-clockwise; negative
          angles rotate clockwise. If pivot is provided, rotation is around that
          pivot; otherwise around the object’s center.
  RETURNS: This object (for chaining).

- animatedRotate(angleDelta: number, duration?: Duration, pivot?: Vector, options?: object): Promise<ValObject>
  EFFECT: Animate a **relative** rotation by angleDelta radians over duration,
          starting from the object’s current angle.
          If pivot is provided, rotation is around that pivot point; otherwise it
          is around the object’s center.
          options.rateFunc controls easing: "linear" | "smooth" | "there_and_back" (default "smooth").
  RETURNS: This object (for chaining).

  EXAMPLE:
  - await obj.animatedRotate(PI, Duration(1), Scene.O, { rateFunc: "there_and_back" })

- absoluteRotate(angle: number, pivot?: Vector): ValObject
  EFFECT: Instantly set the **absolute** rotation angle of this object to angle (radians).
          If pivot is provided, the object is rotated about that pivot.
  RETURNS: This object (for chaining).

- animatedAbsoluteRotate(angle: number, duration?: Duration, pivot?: Vector, options?: object): Promise<ValObject>
  EFFECT: Animate the **absolute** rotation angle of this object to angle (radians).
          If pivot is provided, the object is animated as if rotating around that pivot.
          options.rateFunc controls easing: "linear" | "smooth" | "there_and_back" (default "smooth").
  RETURNS: This object (for chaining).

- scale(value: number): ValObject
  EFFECT: Instantly scale the object by a multiplicative factor value (Manim-style).
          Example: scale(2) doubles size, scale(0.5) halves it.
          If you want absolute scaling (set the scale to an exact value), use setScale(...).
  RETURNS: This object (for chaining).

- animatedScale(value: number, duration?: Duration, options?: object): Promise<ValObject>
  EFFECT: Animate scaling by a multiplicative factor value over duration (Manim-style).
          Example: animatedScale(2, 1s) doubles size over 1 second.
          options.rateFunc controls easing: "linear" | "smooth" | "there_and_back" (default "smooth").
  RETURNS: This object (for chaining).

- setScale(value: number, pivot?: Vector): ValObject
  EFFECT: Instantly set the absolute scale of this object to value.
          If pivot is provided, the object is scaled about that pivot point (Manim about_point).
  RETURNS: This object (for chaining).

- animatedSetScale(value: number, duration?: Duration, pivot?: Vector, options?: object): Promise<ValObject>
  EFFECT: Animate the absolute scale of this object from its current scale to value.
          If pivot is provided, scale about that pivot point.
          options.rateFunc controls easing: "linear" | "smooth" | "there_and_back" (default "smooth").
  RETURNS: This object (for chaining).

- setColor(color: Color): ValObject
  EFFECT: Instantly set stroke color.
  RETURNS: This object (for chaining).

- animatedColor(color: Color, duration?: Duration): Promise<ValObject>
  EFFECT: Animate stroke color to color.
  RETURNS: This object (for chaining).

- setFillColor(color: Color): ValObject
  EFFECT: Instantly set fill color.
  RETURNS: This object (for chaining).

- animatedFillColor(color: Color, duration?: Duration): Promise<ValObject>
  EFFECT: Animate fill color to color.
  RETURNS: This object (for chaining).

- setOpacity(value: number): ValObject
  EFFECT: Instantly set overall opacity.
  RETURNS: This object (for chaining).

- animatedOpacity(value: number, duration?: Duration): Promise<ValObject>
  EFFECT: Animate opacity to value.
  RETURNS: This object (for chaining).

- setFillOpacity(value: number): ValObject
  EFFECT: Instantly set fill opacity only.
  RETURNS: This object (for chaining).

- animatedFillOpacity(value: number, duration?: Duration): Promise<ValObject>
  EFFECT: Animate fill opacity to value.
  RETURNS: This object (for chaining).

- setStroke(value: number): ValObject
  EFFECT: Instantly set stroke width.
  RETURNS: This object (for chaining).

- animatedStroke(value: number, duration?: Duration): Promise<ValObject>
  EFFECT: Animate stroke width to value.
  RETURNS: This object (for chaining).

- setStyle(source: ValObject): ValObject
  EFFECT: Copy color/fillColor/opacity/fillOpacity/stroke from source.
  RETURNS: This object (for chaining).

- movable(): ValObject
  EFFECT: Make the object draggable; on drag it moves to the pointer position.
  RETURNS: This object (for chaining).

- shiftable(): ValObject
  EFFECT: Make the object draggable; on drag it shifts by the pointer delta.
  RETURNS: This object (for chaining).

- interactive(): ValObject
  EFFECT: Convenience alias for `shiftable()`.
  RETURNS: This object (for chaining).

------------------------------------------------------------
SCENE FUNCTIONS (CANVAS / SLIDE OPERATIONS)

These functions operate on the current canvas/slide as a whole, or on ValObjects within it.

- Scene.viewportWidth(): number
  EFFECT: Returns the current viewport width in pixels (0 if not yet measured).

- Scene.viewportHeight(): number
  EFFECT: Returns the current viewport height in pixels (0 if not yet measured).

- Scene.visibleTop(): number
  EFFECT: Returns the current visible area top bound (world units).

- Scene.visibleBottom(): number
  EFFECT: Returns the current visible area bottom bound (world units).

- Scene.moveOriginTo(location: Vector, duration?: Duration): Promise<void>
  EFFECT: Animate the FlowScene camera to [location], then rebase the virtual origin so
          new content authored at (0,0) is centered in the viewport.

- Scene.ensureVisible(obj: ValObject, padding?: number, duration?: Duration): Promise<void>
  EFFECT: If [obj] is not currently visible (within padding), automatically scroll/focus
          the FlowScene so [obj] becomes visible, then continue the script.
  NOTE: Use this before animating/changing an object that might be off-screen.

- Scene.documentHeight(): number
  EFFECT: Returns the current known FlowScene document height (world units).

- Scene.add(obj: ValObject): void
  EFFECT: Immediately add obj to the current canvas so it becomes visible and interactive.

- Scene.addAll(objects: ValObject[]): void
  EFFECT: Add all objects in the array to the current canvas.

- Scene.remove(obj: ValObject): void
  EFFECT: Remove obj from the canvas; it is no longer visible or interactive.

- Scene.removeAll(objects: ValObject[]): void
  EFFECT: Remove all given objects from the canvas.

- Scene.create(obj: ValObject, duration?: Duration): Promise<void>
  EFFECT: Add obj to the canvas and gradually reveal it (e.g. from 0% to full visibility).

- Scene.unCreate(obj: ValObject, duration?: Duration): Promise<void>
  EFFECT: Gradually hide obj with an animation, then remove it from the canvas.

- Scene.fadeIn(obj: ValObject, duration?: Duration): Promise<void>
  EFFECT: Animate obj’s opacity from 0 to its configured opacity.

- Scene.fadeOut(obj: ValObject, duration?: Duration): Promise<void>
  EFFECT: Animate obj’s opacity from current value to 0, but keep the object in the scene graph (it can be reused later).

- Scene.highlight(obj: ValObject, duration?: Duration): Promise<void>
  EFFECT: Briefly emphasize obj (e.g. pulse or glow) to draw attention.

- Scene.showPassingFlash(obj: ValObject, duration?: Duration): Promise<void>
  EFFECT: Show a quick flash/outline around obj (flash-around effect).

- Scene.flashAround(obj: ValObject, duration?: Duration): Promise<void>
  EFFECT: Alternative flash-around effect (yellow outline/box).

- Scene.transform(fromObj: ValObject, toObj: ValObject, duration?: Duration): Promise<void>
  EFFECT: Morph the current drawing of fromObj into the drawing of toObj over a smooth
          animation lasting duration (if provided).
          After the transform finishes, fromObj is removed from the scene and toObj is
          added to the scene. The fromObj instance is still usable in code (you can
          create(fromObj) again later); it is only removed from the canvas.
          SPECIAL CASE (Groups):
          - If both fromObj and toObj are Group:
            - Children are matched by index: fromObj.objects[i] -> toObj.objects[i].
            - Each matched pair is transformed in parallel over the same duration.
            - If toObj has more children than fromObj, the extra children in toObj
              are faded in.
            - If fromObj has more children than toObj, the extra children in fromObj
              are faded out.

- Scene.pause(duration: Duration): Promise<void>
  EFFECT: Pause the script for the given duration before continuing (timeline delay).

- Scene.skip(): void
  EFFECT: Make Scene.pause(...) return immediately (skip timeline delays).

- Scene.resume(): void
  EFFECT: Resume normal timing after Scene.skip() (pause delays work again).

- Scene.update(): void
  EFFECT: Force an immediate redraw/update of the canvas (usually not needed if the engine auto-updates).

- Scene.clear(): void
  EFFECT: Remove all objects from the current canvas.

- Scene.askChoice(prompt: string, choices: string[] | {id:string,label:string}[], options?: object): Promise<ChoiceResult>
  EFFECT: Show a multiple-choice question (on the canvas) and wait for the student to pick an option.
  RETURNS: A ChoiceResult with `.id` and `.label`.
  NOTE: If choices is a string array, ids become A/B/C/... in order.

  OPTIONS:
  - lockScene?: boolean = true
    EFFECT: If true, only the question UI is clickable while waiting.
  - timeoutMs?: number
    EFFECT: Optional timeout. If it fires, returns id="TIMEOUT".

VALUE_TYPE ChoiceResult
READABLE PROPERTIES:
- id: string
- label: string

EXAMPLE:
  ans = await Scene.askChoice(
    "What does the integral represent?",
    ["The slope", "The area under the curve", "The maximum value"],
    { lockScene: true }
  );
  if (ans.id == "B") {
    n = Scene.narrate("Correct, it's the area.");
    await n.start();
  } else {
    n = Scene.narrate("Not quite. Let's review.");
    await n.start();
  }

- Scene.narrate(text: string, showSubtitle?: boolean = true): NarrationHandle
  EFFECT: Create a narration handle. This does NOT start playback.
          Use `.atWord(...)` / `.waitUntil(...)` to declare word-synced cues, then `await n.start()`.
          NOTE: Word timing comes from the backend `speakWithMarks` endpoint.

VALUE_TYPE NarrationHandle
READABLE PROPERTIES:
- currentMs: number
  EFFECT: Current audio playback position (milliseconds).
- marks: { word: string, startMs: number, endMs: number }[]
  EFFECT: Word marks returned by the backend for this narration.

METHODS:
- start(): Promise<void>
  EFFECT: Start playback and await narration completion.
          IMPORTANT: This also waits for any async cue callbacks to finish.
- atWord(wordOrOccurrence: string, callback: Function): void
  EFFECT: Schedule callback to run when the given word is spoken.
          Supports occurrences using `"word#2"` (1-based).
- waitUntil(wordOrOccurrence: string): Promise<void>
  EFFECT: Pause until the given word's start time is reached.

EXAMPLE:
  const n = Scene.narrate("Now move the camera down, then fade out.", true);
  n.atWord("move", () => Scene.moveOriginTo(Vector(0, -200), Duration(0.8)));
  n.atWord("fade", () => Scene.fadeOut(circle, Duration(0.6)));
  await n.start();

------------------------------------------------------------
SCENE DIRECTION CONSTANTS

These are VAL-provided convenience vectors for layout/placement.

- Scene.O  : Vector  // (0, 0)
- Scene.R  : Vector  // (1, 0)
- Scene.L  : Vector  // (-1, 0)
- Scene.U  : Vector  // (0, 1)
- Scene.D  : Vector  // (0, -1)
- Scene.UL : Vector  // (-1, 1)
- Scene.UR : Vector  // (1, 1)
- Scene.DL : Vector  // (-1, -1)
- Scene.DR : Vector  // (1, -1)

------------------------------------------------------------
CLASS Circle EXTENDS ValObject
CONSTRUCTOR:
- Circle(radius?: number)

METHODS:
- getSectors(count: number, startAngle?: number = 0): Group
  EFFECT: Return a Group containing count `Sector` objects that partition this
          circle into equal angular slices, arranged around the circle and ordered
          counterclockwise starting from startAngle (default 0).

------------------------------------------------------------
CLASS Rectangle EXTENDS ValObject
CONSTRUCTOR:
- Rectangle(width?: number, height?: number)

------------------------------------------------------------
CLASS Polygon EXTENDS ValObject
CONSTRUCTOR:
- Polygon(points: Vector[])
DESCRIPTION: General polygon defined by a list of vertex positions. The polygon
             is automatically closed and its internal reference location is set
             to the geometric center of its vertices.

METHODS:
- getVertices(): Vector[]
  EFFECT: Return the polygon’s vertices in world coordinates (after the polygon’s
          current translation/rotation/scale). This is the preferred way to align
          polygons/triangles precisely (vertex snapping).

- getSectors(count?: number): Group
  EFFECT: Return a Group of `Line` objects representing this polygon’s edges.
          If count is provided and smaller than the number of edges, the Group
          contains the `count` longest edges (longest-first). Otherwise, it
          returns all edges in perimeter order.

------------------------------------------------------------
CLASS RegularPolygon EXTENDS ValObject
CONSTRUCTOR:
- RegularPolygon(sides?: number, radius?: number)
DESCRIPTION: Regular n-gon with all sides and angles equal, inscribed in a
             circle of given radius. `sides` defaults to 5, `radius` defaults
             to 150 pixels. The polygon is centered at its geometric center.

------------------------------------------------------------
CLASS Arc EXTENDS ValObject
CONSTRUCTOR:
- Arc(radius?: number, startAngle?: number, angle?: number)
DESCRIPTION: Circular arc, optionally with a central angle span.

------------------------------------------------------------
CLASS Sector EXTENDS ValObject
CONSTRUCTOR:
- Sector(radius?: number, startAngle?: number, angle?: number)
DESCRIPTION: Filled circular sector (like Manim’s `Sector`). The wedge is cut
             from a circle of radius radius, spanning from startAngle to
             startAngle + angle (radians, CCW positive). By default, rotation
             methods use the sector’s geometric
             center as the pivot, matching Manim’s behaviour.

------------------------------------------------------------
CLASS AnnularSector EXTENDS ValObject
CONSTRUCTOR:
- AnnularSector(innerRadius?: number, outerRadius?: number, startAngle?: number, angle?: number)
DESCRIPTION: Ring-shaped sector (like Manim’s `AnnularSector`) between
             innerRadius and outerRadius, spanning the same angular parameters
             as `Sector`. Rotation defaults to the annular sector’s geometric
             center.

------------------------------------------------------------
CLASS Line EXTENDS ValObject
CONSTRUCTOR:
- Line(start?: Vector, end?: Vector)

------------------------------------------------------------
CLASS Arrow EXTENDS ValObject
CONSTRUCTOR:
- Arrow(start?: Vector, end?: Vector)

------------------------------------------------------------
CLASS Dot EXTENDS ValObject
CONSTRUCTOR:
- Dot(radius?: number)

------------------------------------------------------------
CLASS Ellipse EXTENDS ValObject
CONSTRUCTOR:
- Ellipse(width?: number, height?: number)

------------------------------------------------------------
CLASS Polyline EXTENDS ValObject
CONSTRUCTOR:
- Polyline(points: Vector[])

------------------------------------------------------------
CLASS Path EXTENDS ValObject
CONSTRUCTOR:
- Path(pathData: PathData)

------------------------------------------------------------
CLASS Clip EXTENDS ValObject
CONSTRUCTOR:
- Clip(shape: ValObject)

------------------------------------------------------------
CLASS Text EXTENDS ValObject
CONSTRUCTOR:
- Text(text: string)

------------------------------------------------------------
CLASS MathTex EXTENDS ValObject
CONSTRUCTOR:
- MathTex(latex: string)

IMPORTANT (AI guidance):
- Prefer JS raw strings for LaTeX using tagged templates:
  - Use `String.raw\`...\`` so backslashes are preserved exactly.
- If you do NOT use `String.raw`, you must double-escape backslashes in normal strings,
  e.g. `"\\int_0^1"`.

EXAMPLES:
- new MathTex(String.raw`\int_a^b f(x)\,dx`)
- new MathTex(String.raw`\text{Accumulation \& area}`)

------------------------------------------------------------
CLASS Group EXTENDS ValObject
CONSTRUCTOR:
- Group(objects?: ValObject[])

DESCRIPTION:
- Group is a **logical handle** (Manim-style), not a standalone drawable entity.
- The children in `objects` are the actual drawable objects; the group simply
  provides a convenient way to move, rotate, and animate them together.

IMPORTANT:
- `Scene.create(Group([...]))` creates the *children* (the group itself is not added as a single scene object).
- For a **single interactive unit** (UI clusters), prefer `Container` instead.

READABLE_PROPERTIES:
- objects: ValObject[]      // children of this group, in drawing order

METHODS:
- add(object: ValObject): void
  EFFECT: Add object as a child of this group.

- remove(object: ValObject): void
  EFFECT: Remove object from this group if present.

- addAll(objects: ValObject[]): void
  EFFECT: Add all given objects to this group.

- removeAll(objects: ValObject[]): void
  EFFECT: Remove all given objects from this group (ignores any not present).

- column(spacing?: number, alignment?: "center" | "start" | "end"): Group
  EFFECT: Arrange all children vertically in a column with the given spacing between
          them. alignment controls how they are aligned along the column axis.
          Returns this group for chaining.

- row(spacing?: number, alignment?: "center" | "start" | "end"): Group
  EFFECT: Arrange all children horizontally in a row with the given spacing between
          them. alignment controls how they are aligned along the row axis.
          Returns this group for chaining.

- arrangeNextToEachOther(direction?: Vector, spacing?: number): Group
  EFFECT: Arrange children sequentially so that each child is placed next to the
          previous one in the given direction, with the specified spacing between
          them. direction defaults to pointing to the right. Returns this group.

- setClipPath(path: Path): Group
  EFFECT: Use the geometry of path as a clipping region for this group so that only
          the portions of the children inside the path are visible. Returns this group.

- clearClipPath(): Group
  EFFECT: Remove any clipping region previously set on this group so that all
          children are fully visible again. Returns this group.

------------------------------------------------------------
CLASS Container EXTENDS ValObject
CONSTRUCTOR:
- Container(objects?: ValObject[])

DESCRIPTION:
- Container is a **single interactive unit** that groups multiple objects together.
- Unlike Group, a Container is added/removed as one unit by Scene operations.
- Event model: the Container receives pointer events; children are treated as visual-only.

READABLE_PROPERTIES:
- objects: ValObject[]      // children managed by this container

COMMON USAGE:
- UI clusters: tooltips, callouts, multiple labels you want to move/remove together.

------------------------------------------------------------
CLASS Note EXTENDS ValObject
CONSTRUCTOR:
- Note(config: any)
DESCRIPTION: Composite “note” built from multiple objects. Config is engine-specific.

------------------------------------------------------------
CLASS Brace EXTENDS ValObject
CONSTRUCTOR:
- Brace(start: Vector, end: Vector)

METHODS:
- getLabel(content: ValObject): ValObject
  EFFECT: Return label object positioned relative to brace.

------------------------------------------------------------
CLASS Axis EXTENDS ValObject
CONSTRUCTOR:
- Axis(xRange: Range, yRange: Range)

METHODS:
- getEquation(fn: (x: number) => number): Equation
- getParametricEquation(fx: (t: number) => number, fy: (t: number) => number, range: Range): ParametricEquation
- getXLabel(label: string): ValObject
- getYLabel(label: string): ValObject
- vectorAt(x: number, y?: number=0): Vector
  EFFECT: JS-friendly alias for c2v(x,y). If y is omitted, y defaults to 0.
  NOTE: Use this to place labels/ticks under a graph, e.g.:
        - const aLine = new Line(axes.vectorAt(1), axes.vectorAt(1).add(Vector(0, -20)));

- coordAt(v: Vector): Vector
  EFFECT: Convert a pixel-space Vector back into axis coordinates (x,y).

- getTickPoints(): Group
  EFFECT: Return a Group of tick labels/markers for the axis.

- getAreaUnderCurve(equation: Equation, range: Range): ValObject
- getRiemannRectangles(equation: Equation, range: Range, dx?: number, color?: Color, fillOpacity?: number): Group
  EFFECT: Return rectangles representing a Riemann-sum approximation of the area
          under equation on range. Smaller dx means more, thinner rectangles.

------------------------------------------------------------
CLASS Equation EXTENDS ValObject
CONSTRUCTOR:
- Equation(fn: (x: number) => number)

METHODS:
- fn(x: number): number
  EFFECT: Evaluate the equation at x (returns y = f(x)).
  NOTE: This is a JS-friendly alias so scripts can do `eq.fn(x)` even though the
        constructor parameter is also named `fn`.
- vectorAt(x: number): Vector

------------------------------------------------------------
CLASS ParametricEquation EXTENDS ValObject
CONSTRUCTOR:
- ParametricEquation(fx: (t: number) => number, fy: (t: number) => number, range: Range)

METHODS:
- vectorAt(t: number): Vector

------------------------------------------------------------
CLASS Button EXTENDS ValObject
CONSTRUCTOR:
- Button(
    label?: string,
    radius?: number,
    color?: Color,
    textColor?: Color,
    fillColor?: Color,
    fillOpacity?: number,
    width?: number,
    height?: number,
    onClick?: Handler
  )
DESCRIPTION: Clickable rounded-rectangle button with customizable label, size,
             colors, and optional onClick handler. All parameters are optional
             and can be passed either positionally or as named arguments using
             the same parameter names (for example:
             Button(label="OK", width=160, onClick=...)).

------------------------------------------------------------
VALUE_TYPE Vector
CONSTRUCTOR:
- Vector(x?: number, y?: number, z?: number)

READABLE PROPERTIES:
- x: number
- y: number
- z: number

METHODS:
- add(vector: Vector): Vector
  EFFECT: Return the vector obtained by adding this vector and vector component-wise.

- subtract(vector: Vector): Vector
  EFFECT: Return the vector obtained by subtracting vector from this vector component-wise (this - vector).

- multiply(scalar: number): Vector
  EFFECT: Return the vector obtained by multiplying this vector by scalar.

- divide(scalar: number): Vector
  EFFECT: Return the vector obtained by dividing this vector by scalar.

- normalize(): Vector
  EFFECT: Return a unit-length vector pointing in the same direction as this vector.

- dot(vector: Vector): number
  EFFECT: Return the dot product between this vector and vector.

- cross(vector: Vector): number
  EFFECT: Return the 2D cross product (scalar) between this vector and vector.

- rotate(angle: number): Vector
  EFFECT: Return a new vector equal to this vector rotated by angle radians around the origin.

- lerp(vector: Vector, t: number): Vector
  EFFECT: Return the linear interpolation between this vector and vector for parameter t in [0, 1].

------------------------------------------------------------
VALUE_TYPE Color
CONSTRUCTOR:
- Color(hexCode: number)
DESCRIPTION: hexCode is ARGB/RGBA integer (engine-defined).

------------------------------------------------------------
VALUE_TYPE Duration
CONSTRUCTOR:
- Duration(time: number)
DESCRIPTION: time is interpreted as **seconds** (engine convention).

VERY IMPORTANT (common mistake):
- `Duration(0.6)` means 0.6 seconds (600ms)
- `Duration(600)` means 600 seconds (10 minutes) — this will look “stuck”

READABLE PROPERTIES:
- inDays: number
- inHours: number
- inMinutes: number
- inSeconds: number
- inMilliseconds: number
- inMicroseconds: number

------------------------------------------------------------
VALUE_TYPE Range
CONSTRUCTOR:
- Range(start?: number, end?: number, step?: number)
