DEV Community

Cover image for Inside a pupa you can rewind: deriving every frame from t alone (Canvas 2D)
sishiya (しし屋)
sishiya (しし屋)

Posted on

Inside a pupa you can rewind: deriving every frame from t alone (Canvas 2D)

A caterpillar becomes a pupa and then an adult. Inside the pupa, much of the larval body breaks down and the adult body is rebuilt.

I made a page that shows that inside as a side-view diagram, which you can scrub back and forth with a single time slider. The model insect is the tobacco hornworm moth (Manduca sexta), the best-studied species. Canvas 2D, no libraries.

https://sishiya.github.io/manabi-lab/metamorphosis/?lang=en

This post is about how I made sure that "dragging the slider back returns the same picture".

The rule: everything on screen comes from t

Time t is days since pupation. Wandering (when the larva walks around looking for a place to burrow) is −4, and emergence is 18 (18–20 days in the lab).

Ordinary animation advances the previous frame's state a little at a time. Then you cannot build the picture for a time you scrub back to.

So everything on screen is determined only by t and the real-time clock clock. No previous-frame state is kept. Even the fragments scattered from breaking organs are computed from t.

One timetable TL holds everything

What happens when lives in one table, TL. Each row is tagged with how solid its source is:

  • sure: known with dates for Manduca sexta
  • est: an estimate

The "estimate" labels in the on-screen text follow these tags. To change timing, you only edit this table.

Hormone levels are functions of t too. Ecdysone (the molting hormone), for example, has a peak in the prepupa; the adult peak rises from P2.5, is highest at P7–9 and drops sharply at P10. Only the shape (0–1) is shown, not absolute amounts.

Every stage uses "the same 15 boundaries"

Larva, prepupa, pupa and adult look completely different. Drawing each stage separately would make it impossible to connect them.

So every stage's body is described by the same 15 segment boundaries:

  • s = 0 is the tip of the head, 1 the head/prothorax boundary, ..., 14 the tip of the tail
  • each boundary has position X, dorsal height D, ventral V and backbone height Y

Organs are placed at (s, v), with v = +1 on the back surface and −1 on the belly. Morphing from larva to pupa is then just blending values boundary by boundary. Organs that have both a larval and an adult position blend by each organ's own progress.

Breaking organs: fix the randomness by place

Larval muscles and salivary glands break down early in the pupa. breakingTube() draws this:

  1. split the tube into points
  2. give each point a slightly shifted "progress" using randomness
  3. go "connected → fragments → gone" by that progress
// a = how much of the organ remains (1 → 0). f = how broken each point is
const f = R.map((p, i) => clamp((1 - a) * 1.7 - hash(seed, i) * 0.7, 0, 1));
Enter fullscreen mode Exit fullscreen mode

The randomness is a fixed value (a hash) made from the point's index i. Drawing new random numbers every frame would move the fragments each time you scrub back. With a hash, the same t always gives the same fragments in the same places. Blood cells gather to clean them up.

Where I got stuck: wings poking out of the back

Wings start as small pouches inside the thorax and turn inside out in the prepupa.

At first I placed the wings pointing "from the base toward the belly", and the wing body poked out of the back. Putting the base high on the side of the thorax, with the leading edge sloping down toward the belly, matched a real pupa.

Also, rotating the wings straight to the adult orientation right after emergence made them look, from outside, like "wings appearing out of nowhere". So now they expand in the same place and orientation as in the pupa (15–70 minutes after emergence), then rotate at the base and fold (70–85 minutes). That also fits the observation that the wings finish expanding in about 80 minutes and then rotate at the base.

Sharing the slider's time

Many things happen within hours around emergence. In plain days, that would be a sliver of the slider. So the slider-to-time mapping changes by section:

Share of slider Time
76% −6 to 18 days (until emergence)
8% emergence to 15 min (out of the case, out of the soil)
7% to 100 min (wings expand)
3% to 6 hours
6% to 1.5 days

How I check it

Every time, I run through all time in the three views (see-through, cut open, outside) and check for errors:

for (const m of ['in', 'cut', 'out']) {
  __mm.VIEW.mode = m;
  for (let i = 0; i <= 300; i++) {
    __mm.setT(uToT(i / 300));
    if (__mm.err) throw __mm.err;
  }
}
Enter fullscreen mode Exit fullscreen mode

Because the picture depends only on t, calling setT at scattered times gives the same pictures as playing in order.

Main sources

The app's "About this app" lists 15. A few of them:

  • J. W. Truman, L. M. Riddiford (1999) Nature (hormones of complete metamorphosis)
  • M. Jindra, S. R. Palli, L. M. Riddiford (2013) Annual Review of Entomology (Kr-h1, broad, E93)
  • L. M. Schwartz, J. W. Truman (1982) Science (muscles between abdominal segments die after emergence)
  • K. A. Hegstrom, L. M. Riddiford, J. W. Truman (1998) Journal of Neuroscience (remodeling of larval muscles and the flight muscles)

This page is part of Manabi Lab, a series of small learning apps that run in the browser (English and Japanese). The code and this post were written together with AI (Claude).

https://sishiya.github.io/manabi-lab/?lang=en

Top comments (0)