Technical record · updated
duh & P, a technical record
How the site’s two companions are built. duh is the soft black dot that lives on every human page: a stable center with a deformable outline that you can pet, drag and throw. P is the little robot that came first; it keeps its motion playground on the project page and its articulated model in the 3D home. Both are original browser characters that move with authored motion, not a physics engine or a language model.
Neither character uses a camera, a microphone, audio or a remote model, and neither is an assistant. Their remarks are local strings. Pointer proximity is the only visitor signal, and listening is a visual pose. No visitor study has been run, so nothing here claims that they improve engagement.
- 6 systems
- 20 techniques
- 12 design moves
- 5 open problems
- 17 sources
Each system lists its techniques: what each one does, how it works, the files that hold it, the research it borrows from, what it cannot do yet, and the next moves worth trying. The design history keeps every move in order, with what the result said back.
Programs can read the same record as JSON. In the repository, python bin/design_record_brief.py pip turns it into a brief for the next pass, and new work is appended to _data/design_records/pip.yml.
duh’s body
duh is a stable center collider wrapped in a deformable outline. The physics, the silhouette, the mood and the shape repertoire are separate, so each can change without breaking the others.
A center collider with explicit gesture states
Shipped · Oct 8, 2026
duh’s center moves on fixed 1/120 s steps with a velocity ceiling and damped wall contacts, and its gestures run through six named states.
How it works, files and sources
duh-motion.mjs integrates the center at fixed 1/120 s steps, at most six from one 50 ms input frame, under a 1,600 px/s ceiling, with damped contacts against the viewport walls. Contacts with text lines are swept, a moving circle against rectangles, so a fast throw cannot skip a thin paragraph, and a surface the body already overlaps at release is ignored. Throw velocity comes from recent timestamped release samples, and a stationary hold discards old velocity. The states are REST, PLAY, HELD, AIRBORNE, TIDY and RETREAT. There is no queued animation, a new drag interrupts motion at once, and cancellation never becomes a throw. On phones the throw speed limit scales down with the screen width.
- Files
-
-
assets/js/companion/duh-motion.mjsCenter physics, gesture states and swept contacts. -
docs/duh-companion.mdArchitecture and interaction contract.
-
- Tests
- Known limits
-
- A bounded 2D illusion for a reading page, not a layout physics engine.
An inertial shell around the center
Shipped · Oct 8, 2026
A bounded secondary shell adds gravity sag, grip lag, direction-aware stretch and damped shaking without moving the collider.
How it works, files and sources
duh-body.mjs drives the shell from the grip’s acceleration, with substeps that bound even a delayed input frame. Gravity pools the base on a surface, and a held shell hangs below the grip. The contour stays area-normalized, with bounded radial dents. Position-based dynamics is background reading for controlled deformation here; the shell does not implement that solver.
- Files
-
-
assets/js/companion/duh-body.mjsThe inertial shell.
-
- Tests
- Sources
- Known limits
-
- An art-directed approximation, not a material model.
Shapes, eyes and theme-keyed shading
Shipped · Oct 8, 2026
One 96 × 96 canvas draws duh from twenty fixed-angle radii that morph between a dot, an apple, a peach, a watermelon, a rounded square and a triangle.
How it works, files and sources
duh-portrait.mjs joins twenty radii with quadratic midpoints that stay inside their control hull, at a device-pixel ratio of at most 2, and morphs between shapes with an analytic damped radial spring that cannot drift with the frame rate. Volume comes without per-pixel normals: a rim band away from the key light, a key highlight that rides the deformed outline, and a core shadow toward the contact, all keyed to the four time-of-day themes. Fruit stipple and rind stripes are static, not animated noise. The eyes lead an excursion, widen in flight, ease into giggles and close for sleep, and the contact shadow uses the deformed outline.
- Files
-
-
assets/js/companion/duh-portrait.mjsContour, shapes, eyes and shading. -
_sass/_duh.scssTheme tokens, the character button and the play corner.
-
- Tests
- Known limits
-
- At 96 pixels each fruit has very few marks to stay distinct, such as the peach’s seam and blush or the watermelon’s rind.
A soft-body preview
Retired · Oct 13, 2026
An XPBD ring with stretch, bending, area and shape-matching constraints, previewed behind ?duh=soft next to the classic body.
What it was and why it was retired
The ring was attached the way duh is actually held: a grip arc while carried, static friction at the base on a surface, and a centroid anchor only in flight. That gave about 8 px of swing under a 15,000 px/s² shake, 4.5 px of lean from a push, a resting pool 1.1 times as wide as tall, and under 0.5 % volume error. Activity counted velocity, not deformation, so a pooled shape stopped keeping the page drawing at 60 fps.
- Why it was retired
- Side by side with the inertial shell, its motion read as jittery and its physics as broken. Its theme-keyed rim, key and core shading moved to the classic body, and the preview’s code and tests were removed.
0102b70c3
Moves in this system: From P to duhSoft duh, held by what holds it
duh on the page
duh shares the reading surface with the words. It perches only in clear space, relocates with a visible jump, and any contact with the page is a reversible illusion that never touches the text itself.
Perches in clear space
Shipped · Oct 8, 2026
duh rests and travels only where it covers no reading text, link, control, canvas or protected figure.
How it works, files and sources
duh-page.mjs uses P’s clearAt helper and chooseDuhPerch over cached obstacle rectangles: prose, headings, definition lists, captions, project fact strips, links, controls, canvases and anything marked data-duh-protect. Style and class changes on the main column or its ancestors invalidate the cache even when the content keeps its size, and a rescan keeps the current position and any still-clear path. P’s moving playground character is not treated as a wall. duh-viewport.mjs finds short gaps that the inherited row grid misses between a phone’s reading block and the coastal footer.
- Files
-
-
assets/js/companion/duh-page.mjsScheduler, pointer capture, clearance, invitations and cleanup. -
assets/js/companion/duh-viewport.mjsNarrow-gap perches on phones. -
assets/js/companion/behaviour.mjsThe shared `clearAt` and `choosePerch` helpers.
-
- Sources
- Known limits
-
- Layout is measured on invalidation rather than every frame, but each rescan still costs main-thread time on a slow phone.
- Next moves
-
- Measure first Profile rescans per invalidation type on a phone-class CPU before optimizing them.
Relocation as a jump
Shipped · Oct 13, 2026
When duh has to move off text, it jumps there with anticipation instead of fading and sliding.
How it works, files and sources
A necessary relocation, such as after landing beside text, is a jump: a 0.1 s wind-up, an arc 28 to 90 px high sized to the distance, over whatever is in the way, and a landing squash at the perch already found clear. Travel duration follows distance, and a minimum-jerk curve gives anticipation, acceleration and a soft landing. The separate contact shadow grows and fades with height over its landing plane. Anticipation and arcs are two of the principles Lasseter carried from hand-drawn animation into computer animation.
- Files
-
-
assets/js/companion/duh-page.mjsRecovery jumps, hops and rolls.
-
- Tests
- Sources
Reversible contact with the page
Shipped · Oct 8, 2026
A strong throw can nudge nearby lines and rock the play corner’s block; everything settles back and duh makes a small repair.
How it works, files and sources
Swept contacts use at most 160 cached surfaces, including the ink-line bounds of short paragraphs, headings and list items. A strong hit moves text by at most 4 px and 0.35°, an off-center hit on a heavy object by at most 3 px with up to 1.8° of rock, and at most three nodes react, with a damped oscillation and a small residue for duh to repair. The motion is an additive compositor transform: text is never split or reparented, and selection clears the effect immediately. A pointer press on a link freezes its visual position until the click is dispatched, with the animation’s hold time pinned synchronously, so the native action keeps its target. The play corner’s four decorative words get pointer-transparent copies that fly and come home.
- Files
-
-
assets/js/companion/duh-page.mjsImpact effects, repair gestures and the play corner. -
_projects/pip.mdThe play corner’s block and loose words.
-
- Tests
- Known limits
-
- Long reading columns stay still on purpose; only short blocks react.
Pets, throws, rests and small adventures
Shipped · Oct 8, 2026
A released click pets duh, rough throws earn a short breather, and in empty space it sometimes hops, rolls or turns into fruit.
How it works, files and sources
Three close clicks give one happy squish, and clicks never count as roughness. Three intentional releases above 850 px/s within 12 seconds arm an eight-second breather after duh settles; collisions never count. A retreat is a small tucked pose at the departure point, and tapping it brings duh back early. An approach needs a stationary fine pointer in empty nearby space, a clear path and a 12-second cooldown, and the anticipated hop stops 43 px from the pointer. Spontaneous hops, rolls and shape changes wait while a visitor types, selects or plays.
- Files
-
-
assets/js/companion/duh-page.mjsInteraction rules and spontaneous actions. -
assets/js/companion/duh-motion.mjsRoughness counting and retreat.
-
- Tests
- Known limits
-
- The thresholds are authored. Whether they feel right over a long reading session is untested.
Moves in this system: Contact that leaves the page intactHidden from screen readers is not hidden from the eyeThe reset that was a ghost
Input, accessibility and stillness
Both companions are optional company on a reading page. They answer keyboard, mouse and touch, stay out of scrolling and selection, and keep a composed still state when motion is reduced or paused.
duh as a semantic button
Shipped · Oct 10, 2026
The character itself is the control. Tap or Enter pets or wakes it, keys move and toss it, and Alt+Shift+D recalls it from anywhere.
How it works, files and sources
A stationary 700 ms hold pauses, Shift-click or R resets in place, P pauses, H greets, T tosses and the arrows move duh. Its native tooltip, accessible description and the project page explain the actions, and its label stays in sync when pausing, waking or resetting. An earlier fixed corner panel and separate Pause and Reset buttons were removed, so pause and reset live on the character. A graphics failure keeps the button and a simple dot. Single-pointer alternatives to dragging follow WCAG’s guidance on dragging movements.
- Files
-
-
assets/js/companion/duh-page.mjsKeyboard, pointer and semantic controls.
-
- Tests
- Sources
Touch without stealing the page
Shipped · Oct 9, 2026
Only the 56-pixel character takes over touch; the rest of the page scrolls and selects as usual, even while browser bars resize the viewport.
How it works, files and sources
Only the character button uses touch-action: none and disables callouts and selection. Pointer capture survives viewport updates, coalesced samples keep the hand’s motion, duplicate samples are ignored, and the release point completes the velocity estimate. Coordinates use the layout viewport; VisualViewport offsets, visible size and safe-area insets define the usable bounds, so a browser-bar resize updates the bounds without canceling the gesture. Cancellation, a second pointer, blur or leaving the page clears the gesture without a throw. Double-tap followed by a tap on clear space moves duh without dragging, and never consumes a link’s native action.
- Files
-
-
assets/js/companion/duh-page.mjsPointer capture and viewport handling. -
assets/js/companion/duh-motion.mjsViewport constraints during a gesture.
-
- Tests
- Known limits
-
- WebKit’s test driver supplies taps and pointer capture but no native touch drag, so a physical iPhone remains a separate check.
- Next moves
-
- Ready A recorded drag, throw and double-tap-move pass on a physical iPhone.
Stillness on request
Shipped · Sep 13, 2026
Under reduced motion or the site’s Pause motion, both companions keep their shapes and expressions but stop pursuing, bouncing and shaking.
How it works, files and sources
duh switches shapes immediately and holds static expressions, with no pursuit, bounce, shake, scattered pieces or animated cleanup, and paused motion accumulates no physics. P composes a still greeting pose and its brief rests end automatically. Pause motion in the theme panel makes the reduced-motion media query match for every listener, so both characters still through the same path as the system preference. Hidden pages stop drawing, and frame intervals are capped rather than replaying background-tab time.
- Files
-
-
assets/js/companion/duh-page.mjsduh’s reduced-motion and pause handling. -
assets/js/companion/studio.mjsThe playground’s composed poses. -
assets/js/theme.jsPause motion, laid over the reduced-motion query.
-
Moves in this system: Phones, fingers and browser bars
P’s model and portrait
P exists in three renderings that share one motion controller: an articulated Blender model in the 3D home, a small analytic WebGL portrait on reading pages, and an enlarged playground on its project page.
An articulated Blender model
Shipped · Sep 14, 2026
A rounded ceramic head, a tapered shell, two convex lenses, two antennae and detached arms, each on its own pivot, exported as one GLB.
How it works, files and sources
bin/build_pip.py authors P in Blender 4.5.9 LTS through its background Python API, with a studio camera and lights, and exports named head, antenna, eye and arm pivots to assets/models/pip/pip.glb (848,688 bytes, 27 meshes). The browser poses those pivots at runtime; there is no skeletal clip or physics. The optical catchlights follow the pupil positions, matching the portrait. A transparent Cycles render becomes the poster and the fallback when graphics fail. The design draws on Reachy Mini’s expressive head and unequal lens eyes and on EVE’s floating tapered body; the geometry is original.
- Files
-
-
bin/build_pip.pyReproducible authoring, GLB export and the Cycles poster. -
artwork/pip/PROVENANCE.mdModel provenance and references. -
assets/js/home-scene/companion.mjsLoads and poses the model in the 3D home.
-
- Sources
- Known limits
-
- Blender was used through its Python API; no hand modeling in the interface is claimed.
An analytic WebGL portrait
Shipped · Sep 13, 2026
Reading pages draw P with analytic surfaces in a small WebGL canvas, so the default page never loads Three.js, the GLB or the house.
How it works, files and sources
portrait.mjs shapes a continuous pear-shaped shell, a recessed seam, two hinged antennae with soft tips and optical lenses with reflections, lit by broad softboxes so curved ceramic and glass read at icon size, with ambient occlusion and a ground shadow. Morning, noon, afternoon and evening change the key light, reflections, eye color and shadow. A lost graphics context leaves the still poster.
- Files
-
-
assets/js/companion/portrait.mjsThe analytic portrait. -
assets/js/companion/bridge.mjsTheme lights shared by the page and the room.
-
- Known limits
-
- The page and room share identity and motion, but their lighting pipelines differ.
A motion playground on the project page
Shipped · Sep 14, 2026
The project page gives P a larger stage with gesture buttons, page encounters to invite and a nap, with its credits in the reading flow.
How it works, files and sources
studio.mjs runs the enlarged playground: Hello, Curious, Got it and Oops previews, a nap that ends by itself, and an invitation to try four page encounters (fly around, squeeze past, open a portal, a little bump). The controllers are hidden without JavaScript. A visible but unowned playground sleeps until it owns the shared companion, and wakes once on ownership, visibility or a back/forward return, without aging the motor.
- Files
-
-
assets/js/companion/studio.mjsThe playground controller. -
_projects/pip.mdThe playground markup and credits.
-
Moves in this system: A companion beside the albumReachy Mini’s head, EVE’s float
P’s motion and attention
One pose controller drives every rendering of P. Gaze leads, the head follows, the body takes its time and the antennae settle last; in the room, a small director stages a visitor encounter.
One pose controller with minimum-jerk timing
Shipped · Sep 14, 2026
Original gestures blend with pointer attention through standard minimum-jerk transitions and damped antenna springs.
How it works, files and sources
motion.mjs owns the hello, curious, nod, peek, repair and sleepy poses, and later listen, stretch and delight. Each transition follows the standard zero-velocity, zero-acceleration quintic, the minimum-jerk trajectory of Flash and Hogan, and an interrupted gesture starts from its current pose. Eyes and neck follow an exact cascaded low-pass response in closed form, so the eyes arrive first at any frame rate; filled pupils widen, squint and wink independently, and fins wave, startle and tuck in. Choreography follows the real frame interval down to 4 fps, with substeps that keep the antenna springs stable. Reachy Mini’s movement API informed the separation of head, antennae and body targets; no SDK or recorded motion is bundled.
- Files
-
-
assets/js/companion/motion.mjsPose vocabulary, gaze blending and springs. -
assets/js/companion/behaviour.mjsShared springs, perches and authored remarks.
-
- Tests
- Known limits
-
- Authored animation, not robot inverse kinematics or flight physics.
A visitor encounter in the room
Shipped · Oct 2, 2026
In the 3D home, P notices a nearby pointer, greets once, listens, then returns to Sirui’s activity or the record.
How it works, files and sources
performance.mjs directs the encounter. A nearby pointer triggers one greeting; staying nearby cannot repeat it, and a new encounter needs an absence and an 18-second cooldown. Eyes lead the neck, the shell follows later and the antennae settle on their own, after the habituation and layered gaze in Pan and colleagues’ robot gaze work. attention.mjs resolves targets in P’s own optical frame, so orbiting the room never makes it look at the wrong side of an object, and accepts a genuine touch tap as an invitation while rejecting pans, cancellations and multiple fingers. Stillness ends an encounter but keeps its cooldown, so resuming does not wave again.
- Files
-
-
assets/js/companion/performance.mjsEncounter director and flight timing. -
assets/js/companion/attention.mjsOptical axes and touch invitations. -
docs/evidence/pip-performance-october.mdEvidence and corrected defects.
-
- Sources
- Known limits
-
- Pointer proximity stands in for a visitor; listening is a pose, and P cannot hear or understand anyone.
Moves in this system: Momentum through bendsNotice, greet, listen, return
P on the page and in the room
On reading pages P plans routes around the actual rendered rectangles; in the room it flies its own measured corridors between safe perches. One owner decides where P lives at any moment.
Routes around rendered text
Shipped · Sep 14, 2026
P flies around prose, figures, links and controls, squeezes through gaps that fit, and opens a pair of portals when a crossing is blocked.
How it works, files and sources
travel.mjs builds a visibility graph over the expanded corners of live obstacle rectangles, capped to the viewport, and rounds the path only where the whole curve has clearance. One arc-length clock carries momentum through bends instead of restarting the easing at every corner, and P looks toward its route during a short departure pause. A squeeze shrinks in place and folds the flippers before a gap that fits the smaller footprint; a blocked crossing uses two portals at clear endpoints, and P moves only while invisible. Scrolling, resizing, pause, reduced motion, hiding and navigation cancel travel and remove the portals.
- Files
-
-
assets/js/companion/travel.mjsVisibility graph, rounded paths, squeezes and portals. -
assets/js/companion/page.mjsP’s page controller, journeys, bumps and remarks.
-
- Tests
- Known limits
-
- Bounded page-space navigation; obstacles are rendered rectangles, never rewritten text.
Rare bumps with a repair
Shipped · Sep 14, 2026
Most encounters are avoidance. A rare nudge to a card or heading has a visible approach, a small reversible wobble and a repair gesture.
How it works, files and sources
After an initial 55 to 105 seconds, automatic bump attempts come every 85 to 150 seconds with a 30 % chance, and proximity or clearance can still suppress them. P never nudges a hovered or focused element, and pause, reduced motion, hiding or navigation cancel the transform. Ordinary remarks wait about 45 to 80 seconds between attempts and disappear after 3.5 seconds.
- Files
-
-
assets/js/companion/page.mjsBumps, repairs and remark timing. -
assets/js/companion/behaviour.mjsRoute-aware authored copy.
-
One owner across page, room and playground
Shipped · Sep 13, 2026
Exactly one renderer owns P at a time, and handing it over neither duplicates its mood nor fast-forwards a flight.
How it works, files and sources
bridge.mjs holds the single companion state that moves between the reading surface, the 3D room and the playground. When the room loses ownership, its director is interrupted without touching the motor the page now drives; regaining it composes a quiet task behind the arrival fade. Active time, route progress and cooldown stay fixed while the page owns P. Since October 8 ordinary pages mount duh, so P’s room adapter stays dormant outside its tests and the project page.
- Files
-
-
assets/js/companion/bridge.mjsShared state and ownership. -
assets/js/home-scene/companion.mjsThe room adapter and director handoff.
-
Flights along measured air corridors
Shipped · Oct 2, 2026
In the room P rises before it moves, cruises 1.65 m above its route, rounds corners inside a 12 cm envelope and settles at locally fitted perches.
How it works, files and sources
clearance.mjs gives P its own corridor around shelf contents and the coat by the stair, measured against the shell’s 26 cm reach below its origin; the human circulation points stay untouched. performance.mjs times each flight with a minimum-jerk distance curve, banks within 0.16 rad, keeps peak speed under 1.6 m/s and finishes with under a centimetre of settling. Room changes queue P’s latest destination until it has physically arrived, because starting a new route mid-flight reset the floor and could jump P 2.6 m.
- Files
-
-
assets/js/companion/clearance.mjsMeasured air corridors and perches. -
assets/js/companion/performance.mjsFlight timing, banking and settling. -
assets/js/home-scene/companion.mjsDestination queue and altitude from active flight age.
-
- Sources
- Known limits
-
- A measured route profile, not a general collision or flight solver; new furniture needs new measurements.
Moves in this system: Expressions and page encountersAir corridors around real shelves
Design history
Each move follows the loop from Donald Schön's The Reflective Practitioner: frame the problem, make a move, let the result talk back, then reframe. The dimensions are the variables the move changed. Oldest first; new moves are added at the end.
-
A companion beside the album
- Frame
- I asked for a playful companion that moves with delayed, damped motion and sometimes acts on its own.
- Dimensions
- silhouette; gaze before body; how often it acts on its own; clearance from reading
- Move
- A white hovering robot with a rectangular visor, drawn by a small analytic WebGL portrait on the page and as articulated geometry in the room, with one owner moving it between them, short remarks and card nudges that restore exactly.
- Backtalk
- In review the rectangular visor and small round body were not enough. I asked for the physical charm of Reachy Mini and the floating ease of EVE.
- Reframe
- Separate the head, antennae and body, and build a real articulated model.
-
Reachy Mini’s head, EVE’s float
- Frame
- P needed a coherent silhouette and a few legible gestures before more behavior.
- Dimensions
- lens shape; antenna length and stiffness; body taper; gesture timing
- Move
- Two unequal convex lenses, independently posed antennae, a tapered ceramic shell and detached arms, authored in Blender and mirrored by the portrait, with minimum-jerk gestures and a project playground with visible credits.
- Backtalk
- The separate lenses and tapered shell helped, but the hollow eye rings still felt cold and the antennae too wiry.
- Reframe
- Softer filled pupils, shorter flexible antennae and shaped flippers, on a larger stage where small differences are easier to judge.
- Sources
-
Expressions and page encounters
- Frame
- P had to share reading pages with the words without covering them or breaking them.
- Dimensions
- route around rectangles; squeeze footprint; portal endpoints; how often accidents happen
- Move
- Filled pupils that widen, squint and wink, and a planner that flies around live text rectangles, squeezes through gaps that fit and opens paired portals across blocked crossings, with rare bumps that have a visible approach and a repair.
- Backtalk
- Browser review caught a narrow-screen invitation choosing a destination only a portal could reach; invited flights and squeezes now search nearby connected clear space first.
- Reframe
- Most encounters should be avoidance, not accidents.
-
Momentum through bends
- Frame
- Travel stopped and restarted at every corner of its route.
- Dimensions
- path continuity; departure anticipation; response rate per part; gesture vocabulary
- Move
- An arc-length path with collision-checked rounded corners carries one clock through bends, P looks toward its route before leaving, eyes, neck, fins, shell and antennae settle at different rates, and listen, stretch and delight join the vocabulary.
- Backtalk
- At slower frame rates journeys ran late and gestures lost their expression (
4468fd08e,627d9d03b), until scheduling followed real time and only spring integration was capped. - Reframe
- A character’s timing belongs to the clock, not to the frame rate.
-
Notice, greet, listen, return
- Frame
- In the room P played unrelated random gestures, so a visitor nearby never got a real encounter.
- Dimensions
- encounter order; habituation and cooldown; eye; neck and shell bandwidth; flight timing
- Move
- A director stages one encounter (notice, a single wave, a listening beat, a return to the task), and flights look before leaving, follow minimum-jerk timing, bank gently and round corridor corners.
- Backtalk
- Integration caught two clock defects, a record lookup that ran before the desk existed and a hover shadow on room time while the world was frozen. A minimal rig reproduced a repeated greeting across a page excursion; three of four new regression tests failed against the old adapter and all pass after the fix.
- Reframe
- Keep the visitor signal small and say so. Listening is a pose.
- Sources
-
Air corridors around real shelves
- Frame
- P’s room flights followed the human circulation path, which runs through shelf contents and past a coat by the stair.
- Dimensions
- cruise height; corridor clearance; perch positions; interrupted destinations
- Move
-
clearance.mjsgives P its own measured corridor at a 1.65 m cruise and four corrected perches, and room changes queue the latest destination until P physically arrives (2b296f060). - Backtalk
- Starting a new room route mid-flight reset the floor and could jump P 2.6 m; the queue keeps the airborne center through pause, reduced motion and page ownership.
- Reframe
- This is a measured route profile, not a flight solver; new furniture needs new measurements.
-
From P to duh
- Frame
- I wanted something softer and less mechanical than the robot, a shape that takes a gentle pet, stretches into a throw and settles back into a corner.
- Dimensions
- silhouette; what moves and what stays stable; which pages get a companion
- Move
- duh, a charcoal dot with small readable eyes, became the default companion on every human page in both homepage modes. P kept its playground and its dormant room adapter.
- Backtalk
- The change swapped the silhouette and kept the interest in attentive motion. The retained room robot could have become a second default character, so ordinary pages mount only duh, and the AI surface stays motion-free.
- Reframe
- Keep a stable center with a small deformable outline, and keep mood, shape and gesture separate.
-
Contact that leaves the page intact
- Frame
- A throw should feel like it lands in the page without moving a single word for real.
- Dimensions
- travel and rotation per hit; how many nodes react; link and selection safety
- Move
- A strong throw nudges at most three short blocks by up to 4 px and 0.35° as additive compositor transforms, and duh makes a small repair gesture as they settle.
- Backtalk
- A press on a link during the effect could move the link before the click landed (
3b1d3b842,7b054b0d1). The effect now freezes on pointer-down and pins its hold time synchronously, so the native click keeps its target. - Reframe
- Selection, scrolling and reset restore everything at once, and nothing persists.
-
Phones, fingers and browser bars
- Frame
- On phones duh had to drag under a finger without stealing page scrolling, and survive browser bars resizing the viewport mid-gesture.
- Dimensions
- touch target; viewport coordinates; gesture cancellation; throw speed on small screens
- Move
- Only the 56-pixel character takes touch. Pointer capture survives viewport updates, VisualViewport offsets and safe-area insets set the bounds, and double-tap then tap moves duh without dragging.
- Backtalk
- WebKit’s test driver supplies taps and capture but no native touch drag, so emulation stops short of a real phone.
- Reframe
- Scale the throw speed limit with phone width, and keep a physical-device pass on the list.
-
Soft duh, held by what holds it
- Frame
- I asked for a softer, simulated duh with studio-quality motion, and the body was an art-directed shell.
- Dimensions
- volume error; settling time; deformation under a held shake and a push; resting width to height; idle frame budget
- Move
- An XPBD ring with stretch, bending, area and shape-matching constraints, previewed behind
?duh=softbeside the classic body. - Backtalk
- Under shaking it first stayed perfectly round, because a uniform force on a free body only translates it. Attaching it the way it is held produced the jelly, about 8 px of swing under a 15,000 px/s² shake with under 0.5 % volume error. A pooled shape had counted as moving and kept the page drawing at 60 fps at idle.
- Reframe
- Compare the two in a matched recording and live; the choice decides the default.
-
The reset that was a ghost
- Frame
- I preferred the classic duh to the soft preview, which read as jittery, and saw a strange free reset of position at the end of a throw.
- Dimensions
- position continuity frame to frame; what moves duh after a landing and why; what to keep from the preview
- Move
- A frame-by-frame trace found no teleport but a recovery that faded duh and slid it to a clear perch, because the recovery hop always failed its path check. Recovery is now a jump with a wind-up, an arc and a landing squash, and the preview’s shading moved to the classic body.
- Backtalk
- The trace shows the jump in three frames, a wind-up, an apex and a landing on the clear line, and the duh and companion suites pass at desktop and phone sizes.
- Reframe
- Watch for any relocation without a visible cause; a character’s moves need one.
- Sources
Open problems
-
Are the interruptions welcome?
No visitor study has been run. Occasional hops, remarks and bumps may charm in a short visit and grate in a long reading session, and the thresholds that decide how often they happen are authored guesses.
Candidate moves
- A small session study with a reading task, comparing duh present, paused and absent, and counting interruptions noticed and dismissed.
-
Cheaper layout scans
duh re-measures its reading obstacles and up to 160 cached contact surfaces whenever layout invalidates. Work that stalls a two-core test runner also stalls a slow phone, which is why the October efficiency work starts with these rescans.
Candidate moves
- Profile rescans per invalidation type on a phone-class CPU.
- Rebuild only the rectangles inside changed subtrees instead of the whole cache.
-
A real touch drag on an iPhone
WebKit’s test driver supplies native taps and pointer capture but no native touch drag, so drag, throw and double-tap-move have only been checked in emulation and Chromium touch injection.
Candidate moves
- A recorded manual pass on a physical iPhone, including a browser-bar resize mid-drag.
-
A soft body that does not read as broken
The XPBD preview was numerically stable and still read as jittery next to the inertial shell. Any return has to beat the shell in a matched recording, at an idle cost of nearly nothing.
Candidate moves
- Use a soft solver only for one held moment, such as a squeeze, and the shell everywhere else.
Read
-
duh inside the 3D home
duh stays on the page in both homepage modes, and the room’s companion slot holds P’s dormant adapter. A 3D duh needs a body model, contacts with the room and an ownership handoff like P’s.
Candidate moves
- Reuse P’s ownership bridge and perches for a 3D duh, starting with one room.
Before changing anything
Read first
-
docs/duh-companion.mdduh’s architecture, interaction contract, safe impacts, mobile behavior and budget. -
artwork/pip/PROVENANCE.mdP’s model, motion, references and their boundaries. -
docs/homepage-desk-scene-brief.mdThe P section covers its page journeys, encounters, remarks and handoff to the 3D home. -
docs/evidence/pip-performance-october.mdP’s visitor performance in the room and the defects integration caught. -
docs/evidence/pip-expressive/README.mdExpressions, page encounters and the responsive checks behind them. -
_projects/pip.mdThe public page, its playgrounds and the visible credits.
Keep intact
- duh is the only default companion on human pages, in both homepage modes. P stays on its project page, and one owner moves P between the page, the room and the playground.
- AI routes stay undecorated and motion-free.
- No camera, microphone, audio, remote model calls or proprietary character assets. Remarks are local, authored strings.
- Real text, links, selection and page order stay intact. Only the play corner’s decorative words become loose copies, and scrolling, selecting, pausing or resetting restores everything at once. Nothing persists across a refresh.
- Reading clearance protects prose, figures, links, controls, canvases and anything marked
data-duh-protect. - Reduced motion and Pause motion keep the shapes and expressions, with no pursuit, bounce, shake or scattered pieces.
- A relocation needs a visible cause, such as a jump; never a half-transparent slide.
- Hidden pages stop drawing, and the idle scheduler samples at one hertz.
- Keep P’s two antennae, unequal convex lenses, white tapered shell, orange dot and detached fins recognizable, and keep the Reachy Mini, dance library and EVE credits visible.
Keep these checks green
-
test/duh-motion.test.mjsInterrupted drags, throws, contacts, retreat and recall, and viewport changes.node --test test/duh-motion.test.mjs test/duh-body.test.mjs -
test/companion.test.mjsP’s travel, gestures, pointer following, perches and seeded curiosity.node --test test/companion.test.mjs -
test/pip-performance.test.mjsP’s room encounters, flights and their timing under slow rendering.node --test test/pip-performance.test.mjs test/pip-attention.test.mjs test/pip-world-ownership.test.mjs -
test/visual/duh.spec.jsduh preserves content, links and selection; forms, four themes and recovery on the character.npm.cmd run test:visual:scene -
test/visual/companion.spec.jsP’s project journeys, ownership, graphics recovery, reduced motion and credits.npm.cmd run test:visual:scene
Sources
- Pollen Robotics and Hugging Face. Reachy Mini documentation. huggingface.co/docs/reachy_mini/index
- Pollen Robotics and Hugging Face. Reachy Mini Python SDK, movement API. huggingface.co/docs/reachy_mini/SDK/python-sdk#movement
- Pollen Robotics. Reachy Mini Dances Library. A reference for nods, tilts, glances and recovery; nothing is bundled. github.com/pollen-robotics/reachy_mini_dances_library
- Pixar Animation Studios. WALL·E, 2008, especially EVE. A visual and motion influence; no film images or models are used. www.pixar.com/wall-e
- Blender Foundation. Blender 4.5 LTS, run through its background Python API. www.blender.org/
- Matthew K. X. J. Pan, Sungjoon Choi, James Kennedy, Kyna McIntosh, Daniel Campos Zamora, Günter Niemeyer, Joohyung Kim, Alexis Wieland and David Christensen. Realistic and Interactive Robot Gaze. IEEE/RSJ IROS 2020, Disney Research. la.disneyresearch.com/wp-content/uploads/root.pdf
- Tamar Flash and Neville Hogan. The Coordination of Arm Movements: An Experimentally Confirmed Mathematical Model. Journal of Neuroscience 5(7), 1985. doi.org/10.1523/JNEUROSCI.05-07-01688.1985
- John Lasseter. Principles of Traditional Animation Applied to 3D Computer Animation. SIGGRAPH 1987. doi.org/10.1145/37402.37407
- Miles Macklin, Matthias Müller and Nuttapong Chentanez. XPBD: Position-Based Simulation of Compliant Constrained Dynamics. Motion in Games 2016. matthias-research.github.io/pages/publications/XPBD.pdf
- Matthias Müller, Bruno Heidelberger, Matthias Teschner and Markus Gross. Meshless Deformations Based on Shape Matching. ACM Transactions on Graphics 24(3), SIGGRAPH 2005. matthias-research.github.io/pages/publications/MeshlessDeformations_S...
- Matthias Müller, Bruno Heidelberger, Marcus Hennix and John Ratcliff. Position Based Dynamics. VRIPHYS 2006; Journal of Visual Communication and Image Representation 18(2), 2007. matthias-research.github.io/pages/publications/posBasedDyn.pdf
- W3C. Pointer Events, Level 3, including the
touch-actionproperty. www.w3.org/TR/pointerevents3/ - MDN Web Docs. VisualViewport. developer.mozilla.org/en-US/docs/Web/API/VisualViewport
- W3C. Understanding Success Criterion 2.5.7, Dragging Movements, WCAG 2.2. www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html
- W3C. Media Queries Level 5, the
prefers-reduced-motionfeature. www.w3.org/TR/mediaqueries-5/#prefers-reduced-motion - W3C. Understanding Success Criterion 2.3.3, Animation from Interactions, WCAG 2.2. www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html
Compiled on October 15, 2026 by Anthropic’s Claude from the code, the companion notes, P’s provenance file and the October studio log, for Sirui Tao to review and extend.