HardYards/web/world/index.html
type-two f3bf038515 Sprint 14 gate 1 close: round-trip pinned, pool items landed, free-carport fixed
Gate 1.5 -- the round trip. a.test drives the editor's OWN placement function
(placeEntry, exported for exactly this) through
template -> place -> export -> parse -> validateSite -> createWorld -> dress,
then asserts the GLB-backed anchors ADOPTED and the carport's 0.22/0.30 came
out the far side. Mutation-checked with five deliberate breakages; four went
red and ONE DID NOT -- the key-order test shuffled only root keys, every one
of which is in KEY_ORDER, so the alphabetical fallback it claimed to protect
was never reached. Rewritten around unknown keys, re-mutated, red in both
halves. A determinism test that passed while testing nothing.

Picking bug, found by dispatching real PointerEvents rather than reading the
code: clicking an object's FOOT selected nothing, because the pick handle is a
cylinder centred 1.7 m up and from a raised camera the ray has already run
~2 m horizontally by the time it climbs there. pickRef now falls back to
nearest-placed-thing within 1.3 m of the ground hit -- camera-angle
independent, so it can't rot when the pitch changes.

Phantom sail (pool, filed unlanded in S13) -- LANDED and negative-controlled.
Teardown factored into disposeSailView() and called from loadSiteInto before
refreshCameraSolids (which reads sailView and would re-register a disposed
mesh as a camera collider). Rig state goes with the view. Verified through the
real game both ways: with the fix removed the cloth still hangs over the
corner block, screenshotted.

Dev-line (pool) -- two findings, both landed. The line counted `pieces` only,
so it read "debris 0" while seven leaves flew; now reports both populations
off C's leafCount. And index.html's boot-failure handler wrote into #dev,
which is display:none off localhost -- so a failed boot on partly.party showed
a stranger a blank page. It has its own visible #fatal element now.

E's gap 2, runtime half -- and it was worse than "one click away": nextId
namespaces structures s1, s2..., so the FIRST carport the editor places was
already free. collateralFor/wreckStructure/isWrecked now resolve through
structKey (site collateralKey, then GLB collateral_key, then id last so every
existing site is untouched). Negative-controlled: id-equality prices an
editor carport to null. E's tie_off:false honoured at both ends. E's swing set
and jacaranda are in the palette behind a `requires` gate, so they switch
themselves on when the ANCHOR_TYPE widening merges.

Also: `return t.skip(msg)` is a fake pass -- Suite.skip pushes a new result and
lets the current test record as pass. a.test had six; all converted to the
documented 'SKIPPED --' idiom.

Selftest 373/0/0, no skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:14:34 +10:00

81 lines
3.6 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>HARD YARDS — rig it, then survive the night</title>
<style>
html, body { margin: 0; height: 100%; overflow: hidden; background: #9fc4dd; }
canvas { display: block; width: 100%; height: 100%; }
#dev, #help {
position: fixed; left: 10px;
font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
color: #eef4f8; text-shadow: 0 1px 2px #0009;
pointer-events: none; user-select: none;
}
/* SPRINT13 gate 3 — the dev line moved out from under Lane B's anchor panel
(top:12/left:12; this was top:10/left:10, so they typeset on top of each
other all sprint — QA snag list). Top-RIGHT now, and hidden unless asked
for: see main.js's dev-line block. A stranger on partly.party was reading
"60 fps · storm 12.3s · debris 4" on the front door of a public game, which
is a dev build talking to someone who came to play. */
#dev { top: 10px; right: 10px; left: auto; text-align: right; display: none; }
#dev.on { display: block; }
#help { bottom: 10px; opacity: .75; }
/* SPRINT14 pool — the boot failure used to be written into #dev, which is
display:none off localhost. So on partly.party a failed boot showed a
stranger a blank blue page and put the only explanation on a console they
will never open. A fatal error is not dev chatter: it is the one message
that must reach the person who came to play, so it gets its own element
that is visible by default and reads like a sentence rather than a
readout. */
#fatal {
position: fixed; inset: 0; display: none; place-content: center;
padding: 32px; text-align: center; background: #10161b; color: #ffd9d6;
font: 15px/1.7 ui-monospace, SFMono-Regular, Menlo, monospace;
}
#fatal.on { display: grid; }
#fatal b { display: block; font-size: 13px; letter-spacing: .16em; color: #ff8f86; margin-bottom: 12px; }
#fatal span { display: block; font-size: 12px; opacity: .7; margin-top: 14px; }
#banner {
position: fixed; top: 38%; left: 0; right: 0;
text-align: center; pointer-events: none; user-select: none;
font: 700 42px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
letter-spacing: .18em; color: #fff; text-shadow: 0 2px 12px #0007;
opacity: 0; transition: opacity .45s ease;
}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="banner"></div>
<div id="dev">booting…</div>
<div id="fatal"></div>
<script type="importmap">
{ "imports": { "three": "./vendor/three.module.js",
"three/addons/": "./vendor/addons/" } }
</script>
<script type="module">
import { boot } from './js/main.js';
// boot() is async now — it fetches two storm defs, the ped, the clip pack
// and Lane E's debris GLBs. Surface a failure on the page rather than
// letting it die as an unhandled rejection behind a blue screen.
boot().catch((err) => {
console.error(err);
// Into #fatal, NOT #dev: #dev is display:none off localhost, so this
// message has been invisible to every stranger it was written for.
const box = document.getElementById('fatal');
box.innerHTML = '<div><b>HARD YARDS COULDN\'T START</b></div>';
box.firstChild.append(
Object.assign(document.createElement('div'), { textContent: err.message }),
Object.assign(document.createElement('span'), {
textContent: 'A reload usually fixes it. If it doesn\'t, the details are in the browser console.',
}),
);
box.classList.add('on');
});
</script>
</body>
</html>