App 2.50.0: the Caterpillar crawls while you drive
A hump runs from the tail to the head, lifted feet swing forward while the others stay planted, and the branch it walks on slides by with the speed. Parked, it settles into its high arch. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -140,6 +140,7 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does *
|
|||||||
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
|
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
|
||||||
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
|
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
|
||||||
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
|
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
|
||||||
|
- 2026-11-07 — App 2.50.0: **the Caterpillar crawls** (owner: "make it so the caterpillar appears to be going when we drive? Using normal caterpillar movement?"). While the car moves it crawls as a caterpillar does: a hump runs from the tail to the head, the body bunches and stretches a little, feet that are lifted swing forward and the others stay planted on a branch whose knots and leaves slide backwards with the speed; the crawl is quicker the faster the car goes (at most 1.2 crawls a second). It does not change its place on screen, so the readings stay put. Parked, it eases back into its high arch (it keeps drawing for that moment only). Two screenshots redone. Tested: unit tests, lint, emulator with sample values (frames 0.25 s apart show the hump travelling and the branch sliding). Not tried on the car.
|
||||||
- 2026-11-07 — App 2.49.1 published (`car-2.49.1`).
|
- 2026-11-07 — App 2.49.1 published (`car-2.49.1`).
|
||||||
- 2026-11-07 — App 2.49.1: **the Caterpillar looks more like the one in the book** (owner: "Make it more like the original caterpillar", with the German edition's cover as the reference). It arches over the middle like the cover's: overlapping chunks of mottled green and teal paper, a yellow fringe along the back, olive feet, and a red-orange head with a yellow face, green eyes and purple antennae; the speed is on the face. Power still lights the segments (blue when regenerating) and the back humps while driving. Still my own drawing of it, not the book's artwork. Two screenshots redone. Tested: unit tests, lint, emulator with sample values. Not tried on the car.
|
- 2026-11-07 — App 2.49.1: **the Caterpillar looks more like the one in the book** (owner: "Make it more like the original caterpillar", with the German edition's cover as the reference). It arches over the middle like the cover's: overlapping chunks of mottled green and teal paper, a yellow fringe along the back, olive feet, and a red-orange head with a yellow face, green eyes and purple antennae; the speed is on the face. Power still lights the segments (blue when regenerating) and the back humps while driving. Still my own drawing of it, not the book's artwork. Two screenshots redone. Tested: unit tests, lint, emulator with sample values. Not tried on the car.
|
||||||
- 2026-11-07 — App 2.49.0 published (`car-2.49.0`).
|
- 2026-11-07 — App 2.49.0 published (`car-2.49.0`).
|
||||||
|
|||||||
@@ -311,7 +311,9 @@ tissue colours on the book's night page. The caterpillar arches over the middle,
|
|||||||
the book's cover: overlapping chunks of mottled green paper with a yellow fringe along the back and
|
the book's cover: overlapping chunks of mottled green paper with a yellow fringe along the back and
|
||||||
olive feet, and a red-orange head with a yellow face, green eyes and purple antennae. The face
|
olive feet, and a red-orange head with a yellow face, green eyes and purple antennae. The face
|
||||||
carries the speed, and its ten segments are the power, lit from the head backwards while drawing and
|
carries the speed, and its ten segments are the power, lit from the head backwards while drawing and
|
||||||
blue while regenerating; its back humps up and down while you drive. On the left the sun is the battery,
|
blue while regenerating. While you drive it crawls the way a caterpillar does: a hump runs from its
|
||||||
|
tail to its head, the feet it lifts swing forward and the others stay planted, and the branch it
|
||||||
|
walks on slides by, faster the faster the car goes. Parked, it settles into its high arch. On the left the sun is the battery,
|
||||||
filling with the charge; below 20 % it turns into a grey moon and "BATTERY LOW" blinks. A big leaf
|
filling with the charge; below 20 % it turns into a grey moon and "BATTERY LOW" blinks. A big leaf
|
||||||
above it holds trip, usage, climate and odometer on paper labels. On the right a clock in a paper
|
above it holds trip, usage, climate and odometer on paper labels. On the right a clock in a paper
|
||||||
circle, and the story's fruit, one apple, two pears, three plums, four strawberries and five
|
circle, and the story's fruit, one apple, two pears, three plums, four strawberries and five
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId "com.stinnesbeck.nilsmg4"
|
applicationId "com.stinnesbeck.nilsmg4"
|
||||||
minSdk 27
|
minSdk 27
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 167
|
versionCode 168
|
||||||
versionName "2.49.1"
|
versionName "2.50.0"
|
||||||
|
|
||||||
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
||||||
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ import java.util.Locale;
|
|||||||
* In the middle the caterpillar, arched like the one on the book's cover (overlapping chunks of
|
* In the middle the caterpillar, arched like the one on the book's cover (overlapping chunks of
|
||||||
* mottled green tissue paper, a yellow fringe along the back, a red-orange head with a yellow face):
|
* mottled green tissue paper, a yellow fringe along the back, a red-orange head with a yellow face):
|
||||||
* the face carries the speed, and its ten segments are the power, lit from the head backwards while
|
* the face carries the speed, and its ten segments are the power, lit from the head backwards while
|
||||||
* drawing, blue while regenerating; its back humps up and down while the car moves. On the left the sun is the battery, filling with the charge, and turning into a
|
* drawing, blue while regenerating; while the car moves it crawls the way a caterpillar does: a hump runs from tail to head, its feet
|
||||||
|
* lifting and planting, on a branch that slides by. On the left the sun is the battery, filling with the charge, and turning into a
|
||||||
* grey moon with "BATTERY LOW" blinking below 20 %; above it a big leaf holds trip, usage, climate
|
* grey moon with "BATTERY LOW" blinking below 20 %; above it a big leaf holds trip, usage, climate
|
||||||
* and odometer on paper labels. On the right the clock in a paper circle, and the
|
* and odometer on paper labels. On the right the clock in a paper circle, and the
|
||||||
* story's fruit: one apple, two pears, three plums, four strawberries and five oranges, a fruit
|
* story's fruit: one apple, two pears, three plums, four strawberries and five oranges, a fruit
|
||||||
@@ -59,6 +60,8 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
private Bitmap sky;
|
private Bitmap sky;
|
||||||
private int skyW, skyH;
|
private int skyW, skyH;
|
||||||
private float time;
|
private float time;
|
||||||
|
/** Where the crawl is (whole numbers are one full crawl), how far the ground has gone by, and how much it crawls (0 to 1). */
|
||||||
|
private float phase, travel, crawl;
|
||||||
private long lastFrame;
|
private long lastFrame;
|
||||||
private MusicTap music;
|
private MusicTap music;
|
||||||
|
|
||||||
@@ -87,13 +90,18 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
if (w <= 0 || h <= 0) return;
|
if (w <= 0 || h <= 0) return;
|
||||||
float u = Math.min(h / 640f, w / 1100f);
|
float u = Math.min(h / 640f, w / 1100f);
|
||||||
boolean moving = v.speedKmh != null && v.speedKmh > 0.5f;
|
boolean moving = v.speedKmh != null && v.speedKmh > 0.5f;
|
||||||
|
long now = SystemClock.uptimeMillis();
|
||||||
|
float dt = lastFrame == 0 ? 0f : Math.min(0.2f, (now - lastFrame) / 1000f);
|
||||||
|
lastFrame = moving || crawl > 0.01f ? now : 0;
|
||||||
if (moving) {
|
if (moving) {
|
||||||
long now = SystemClock.uptimeMillis();
|
float kmh = Math.min(v.speedKmh, 130f);
|
||||||
time += lastFrame == 0 ? 0f : Math.min(0.2f, (now - lastFrame) / 1000f);
|
time += dt;
|
||||||
lastFrame = now;
|
// One crawl takes a second or two; faster the faster the car goes, and the ground slides by.
|
||||||
} else {
|
phase = (phase + dt * Math.min(1.2f, 0.4f + kmh / 90f)) % 1000f;
|
||||||
lastFrame = 0;
|
travel = (travel + dt * kmh * 1.1f * u) % 100000f;
|
||||||
}
|
}
|
||||||
|
// It settles into its resting arch when the car stops, and sets off again when it moves.
|
||||||
|
crawl += ((moving ? 1f : 0f) - crawl) * Math.min(1f, dt * 4f);
|
||||||
if (sky == null || skyW != w || skyH != h) buildSky(w, h, u);
|
if (sky == null || skyW != w || skyH != h) buildSky(w, h, u);
|
||||||
c.drawBitmap(sky, 0, 0, image);
|
c.drawBitmap(sky, 0, 0, image);
|
||||||
boolean song = drawSong(c, w, u);
|
boolean song = drawSong(c, w, u);
|
||||||
@@ -104,8 +112,9 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
drawClock(c, w, u);
|
drawClock(c, w, u);
|
||||||
drawFruit(c, w, u);
|
drawFruit(c, w, u);
|
||||||
drawGears(c, w, u);
|
drawGears(c, w, u);
|
||||||
|
drawBranch(c, w, u);
|
||||||
drawCaterpillar(c, w, u, pulse);
|
drawCaterpillar(c, w, u, pulse);
|
||||||
keepAnimating(moving || pulse > 0f || song, 33);
|
keepAnimating(moving || crawl > 0.01f || pulse > 0f || song, 33);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void label(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a) {
|
private void label(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a) {
|
||||||
@@ -441,6 +450,31 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- The branch it crawls on ----------
|
||||||
|
|
||||||
|
/** A branch under the caterpillar; its knots and leaves slide backwards as the car goes. */
|
||||||
|
private void drawBranch(Canvas c, int w, float u) {
|
||||||
|
float hx = w / 2f + 250f * u, y = 410f * u + 87f * u;
|
||||||
|
float l = hx - 640f * u, rr = hx + 110f * u, span = rr - l;
|
||||||
|
line.setColor(0xFF6B4A2B);
|
||||||
|
line.setStrokeWidth(20f * u);
|
||||||
|
c.drawLine(l, y, rr, y, line);
|
||||||
|
line.setColor(0x44FFD59A);
|
||||||
|
line.setStrokeWidth(4f * u);
|
||||||
|
c.drawLine(l + 6f * u, y - 6f * u, rr - 6f * u, y - 6f * u, line);
|
||||||
|
for (int k = 0; k < 8; k++) {
|
||||||
|
float x = l + (((k * span / 8f) - travel) % span + span) % span;
|
||||||
|
fill.setColor(0xFF4A3018);
|
||||||
|
r.set(x - 9f * u, y - 5f * u, x + 9f * u, y + 6f * u);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
if (k % 2 == 0) {
|
||||||
|
fill.setColor(0xFF3FA34D);
|
||||||
|
r.set(x - 4f * u, y + 6f * u, x + 20f * u, y + 18f * u);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- The caterpillar: speed and power ----------
|
// ---------- The caterpillar: speed and power ----------
|
||||||
|
|
||||||
/** Tissue-paper greens for a segment: base colours, a light patch and a teal patch, by state. */
|
/** Tissue-paper greens for a segment: base colours, a light patch and a teal patch, by state. */
|
||||||
@@ -458,12 +492,20 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
float hx = w / 2f + 250f * u, hy = 410f * u, hr = 76f * u;
|
float hx = w / 2f + 250f * u, hy = 410f * u, hr = 76f * u;
|
||||||
int lit = Tally.ofPower(v.powerKw, 150f, 50f, SEGMENTS);
|
int lit = Tally.ofPower(v.powerKw, 150f, 50f, SEGMENTS);
|
||||||
int[][] palette = regen() ? REGEN : DRAWING;
|
int[][] palette = regen() ? REGEN : DRAWING;
|
||||||
// The arch: highest in the middle, and it humps up and down while the car moves.
|
// At rest it arches high in the middle. Crawling, a real caterpillar pulls its rear forward and
|
||||||
float arch = 92f * u + (float) Math.sin(time * 3.5f) * 16f * u;
|
// humps the body up, and the hump runs from the tail to the head while the feet it lifts swing
|
||||||
float[] xs = new float[SEGMENTS], ys = new float[SEGMENTS];
|
// forward and the others stay planted; the body also bunches and stretches as it goes.
|
||||||
|
float arch = (92f * (1f - crawl) + 30f * crawl) * u;
|
||||||
|
float cycle = phase % 1f;
|
||||||
|
float stretch = 1f + 0.08f * crawl * (float) Math.sin(cycle * 2f * Math.PI);
|
||||||
|
float wave = 1.25f - cycle * 1.6f;
|
||||||
|
float[] xs = new float[SEGMENTS], ys = new float[SEGMENTS], lift = new float[SEGMENTS];
|
||||||
for (int i = 0; i < SEGMENTS; i++) {
|
for (int i = 0; i < SEGMENTS; i++) {
|
||||||
xs[i] = hx - 92f * u - i * 48f * u;
|
float along = (SEGMENTS - 1 - i) / (SEGMENTS - 1f);
|
||||||
ys[i] = hy + 24f * u - arch * (float) Math.sin(Math.PI * (i + 0.7) / (SEGMENTS + 0.4));
|
float d = Math.max(0f, 1f - Math.abs(along - wave) / 0.4f);
|
||||||
|
lift[i] = crawl * d * d * (3f - 2f * d);
|
||||||
|
xs[i] = hx - 92f * u - i * 48f * u * stretch;
|
||||||
|
ys[i] = hy + 24f * u - arch * (float) Math.sin(Math.PI * (i + 0.7) / (SEGMENTS + 0.4)) - lift[i] * 46f * u;
|
||||||
}
|
}
|
||||||
// From the tail towards the head, each overlapping the one before.
|
// From the tail towards the head, each overlapping the one before.
|
||||||
for (int i = SEGMENTS - 1; i >= 0; i--) {
|
for (int i = SEGMENTS - 1; i >= 0; i--) {
|
||||||
@@ -480,7 +522,9 @@ final class CaterpillarView extends ClusterCanvasView {
|
|||||||
if (i % 2 == 0) {
|
if (i % 2 == 0) {
|
||||||
line.setColor(0xFF8A7B2E);
|
line.setColor(0xFF8A7B2E);
|
||||||
line.setStrokeWidth(8f * u);
|
line.setStrokeWidth(8f * u);
|
||||||
c.drawLine(x, y + 46f * u, x + 3f * u, y + 60f * u, line);
|
// A lifted foot swings forward and up; a planted one is on the branch.
|
||||||
|
float up = lift[i] * 12f * u;
|
||||||
|
c.drawLine(x, y + 46f * u, x + (3f + lift[i] * 12f) * u, y + 60f * u - up, line);
|
||||||
}
|
}
|
||||||
// The chunk of paper with two patches of other tissue on it.
|
// The chunk of paper with two patches of other tissue on it.
|
||||||
r.set(x - 37f * u, y - 48f * u, x + 37f * u, y + 48f * u);
|
r.set(x - 37f * u, y - 48f * u, x + 37f * u, y + 48f * u);
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 266 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 233 KiB |
Reference in New Issue
Block a user