All 6 consistency review items have been resolved, verified with new
unit tests, automated test suites, and clean example builds.
- **Fixed `Goto` Duration Bug**: In
[`turtle-lib/src/command_behavior.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/command_behavior.rs),
mapped Cartesian target coordinates to screen space (`vec2(target.x,
-target.y)`) before calculating $\Delta x$ and $\Delta y$ in
`animation_duration`.
- **Unit Test**: Added `test_goto_duration_cartesian_inversion` testing
that moving from screen $(0, 100)$ (Cartesian $(0, -100)$) to
Cartesian $(0, 100)$ computes duration based on the actual 200px
Euclidean distance ($2.0\text{s}$ at $100\text{ px/s}$).
- **Stored `Degrees`**: Updated `TurtleCommand::SetHeading(Degrees)` in
[`turtle-lib/src/commands.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/commands.rs)
to store user degrees directly ($0^\circ = \text{East}$, $90^\circ =
\text{North}$), matching `Turn(Degrees)` and `Circle { angle: Degrees
}`.
- **Deferred Screen-Space Conversion**: Converted to internal screen
radians (`normalize_angle(-heading.as_radians().value())`) inside
`apply_to_params` in
[`turtle-lib/src/command_behavior.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/command_behavior.rs).
- **Instant Duration**: Maintained instant transition ($0.01\text{s}$
minimum) in `animation_duration`.
- **Unit Test**: Added `test_set_heading_degrees_and_instant_duration`
verifying $90^\circ$ maps to North ($-\frac{\pi}{2}$ in screen space),
$0^\circ$ to East ($0$), $270^\circ$ to South ($+\frac{\pi}{2}$), and
duration is $0.01\text{s}$.
- **Enhanced `Length` Type**: Expanded
[`turtle-lib/src/general/length.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/general/length.rs)
with `new()`, `value()`, `Neg`, `PartialOrd`, and conversion
implementations (`From<f32>`, `From<f64>`, `From<i32>`, `From<i16>`,
`From<usize>`).
- **Adopted Across API**:
- `TurtleCommand::Move(Length)` in `commands.rs`.
- `TurtleCommand::Circle { radius: Length, ... }` in `commands.rs`.
- `forward<T: Into<Length>>` and `backward<T: Into<Length>>` in
[`turtle-lib/src/builders.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/builders.rs).
- `circle_left`, `circle_right` accept `radius: impl Into<Length>`.
- Kept stroke attribute `pen_width` as `Precision` (`f32`).
- **Unit Test**: Added `test_length_conversions_and_negation` in
`general::length`.
- **Removed Duplicate Alias**: Removed `all_animations_complete(&self)`
from `TurtleApp` in
[`turtle-lib/src/lib.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/lib.rs);
updated call in
[`turtle-lib/src/export.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/export.rs)
to use canonical `is_complete(&self)`.
- **Corrected
[`README.md`](file:///home/dietrich/Projekte/Source/turtlers/README.md)**:
- `plan.goto(...)` $\rightarrow$ `plan.go_to(...)`
- `plan.set_color(...)` $\rightarrow$ `plan.set_pen_color(...)`
- `create_turtle()` $\rightarrow$ `create_turtle_plan()`
- **Corrected
[`AGENTS.md`](file:///home/dietrich/Projekte/Source/turtlers/AGENTS.md)**:
- `create_turtle()` $\rightarrow$ `create_turtle_plan()` in Threading
Pattern.
- **Unified on `1000.0`**:
- Updated
[`README.md`](file:///home/dietrich/Projekte/Source/turtlers/README.md)
lines 9, 108, 109 to state `speed >= 1000` is Instant and `speed <
1000` is Animated.
- Updated
[`turtle-lib/examples/circle_test.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/circle_test.rs)
to use `turtle.set_speed(1000)`.
- **Renamed Examples**:
- `turtle-lib/examples/stern.rs` $\rightarrow$
[`turtle-lib/examples/star.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/star.rs)
- `turtle-lib/examples/nikolaus.rs` $\rightarrow$
[`turtle-lib/examples/house_of_nikolaus.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/house_of_nikolaus.rs)
- **Translated Functions & Parameters**:
- `house_of_nikolaus.rs`: Translated `nikolausquadrat` $\rightarrow$
`house_square`, `nikolausdiag` $\rightarrow$ `house_diagonal`,
`nikolausdach2` $\rightarrow$ `house_roof`, parameter `groesse`
$\rightarrow$ `size`. Added doc comment explaining the Eulerian path
puzzle.
- `breadboard.rs`: Translated `pin_reihe` $\rightarrow$ `pin_row`,
`pin_spalte` $\rightarrow$ `pin_column`, `pin_seite` $\rightarrow$
`pin_side`, parameter `anzahl` $\rightarrow$ `count`,
`anzahl_reihen` $\rightarrow$ `row_count`. Added `#[cfg(feature =
"svg")]` to helper functions to eliminate dead-code warnings when
SVG feature is not enabled.
- **Translated UI Text & Print Messages**:
- `"Drücke E für SVG-Export"` $\rightarrow$ `"Press E for SVG export"`
- `"SVG exportiert nach test.svg"` $\rightarrow$ `"SVG exported to
test.svg"`
- `"Fehler beim Export: {:?}"` $\rightarrow$ `"Export error: {:?}"`
- `"SVG-Export ist nicht aktiviert..."` $\rightarrow$ `"SVG export is
not enabled..."`
- Translated module doc in `export_svg.rs`.
- **Updated
[`README.md`](file:///home/dietrich/Projekte/Source/turtlers/README.md)**
example lists and CLI commands to reference `star` and
`house_of_nikolaus`.
---
- `cargo test --package turtle-lib`:
- 13 unit tests passed (including 3 new targeted unit tests)
- 34 doctests passed (1 ignored internal helper)
- `cargo check --workspace --all-targets --all-features`: Passed with 0
errors.
- `cargo check --examples --package turtle-lib --all-features`: All 30
examples compiled cleanly with 0 errors.
- `cargo clippy --package turtle-lib -- -Wclippy::pedantic
-Aclippy::cast_precision_loss -Aclippy::cast_sign_loss
-Aclippy::cast_possible_truncation`: Passed with 0 errors.
230 lines
9.5 KiB
Markdown
230 lines
9.5 KiB
Markdown
# Turtle Graphics Library - AI Agent Instructions
|
|
|
|
## Project Overview
|
|
|
|
Rust workspace with turtle graphics implementations. **Primary focus: `turtle-lib`** - lightweight library using Macroquad + Lyon for GPU-accelerated rendering with multi-turtle support and optional threading.
|
|
|
|
### Workspace Structure
|
|
```
|
|
turtlers/
|
|
├── turtle-lib/ # MAIN LIBRARY - Macroquad + Lyon (focus here)
|
|
│ └── examples/ # 30 examples including threading patterns
|
|
└── turtle-lib-macros/ # Proc macro for turtle_main
|
|
```
|
|
|
|
## Architecture (`turtle-lib`)
|
|
|
|
### Core Design Pattern: Turtle Entities + Persistent Animation Controllers
|
|
- **Builder API** (`TurtlePlan`) accumulates commands into immutable `CommandQueue`
|
|
- **TurtleWorld** maintains persistent `Vec<Turtle>` (`world.turtles`), each encapsulating state and a `TweenController`
|
|
- **TweenController** manages command execution, queue consumption, and animation interpolation per turtle
|
|
- **Lyon Tessellation** converts all primitives to GPU meshes
|
|
- **Multi-Turtle** support: Create multiple turtles with `add_turtle()` or threading channels
|
|
|
|
### Key Architectural Decision: Turtle Entity Encapsulation
|
|
Each turtle in `TurtleWorld` is represented by a `Turtle` struct (`state.rs`) storing its own `turtle_id`, `TurtleParams`, fill state, tessellated drawing commands, SVG log, and an embedded `TweenController`.
|
|
- `turtle_id` is stored directly on `Turtle` (and passed to `TweenController::update` for logging and side effects); `TweenController` itself manages animation state without needing turtle identity.
|
|
- Rendering (`drawing.rs`) iterates through `world.turtles` sequentially, directly accessing each turtle's `tween_controller.current_tween()`, `commands`, and `filling`.
|
|
|
|
### Key Files
|
|
```
|
|
src/
|
|
├── lib.rs - TurtleApp, multi-turtle API, channel integration
|
|
├── builders.rs - Fluent API traits (forward/right/circle/reset/etc)
|
|
├── commands.rs - TurtleCommand enum (Move/Turn/Circle/Reset/etc)
|
|
├── execution.rs - Command execution (immediate) + state updates
|
|
├── tweening.rs - Animation + tween interpolation (TweenController per Turtle)
|
|
├── drawing.rs - Lyon mesh rendering with Macroquad
|
|
├── state.rs - Turtle, TurtleParams, TurtleWorld (persistent state)
|
|
├── tessellation.rs - Lyon integration (polygons/strokes/fills/arcs)
|
|
├── circle_geometry.rs - Arc/circle math helpers
|
|
└── commands_channel.rs - Async channels for threading patterns
|
|
```
|
|
|
|
### Critical Concepts
|
|
|
|
**1. Consolidated Move Commands**:
|
|
- `Move(distance)` - negative = backward (no separate Backward)
|
|
- `Turn(angle)` - positive = right, negative = left (degrees)
|
|
- `Circle{radius, angle, steps, direction}` - unified left/right via CircleDirection
|
|
- `Reset` - clears drawings, animations, fill state; resets params to defaults
|
|
|
|
**2. Fill System (Multi-Contour with Holes)**:
|
|
- `FillState` tracks `Vec<Vec<Coordinate>>` (multiple closed contours)
|
|
- `pen_up()` closes current contour, `pen_down()` opens new one
|
|
- Lyon's EvenOdd fill rule auto-detects holes (inner contours with opposite winding)
|
|
- Example: Donut = outer circle (pen_down) → pen_up → inner circle → end_fill
|
|
|
|
**3. Animation Modes**:
|
|
- Speed `>= 1000`: Instant mode (no tweening, executes with `max(1, speed - 1000)` draw calls per frame)
|
|
- Speed `< 1000`: Animated mode (tweens with CubicInOut easing, duration based on distance/speed)
|
|
- Dynamic switching via `SetSpeed` command mid-animation
|
|
|
|
**4. Multi-Turtle Architecture**:
|
|
- Each turtle in `world.turtles` owns its persistent `TweenController`
|
|
- Rendering directly inspects each turtle's active tween via `turtle.tween_controller.current_tween()` during sequential traversal of `world.turtles`
|
|
- Supports concurrent animation of multiple turtles
|
|
- Threading channels: `create_turtle_channel(buffer_size)` returns `TurtleCommandSender`, with the receiver managed internally by `TurtleApp`
|
|
|
|
**5. Threading Pattern** (for interactive apps like Hangman):
|
|
- `create_turtle_channel(buffer_size)` returns `TurtleCommandSender` (clonable, Send)
|
|
- Spawn game logic on thread, send `CommandQueue` batches via channel
|
|
- Main render loop calls `app.process_commands()` before `update()` to drain channels
|
|
- Example: `hangman_threaded.rs` spawns stdin reader, sends drawing commands when player guesses
|
|
|
|
## Developer Workflows
|
|
|
|
### Building & Testing
|
|
```bash
|
|
# Main library
|
|
cargo build --package turtle-lib
|
|
cargo test --package turtle-lib
|
|
cargo clippy --package turtle-lib -- -Wclippy::pedantic \
|
|
-Aclippy::cast_precision_loss -Aclippy::cast_sign_loss -Aclippy::cast_possible_truncation
|
|
|
|
# Run examples
|
|
cargo run --package turtle-lib --example hello_turtle
|
|
cargo run --package turtle-lib --example yinyang
|
|
cargo run --package turtle-lib --example hangman_threaded
|
|
```
|
|
|
|
### Code Quality Standards
|
|
- Clippy pedantic enabled (graphics math casts allowed)
|
|
- Examples must build warning-free
|
|
- Use `#[must_use]` on builder methods
|
|
- Builder methods return `&mut Self` (never owned Self) for chaining
|
|
|
|
## Project-Specific Patterns
|
|
|
|
### 1. Single Turtle (Default)
|
|
```rust
|
|
#[turtle_main("Simple")]
|
|
fn draw(turtle: &mut TurtlePlan) {
|
|
turtle.forward(100.0).right(90.0);
|
|
}
|
|
```
|
|
|
|
### 2. Multi-Turtle Direct Setup
|
|
```rust
|
|
let mut app = TurtleApp::new();
|
|
let t0_id = app.add_turtle(); // Default setup
|
|
let t1_id = app.add_turtle();
|
|
|
|
app.append_commands(t0_id, turtle1_plan.build());
|
|
app.append_commands(t1_id, turtle2_plan.build());
|
|
```
|
|
|
|
### 3. Threading Pattern (Hangman Example)
|
|
```rust
|
|
let mut app = TurtleApp::new();
|
|
let turtle_tx = app.create_turtle_channel(100);
|
|
|
|
// Spawn game thread
|
|
let tx = turtle_tx.clone();
|
|
std::thread::spawn(move || {
|
|
loop {
|
|
let letter = get_input(); // Blocks
|
|
let mut plan = create_turtle_plan();
|
|
plan.forward(50.0);
|
|
tx.send(plan.build()).ok();
|
|
}
|
|
});
|
|
|
|
// Main loop
|
|
loop {
|
|
clear_background(WHITE);
|
|
app.process_commands(); // ← Drains channel
|
|
app.update();
|
|
app.render();
|
|
next_frame().await;
|
|
}
|
|
```
|
|
|
|
### 4. Multi-Contour Fills (Donut)
|
|
```rust
|
|
turtle.set_fill_color(BLUE)
|
|
.begin_fill()
|
|
.circle_left(100.0, 360.0, 72); // Outer
|
|
|
|
turtle.pen_up()
|
|
.go_to(vec2(0.0, -30.0))
|
|
.pen_down()
|
|
.circle_left(30.0, 360.0, 36); // Inner (hole)
|
|
|
|
turtle.end_fill(); // EvenOdd creates hole
|
|
```
|
|
|
|
### 5. Reset Turtle
|
|
```rust
|
|
turtle.forward(100.0)
|
|
.reset() // Clears drawings, resets to defaults
|
|
.forward(50.0); // Fresh start
|
|
```
|
|
|
|
## Common Tasks
|
|
|
|
### Adding New Turtle Command
|
|
1. Add variant to `TurtleCommand` enum in `commands.rs`
|
|
2. Implement builder method in `builders.rs` (always return `&mut Self`)
|
|
3. Add execution in `execution.rs` (for immediate state changes) or `tweening.rs` (for animated state)
|
|
4. If animated, implement in `calculate_target_state()` in `tweening.rs`
|
|
5. Update drawing/tessellation if needed
|
|
|
|
### Adding an Example
|
|
- Use `turtle_main` macro for simplicity
|
|
- Import only `use turtle_lib::*;` (all exports included)
|
|
- For threading: use `create_turtle_channel()` + `process_commands()`
|
|
- Place in `turtle-lib/examples/` and update README examples
|
|
|
|
### Debugging Animation Issues
|
|
```bash
|
|
RUST_LOG=turtle_lib=debug cargo run --example yinyang
|
|
```
|
|
- Check `tweening.rs` for state transitions
|
|
- Verify `command_creates_drawing()` includes your command type
|
|
- Circle direction: Left = counter-clockwise, Right = clockwise
|
|
|
|
## Critical Implementation Details
|
|
|
|
### TurtleCommand::Reset Behavior
|
|
- Clears `Turtle::commands` (all drawings)
|
|
- Clears `Turtle::filling` (ongoing fill operations)
|
|
- Resets `TurtleParams` to defaults (position 0,0, heading 0, pen down, etc.)
|
|
- Preserves `turtle_id` after reset
|
|
- Called via `execute_command()` in both instant and animated modes
|
|
|
|
### Turtle Entity and State Encapsulation
|
|
- Each `Turtle` struct owns its `turtle_id`, visual `params`, `filling` state, tessellated `commands`, `svg_log`, and `tween_controller`
|
|
- `TweenController` is responsible purely for command queue management and interpolation, decoupled from turtle identity
|
|
- Rendering iterates sequentially over `world.turtles`, accessing each turtle's `commands`, active tween (`turtle.tween_controller.current_tween()`), and live fill preview in place
|
|
|
|
### Lyon Tessellation
|
|
- All drawing → `tessellate_arc/stroke/circle/multi_contour` → `MeshData` → Macroquad `Mesh`
|
|
- Circle direction affects angle stepping: Left subtracts, Right adds
|
|
- Start angle for circles: `geom.start_angle_from_center.to_degrees()` (NOT rotation)
|
|
- EvenOdd fill rule: holes detected by contour winding (no explicit flag needed)
|
|
|
|
## Dependencies & Integration
|
|
|
|
### Main Dependencies
|
|
- `macroquad = "0.4"` - Window/rendering framework
|
|
- `lyon = "1.0"` - Tessellation (fills, strokes, circles)
|
|
- `tween = "2.2.0"` - Animation easing (CubicInOut)
|
|
- `tracing = "0.1"` - Optional logging (zero cost when unused)
|
|
- `crossbeam = "0.8"` - Threading pattern support (channels)
|
|
|
|
## What NOT to Do
|
|
|
|
- Don't assume `TweenController` stores `turtle_id` or look up active tweens globally by ID (rendering iterates `world.turtles` and inspects `turtle.tween_controller` directly)
|
|
- Don't add `use macroquad::prelude::*` without explicit need (causes unused imports)
|
|
- Don't manually triangulate—always use Lyon `tessellate_*` functions
|
|
- Don't separate Forward/Backward—use negative `Move` values
|
|
- Don't call `reset()` expecting to preserve drawing state—it clears everything
|
|
|
|
## Response Style
|
|
|
|
- Be concise, actionable, focused on code
|
|
- Reference specific files/lines when helpful
|
|
- Use examples from `examples/` directory
|
|
- No generic advice; focus on THIS project's patterns
|