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:
- split the tube into points
- give each point a slightly shifted "progress" using randomness
- 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));
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;
}
}
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).
Top comments (0)