Skip to content

feat(render): path-following undulating worm body on the continuous renderer - #238

Merged
chrisjz merged 3 commits into
mainfrom
add-continuous-worm-body-render
Jun 14, 2026
Merged

feat(render): path-following undulating worm body on the continuous renderer#238
chrisjz merged 3 commits into
mainfrom
add-continuous-worm-body-render

Conversation

@chrisjz

@chrisjz chrisjz commented Jun 14, 2026

Copy link
Copy Markdown
Member

Summary

The continuous renderer drew the worm as a single circle plus a heading line that was nearly the same beige as the body — it read as an arrow, not a crawling C. elegans. This adds a path-following, tapered, undulating body, recolours the heading indicator, and distinguishes the head — making the worm's motion legible (which matters for the T7.validation real-worm behavioural-chemotaxis comparison and for the demo).

Non-gating, renderer-only. The worm stays a point kinematically — the body is a pure visual overlay; no physics/sensing/brain/state change.

What it does

  • The renderer keeps its own deque of recent worm positions (it persists across frames and already receives pos — no ContinuousRenderState or agent change), and draws a connected tapered tube through that trail, so the body curves through where the head actually went.
  • A travelling sinusoidal crawl-wave is overlaid (phase advances per frame); width scales with body_length_mm.
  • Distinct head (brighter marker) + contrasting heading line (reddish-orange, no longer blended into the body).
  • Body history resets at episode boundaries (detected by a position-jump discontinuity), so it never streaks across the spawn teleport.
  • Connected drawing (thick links + rounded joints), not disjoint segments.

