assetcheck/look.html: the verify that answers "is this the right shape"
Dims, tri counts and node names all pass on a bike leaning the wrong way and
a gutter that never fell. The Blender contact sheet is close but renders
through Blender's view transform — it showed this sprint's jacaranda as
near-white lavender when the material is #8C7FC0, so a colour call made there
is a call about Blender, not about the game.
Smallest thing that answers the question: the game's own vendored three.js,
daylight, a ground plane, and the 1.7 m ref capsule beside whatever you name.
/tools/assetcheck/look.html?a=swing_set_01,tree_jacaranda_01&cam=10,4.5,22
It caught two things this sprint the numbers could not: the lilac reads lilac
in the real renderer (the thumb was lying), and the swing set's wreck lands on
+Z with a 2.9 m reach against a 0.95 m standing footprint — which is now baked
as a placement extra and asserted.
Sized at draw time, every time: a tab that boots hidden reports 0x0 and you
get a correct scene rendered into nothing. That cost me the first screenshot
of this very page, so it is in the MANUAL gotcha list where it already was.
This commit is contained in:
parent
b9b64604b3
commit
ba062cf679
@ -17,6 +17,7 @@ file.*
|
||||
| does rigging save the garden here? | `tools/garden_bench/` + `tools/site_audit/gardenfly.js` | garden outcomes, separation targets, margin rule |
|
||||
| per-anchor wind exposure over a storm | `tools/storm_envelope/` | the storm-side second harness |
|
||||
| regenerate all canon GLBs | `blender -b -P tools/blender/build_yard_assets.py` | deterministic; `--only <name>` for one asset |
|
||||
| does this asset LOOK right? | `tools/assetcheck/look.html?a=name1,name2` | the game's renderer + the 1.7 m capsule; the only check that answers "wrong shape" |
|
||||
| job-sheet/invoice design preview | `tools/jobsheet/index.html?v=...` | E's design-ahead handover pattern |
|
||||
| deploy to partly.party/hardyards | `sh tools/deploy_hardyards.sh` | ships `web/` ONLY; self-verifies |
|
||||
|
||||
@ -61,8 +62,17 @@ the site JSON, wired explicitly, so a site says what it contains. The bike
|
||||
- `rating_hint` (float) on anchor nodes — effective failure = `hw.rating ×
|
||||
ratingHint`. Unset ⇒ 1 at adoption; the sim reads the anchor, live.
|
||||
- `collateral` (string) on anchor nodes — what breaks when this anchor's
|
||||
corner blows. `collateral_key` on a structure whose priced thing has a
|
||||
different name than the structure (house → "gutter").
|
||||
corner blows. `collateral_key` on ANY asset that carries a price, naming
|
||||
which collateral string that price answers to. Not just the odd ones out
|
||||
(house → "gutter"): the carport went five sprints resolving only because
|
||||
site_02 happens to id its structure "carport", and a second carport placed
|
||||
in the editor would have been a free failure.
|
||||
- `tie_off: false` on an `*_anchor` node that is NOT a tie-off (a carry point,
|
||||
a light hint, a stand position). **Silence is not neutral here** —
|
||||
`adoptAnchor` does `rating_hint ?? 1`, so an unrated anchor node is the best
|
||||
steel in the game the moment a site names it. Three e.test.js rules enforce
|
||||
this class of honesty across every GLB: no silent anchors, no collateral
|
||||
string nobody prices, no `anchor_type` outside `ANCHOR_TYPE`.
|
||||
- Named nodes matter: `pickup_anchor` (where carried items sit), the
|
||||
`fascia_anchor_*` family, etc. Check `world.js` adoptAnchor before renaming
|
||||
anything.
|
||||
@ -72,7 +82,13 @@ the site JSON, wired explicitly, so a site says what it contains. The bike
|
||||
toward three.js **−Z**. Never write a docstring about orientation without
|
||||
measuring the exported GLB in three.js coords — E's bike comment lied about its
|
||||
own geometry and only a browser-coords assert caught the flip. Dims/tri-count
|
||||
verifies CANNOT catch a wrong shape or a flipped lean; look at the render.
|
||||
verifies CANNOT catch a wrong shape or a flipped lean; look at the render —
|
||||
`tools/assetcheck/look.html` exists for exactly that, and the Blender contact
|
||||
sheet is NOT a substitute (its view transform rendered a #8C7FC0 canopy as
|
||||
near-white; a colour judgement made there is a judgement about Blender).
|
||||
Better still, bake the orientation claim as an extra and assert the claim
|
||||
against the geometry, so the note and the mesh can only lie together
|
||||
(`swing_set_01.wreck_falls_toward`).
|
||||
|
||||
## Authoring a site (a "level")
|
||||
|
||||
|
||||
107
tools/assetcheck/look.html
Normal file
107
tools/assetcheck/look.html
Normal file
@ -0,0 +1,107 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>assetcheck — LOOK at it, in the real renderer</title>
|
||||
<!--
|
||||
The verify that dims and node names cannot do.
|
||||
|
||||
build_yard_assets.py re-imports each GLB and asserts its box, its tri count
|
||||
and its node names; assets_in_three.html proves the axis mapping natively.
|
||||
Neither can answer "is this the right SHAPE" — a bike leaning the wrong way
|
||||
and a gutter that never fell both pass every number in the repo. The Blender
|
||||
contact sheet is close, but it renders through Blender's view transform: it
|
||||
showed this sprint's jacaranda as near-white lavender when the material is
|
||||
#8C7FC0, so a colour judgement made there is a judgement about Blender.
|
||||
|
||||
This page is deliberately the smallest thing that answers the question: the
|
||||
game's own vendored three.js, daylight, a ground plane, and the 1.7 m ref
|
||||
capsule standing beside whatever you name.
|
||||
|
||||
/tools/assetcheck/look.html?a=swing_set_01,tree_jacaranda_01
|
||||
...&cam=12,4.5,20&look=0,2,0 camera position / aim
|
||||
...&a=tramp_01:debris an asset under models/debris/
|
||||
|
||||
Assets are laid out along X in the order given, spaced by their own width,
|
||||
with the capsule first. Served by server.py from the repo root.
|
||||
-->
|
||||
<style>body{margin:0;background:#a8bccc;overflow:hidden}
|
||||
#hud{position:fixed;left:8px;top:8px;font:12px/1.5 ui-monospace,Menlo,monospace;
|
||||
color:#22303a;background:#ffffffaa;padding:6px 9px;border-radius:4px}</style>
|
||||
<div id="hud">loading…</div>
|
||||
<script type="importmap">
|
||||
{ "imports": { "three": "/web/world/vendor/three.module.js",
|
||||
"three/addons/": "/web/world/vendor/addons/" } }
|
||||
</script>
|
||||
<script type="module">
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||
|
||||
const q = new URLSearchParams(location.search);
|
||||
const names = (q.get('a') ?? 'swing_set_01,tree_jacaranda_01,tree_gum_01').split(',');
|
||||
const vec = (s, d) => (s ?? d).split(',').map(Number);
|
||||
|
||||
const r = new THREE.WebGLRenderer({ antialias: true });
|
||||
document.body.appendChild(r.domElement);
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color('#a8bccc');
|
||||
// Roughly the game's daylight: a bright sky dome over grass bounce, plus a sun
|
||||
// high and to one side. Not the storm grade — this is for reading a shape, and
|
||||
// a shape you can only read in perfect light is a shape that does not work.
|
||||
scene.add(new THREE.HemisphereLight('#cfe2f0', '#6d7a52', 1.1));
|
||||
const sun = new THREE.DirectionalLight('#fff3df', 2.0);
|
||||
sun.position.set(6, 10, 4);
|
||||
scene.add(sun);
|
||||
const ground = new THREE.Mesh(new THREE.PlaneGeometry(120, 120),
|
||||
new THREE.MeshStandardMaterial({ color: '#7e8f5c' }));
|
||||
ground.rotation.x = -Math.PI / 2;
|
||||
scene.add(ground);
|
||||
|
||||
const loader = new GLTFLoader();
|
||||
const load = async (spec) => {
|
||||
const [name, sub] = spec.split(':');
|
||||
const dir = sub ? `${sub}/` : '';
|
||||
const g = await loader.loadAsync(`/web/world/models/${dir}${name}_v1.glb`);
|
||||
// The optional-node flag has to be honoured here too, or the garden bed
|
||||
// shows all three wilt states at once and you "see" a bug that isn't one.
|
||||
g.scene.traverse((o) => { if (o.userData?.hidden_by_default) o.visible = false; });
|
||||
return { name, obj: g.scene };
|
||||
};
|
||||
|
||||
const items = [{ name: 'ref_capsule', obj: (await load('ref_capsule')).obj },
|
||||
...(await Promise.all(names.map(load)))];
|
||||
|
||||
let x = 0;
|
||||
const lines = [];
|
||||
for (const it of items) {
|
||||
const size = new THREE.Box3().setFromObject(it.obj).getSize(new THREE.Vector3());
|
||||
x += size.x / 2 + 0.9;
|
||||
it.obj.position.x = x;
|
||||
x += size.x / 2;
|
||||
scene.add(it.obj);
|
||||
lines.push(`${it.name.padEnd(22)} ${size.x.toFixed(2)} × ${size.y.toFixed(2)} × ${size.z.toFixed(2)} m`);
|
||||
}
|
||||
|
||||
const cam = new THREE.PerspectiveCamera(38, 1, 0.1, 300);
|
||||
cam.position.set(...vec(q.get('cam'), `${x * 0.55},4.0,${x * 0.95 + 6}`));
|
||||
cam.lookAt(...vec(q.get('look'), `${x / 2},2,0`));
|
||||
|
||||
// Size at DRAW time, every time, and never once at module scope: a tab that
|
||||
// boots hidden reports 0×0 and you get a correct scene rendered into nothing.
|
||||
// (MANUAL, "hidden-canvas boot needs one resize before projections are sane" —
|
||||
// it cost me the first screenshot of this very page.)
|
||||
const draw = () => {
|
||||
r.setSize(innerWidth, innerHeight, false);
|
||||
r.domElement.style.width = '100%';
|
||||
r.domElement.style.height = '100%';
|
||||
cam.aspect = innerWidth / Math.max(innerHeight, 1);
|
||||
cam.updateProjectionMatrix();
|
||||
r.render(scene, cam);
|
||||
};
|
||||
addEventListener('resize', draw);
|
||||
draw();
|
||||
|
||||
document.getElementById('hud').textContent =
|
||||
`three r${THREE.REVISION} — capsule is 1.70 m\n${lines.join('\n')}`;
|
||||
document.getElementById('hud').style.whiteSpace = 'pre';
|
||||
window.__ready = true;
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user