diff --git a/THREADS.md b/THREADS.md
index 3e8f044..8e9c52a 100644
--- a/THREADS.md
+++ b/THREADS.md
@@ -144,6 +144,23 @@ Format: `[lane letter] YYYY-MM-DD — note`
flips back and passes green. Only a native glTF reader can prove it. Green: 16/16, with
`branch_anchor_01` at (-0.96, 3.64, -1.46) — height correctly on +Y.
+[E] 2026-07-16 — ⚠️ **LANE A — three lines needed in selftest.html + index.html. Blocks Lane D too.**
+ No page in the repo has an `
+ I did **not** edit your file (§6 says post the need instead, and you'd asked for selftest.html to stay
+ out of the merge path). `e.test.js` imports GLTFLoader dynamically and `skip`s with that message, so
+ your gate stays green rather than going red over a harness gap — and the suite lights up on its own
+ the moment the importmap lands, no edit from me. Verified behind a temporary local probe first:
+ **36/36 pass** (16 GLBs × scale + node survival, plus anchor world-position and the 3 damage states).
+ Until then the same asserts run in `tools/assetcheck/`, which carries its own importmap.
+
[E] 2026-07-16 — ❓ open q for Lane A: your yard puts house fascia anchors at y=2.6, but `house_yardside`'s
fascia sits at 2.80 (2.90 m ridge), and the facade is 9.20 m against a 30 m north edge. Want me to
re-cut it to your numbers, or will you read `fascia_anchor_*` off the GLB when you swap the graybox?
diff --git a/web/world/js/tests/e.test.js b/web/world/js/tests/e.test.js
index 2a73b3f..2fc8de3 100644
--- a/web/world/js/tests/e.test.js
+++ b/web/world/js/tests/e.test.js
@@ -1,28 +1,160 @@
/**
* Lane E selftests — asset sanity.
+ * Lane E owns this file. Other lanes: yours is js/tests/.test.js.
*
- * Lane E owns this file. Lane A pre-created it so adding your suite never means
- * editing selftest.html.
+ * Why this exists when build_yard_assets.py already verifies: the Blender side
+ * re-imports every GLB and asserts dims, tri budget and node names, but it
+ * CANNOT catch an axis error. Blender exports Z-up→Y-up and imports Y-up→Z-up,
+ * so a broken `export_yup` flips back on the way in and round-trips green. Only
+ * a native glTF reader can prove the file is right, and this is the only one in
+ * the repo. So this suite targets the failures that silently break other lanes:
+ * 1. the GLB loads at all through the vendored loader;
+ * 2. it's in metres with its height on +Y — a model exported in centimetres
+ * looks fine alone and absurd next to a person;
+ * 3. the nodes other lanes query by name survived the export. glTF has no
+ * "empty" type, so anchors arrive as bare Object3D and are exactly what an
+ * exporter prunes.
*
- * Most of Lane E's verification is the Blender-side contact sheet against the
- * 1.7 m ref capsule (PLAN3D §5-E), which this harness can't see. What IS worth
- * asserting here, once the GLBs land, is the stuff that silently breaks the
- * other lanes:
- * 1. every GLB loads without error through the vendored GLTFLoader.
- * 2. scale sanity — a loaded tree's bounding box is 4–9 m tall, the fence
- * panel is ~1.6 m, the shackle is ~0.1 m. A model exported in centimetres
- * looks fine alone and absurd next to a person.
- * 3. the named nodes the other lanes query actually exist:
- * tree_gum_01 → `trunk`, `canopy_*`, `branch_anchor_*`
- * house_yardside → `fascia_anchor_*`
- * Lane A sways the canopies by name; Lane B reads branch anchors.
- *
- * Loading is async — `export default async function run(t)` is supported.
- * Useful import:
- * import { GLTFLoader } from '../../vendor/addons/loaders/GLTFLoader.js';
+ * Standalone version with a fuller report: tools/assetcheck/.
*/
-/** @param {import('../testkit.js').Suite} t */
-export default function run(t) {
- t.skip('yard asset GLBs not landed yet — Lane E');
+import * as THREE from '../../vendor/three.module.js';
+import { assert } from '../testkit.js';
+
+// GLTFLoader is imported DYNAMICALLY, below, and that is deliberate.
+//
+// Every three.js addon imports the bare specifier `three`, which only resolves
+// via an