Notes

  • The undulation is a visual overlay (the worm is a point; it moves via the policy's (speed, turn)) — it looks like a crawl but isn't the propulsion. The trajectory itself is faithful (path-following + the 0.5 rad turn bound from fix(env): bound continuous turn rate to a realistic max angular velocity #237). A data-grounded turn-rate-realism check is deferred to T7.validation.
  • Single-agent continuous renderer; multi-agent-continuous body is a documented follow-up.

Validation

New headless tests (history accumulate + episode-reset, full-frame render, heading-colour contrast); full pre-commit run -a clean; OpenSpec change add-continuous-worm-body-render validated --strict and archived (canonical continuous-fidelity-renderer spec updated). Visual confirmed on --theme pixel_continuous.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced the worm pygame renderer with a continuous, path-following body that tapers and wiggles with sinusoidal lateral undulation.
    • Added a distinct heading indicator color that clearly contrasts with the body/head.
    • Body trail automatically resets at episode boundaries to prevent visual streaking.
  • Documentation

    • Updated continuous renderer documentation/specification to reflect the new body-trail and reset behavior.
  • Tests

    • Added coverage for body history accumulation/reset, full-frame rendering, and heading color contrast.

chrisjz and others added 2 commits June 15, 2026 01:09
…enderer

The continuous renderer drew the worm as a single circle + a heading line that was
nearly the same beige as the body, reading as an arrow rather than a crawling worm.

Draw a path-following, tapered, undulating BODY: the renderer keeps its own deque of
recent positions (it persists across frames and already receives pos — no render-state
or agent change), draws a connected tapered tube through that trail (thick links +
rounded joints), with a travelling sinusoidal crawl-wave overlay; head end is a distinct
brighter marker and the heading indicator is recoloured to a contrasting hue. The body
history resets on an episode-boundary position jump. Renderer-only, non-gating; the worm
remains a point kinematically (the body is a pure visual overlay). Negligible cost.

- pygame_renderer.py Continuous2DRenderer: body-history deque + undulation phase;
  _update_body_history (reset on jump); connected undulating tapered body draw;
  WORM_HEAD/BODY/HEADING colour constants
- tests: history accumulate + episode-reset, full-frame render, heading colour contrast
- OpenSpec change add-continuous-worm-body-render (validate --strict)

Improves the T7 real-worm behavioural-validation legibility + the demo. Visual confirmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Applies the MODIFIED "Worm rendering with continuous heading" requirement
(path-following undulating body + distinct heading colour + head/tail) to the
canonical continuous-fidelity-renderer spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf33f10e-ddda-474a-b257-bf5fc58a45bf

📥 Commits

Reviewing files that changed from the base of the PR and between c32a2be and be175c5.

📒 Files selected for processing (1)
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py

📝 Walkthrough

Walkthrough

Adds a continuous worm body visualization to Continuous2DRenderer in pygame_renderer.py: a bounded position-history deque trails each frame, a sinusoidal lateral undulation overlays the backbone, and a discontinuous position jump clears the history at episode boundaries. Accompanying OpenSpec proposal, design, spec, and task artifacts are archived, the live spec is updated, and new TestWormBody tests validate history behavior and color contrast.

Changes

Continuous Worm Body Rendering

Layer / File(s) Summary
OpenSpec proposal, design, and spec artifacts
openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/proposal.md, openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/design.md, openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/tasks.md, openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/specs/continuous-fidelity-renderer/spec.md, openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/.openspec.yaml, openspec/specs/continuous-fidelity-renderer/spec.md
Adds archived proposal, design, tasks, and spec-delta documents; updates the live spec to require tapered undulating body trail, distinct heading indicator, and episode-boundary history reset in place of the prior single-marker requirement.
Renderer constants and per-instance state
packages/quantum-nematode/quantumnematode/env/pygame_renderer.py
Adds deque import; introduces WORM_HEAD_COLOR, WORM_BODY_COLOR, updated WORM_HEADING_COLOR, and private undulation/reset geometry constants at module scope; extends Continuous2DRenderer.__init__ with _body_history, _last_worm_pos, and _undulation_phase fields.
Body history update and render_frame wiring
packages/quantum-nematode/quantumnematode/env/pygame_renderer.py
Adds _update_body_history method that appends positions to the bounded deque, clears on jumps exceeding _WORM_RESET_JUMP_MM, and advances _undulation_phase; inserts the call into render_frame before drawing.
Worm body drawing in _render_entities
packages/quantum-nematode/quantumnematode/env/pygame_renderer.py
Replaces the prior single-marker worm rendering with backbone construction from position history, per-segment sinusoidal lateral offset and tapered radius, thick body links and joint circles in WORM_BODY_COLOR, and a distinct filled+outlined head marker at the current position.
TestWormBody tests
packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py
Adds TestWormBody covering history accumulation and jump-triggered reset via _update_body_history, full-frame render producing a non-empty surface, and color-contrast assertion that WORM_HEADING_COLOR differs from WORM_BODY_COLOR and WORM_HEAD_COLOR.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • SyntheticBrains/nematode#223: Touches the same openspec/specs/continuous-fidelity-renderer/spec.md worm-rendering requirements while this PR adds the corresponding pygame_renderer.py implementation and test coverage.

Poem

🐛 A worm once just a dot on screen,
Now trails a curving path of green.
Its body wiggles, tapers, glows—
Through history each segment flows.
On episode reset, the deque clears,
A sinusoidal dance appears!
— 🐇 hopping with delight

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a path-following, undulating worm body visualization to the continuous renderer.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-continuous-worm-body-render

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py (1)

459-469: ⚡ Quick win

Missing renderer cleanup in new tests. Unlike other tests in this file that use try/finally blocks to close renderers, these two tests create renderers without cleanup, potentially leaking pygame resources.

  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py#L459-L469: Wrap the test body in try/finally and call r.close() in the finally block.
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py#L471-L477: Same — add try/finally with r.close().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py`
around lines 459 - 469, Both test methods
test_history_accumulates_and_resets_on_jump (lines 459-469) and the second test
method at lines 471-477 in
packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py
are creating renderer instances without proper cleanup, which leaks pygame
resources. For each of these two test methods, wrap the entire test body (after
the renderer creation) in a try/finally block, and call r.close() in the finally
block to ensure the renderer is properly cleaned up after the test runs,
matching the pattern used in other tests in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py`:
- Around line 459-469: Both test methods
test_history_accumulates_and_resets_on_jump (lines 459-469) and the second test
method at lines 471-477 in
packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py
are creating renderer instances without proper cleanup, which leaks pygame
resources. For each of these two test methods, wrap the entire test body (after
the renderer creation) in a try/finally block, and call r.close() in the finally
block to ensure the renderer is properly cleaned up after the test runs,
matching the pattern used in other tests in this file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 87cc94a8-4365-4f1e-ab9c-7691091e863e

📥 Commits

Reviewing files that changed from the base of the PR and between 0f73d75 and c32a2be.

📒 Files selected for processing (8)
  • openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/.openspec.yaml
  • openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/design.md
  • openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/proposal.md
  • openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/specs/continuous-fidelity-renderer/spec.md
  • openspec/changes/archive/2026-06-14-add-continuous-worm-body-render/tasks.md
  • openspec/specs/continuous-fidelity-renderer/spec.md
  • packages/quantum-nematode/quantumnematode/env/pygame_renderer.py
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_continuous_renderer.py

…game leak (review)

Wrap the two renderer-creating TestWormBody tests in try/finally with
r.close(), matching the cleanup pattern used by the other tests in this file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@chrisjz
chrisjz merged commit bdee369 into main Jun 14, 2026
4 checks passed
@chrisjz
chrisjz deleted the add-continuous-worm-body-render branch June 14, 2026 20:59
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.

1 participant