LAWSOFUX · motion lab
Concept · doherty-threshold

Doherty Threshold

Center-out radial pulse palette · magenta / cream takeaways · 5

Definition

A system that responds to a user inside roughly 400 milliseconds keeps the user engaged; slower than that and the user disengages, multitasks, or drops the session. Speed is not just performance — it is the single most reliable lever for sustained attention. Below the threshold, work and tool merge; above it, the human waits.

Why it matters for ShurIQ reports

A viz-hub viewport that hangs for two seconds while it computes a re-rank loses the executive in real time. Skeleton states, optimistic UI on filter changes, and chart-by-chart streaming render are how a dense intelligence brief stays inside the threshold. Perceived performance matters more than measured performance — a 350ms paint with motion always beats a 250ms paint that looks frozen.

Takeaways

Visual motion language

Every interaction returns a sub-100ms acknowledgement (subtle scale-pulse on press), then a sequence-reveal of content as data lands. Numbers count-up rather than appearing instantly, signaling computation.

Cavalry recreation seed. 4 concentric circles (ø500, ø380, ø260, ø140) all darker magenta at 0.5 opacity, plus center 60px cream pupil. Pulse all rings inward-out: ring radii expand 1→1.04 in 200ms, hold 200ms, contract 1.04→1 in 200ms — total <400ms cycle (this references the 400ms "Doherty threshold" itself). Loop continuously.

Origins

Walter J. Doherty and Arvind J. Thadani, 1982 — IBM Systems Journal study replacing the prior 2-second standard.

Cavalry scene

The script below builds this concept's motion in Cavalry through the Stallion bridge. Pipe to cavalry_run_script via MCP, or paste into Cavalry's JavaScript Editor.

// Laws of UX · doherty-threshold · Cavalry scene
// Motion family: center-out radial pulse
// Palette: deep magenta + cream
// To run: pipe to cavalry_run_script tool, or paste into Cavalry's JavaScript Editor
// Built 2026-04-30 by ShurAI

(function () {
  var PREFIX = "claude_lawofux_doherty-threshold_";

  var existing = api.getAllSceneLayers();
  for (var i = 0; i < existing.length; i++) {
    try {
      var nm = api.getNiceName(existing[i]);
      if (nm && nm.indexOf("claude_lawofux_") === 0) api.deleteLayer(existing[i]);
    } catch (e) {}
  }

  var BG    = "#5A1E4D";
  var DARK1 = "#4A1740";
  var DARK2 = "#6E2A60";
  var CREAM = "#D6CDB0";

  var bg = api.primitive("rectangle", PREFIX + "bg");
  api.set(bg, { "generator.dimensions": [1080, 1080] });
  api.setFill(bg, true);
  api.set(bg, { "material.materialColor": BG });

  // Concentric circles
  var rings = [
    { name: "r1", radius: 250, color: DARK1 },
    { name: "r2", radius: 190, color: DARK2 },
    { name: "r3", radius: 130, color: DARK1 },
    { name: "r4", radius:  70, color: DARK2 }
  ];
  var ringIds = [];
  for (var i = 0; i < rings.length; i++) {
    var r = rings[i];
    var e = api.primitive("ellipse", PREFIX + "ring_" + r.name);
    api.set(e, {
      "generator.radius": [r.radius, r.radius],
      "position.x": 0, "position.y": 0,
      "opacity": 0
    });
    api.setFill(e, true);
    api.set(e, { "material.materialColor": r.color });
    var inF = i * 8;
    var doneF = inF + 14;
    api.keyframe(e, inF,   { "opacity": 0  });
    api.keyframe(e, doneF, { "opacity": 50 });

    // Doherty pulse: 400ms cycle = ~10 frames @ 24fps
    api.keyframe(e, 60,  { "scale.x": 1.0,  "scale.y": 1.0  });
    api.keyframe(e, 65,  { "scale.x": 1.04, "scale.y": 1.04 });
    api.keyframe(e, 70,  { "scale.x": 1.0,  "scale.y": 1.0  });
    api.keyframe(e, 80,  { "scale.x": 1.0,  "scale.y": 1.0  });
    api.keyframe(e, 85,  { "scale.x": 1.04, "scale.y": 1.04 });
    api.keyframe(e, 90,  { "scale.x": 1.0,  "scale.y": 1.0  });
    ringIds.push(e);
  }

  // Cream pupil
  var pupil = api.primitive("ellipse", PREFIX + "pupil");
  api.set(pupil, {
    "generator.radius": [30, 30],
    "position.x": 0, "position.y": 0,
    "opacity": 0
  });
  api.setFill(pupil, true);
  api.set(pupil, { "material.materialColor": CREAM });
  api.keyframe(pupil, 36, { "opacity": 0   });
  api.keyframe(pupil, 50, { "opacity": 100 });

  var layerCount = api.getAllSceneLayers().length;
  console.log("scene built: doherty-threshold (" + layerCount + " layers)");
})();