Skip to content

Commit d292c81

Browse files
committed
Merge branch 'worktree-agent-a0efef9a0936175d7'
# Conflicts: # src/components/designs/genesis/scene.ts # src/components/designs/vale/art.ts
2 parents d55f13e + 3350dc3 commit d292c81

7 files changed

Lines changed: 623 additions & 8 deletions

File tree

scripts/genesis-timeline-stats.mjs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ function serialize(snap) {
312312
return JSON.stringify({
313313
t: snap.t,
314314
trees: m(snap.trees),
315+
// living details: fell times. Listed explicitly, because (j) compares
316+
// serialisations and anything left out of here is silently not checked.
317+
felled: m(snap.felled ?? new Map()),
315318
buildings: m(snap.buildings),
316319
roads: m(snap.roads),
317320
bridges: m(snap.bridges),
@@ -593,6 +596,26 @@ function run(label, map, pace = 1) {
593596
for (let t = 0.5; t <= 24.0001; t += 0.5) advance(hop3, tl, Math.min(t, 24));
594597
if (serialize(hop3) !== serialize(end)) bad(`(j) 48-hop advance != snapshotAt(24)`);
595598

599+
/* (j2) living details: fell times agree with the chop-done events, and with
600+
* `trees` — the felled log's whole clock hangs off this map, so a stump with
601+
* no fell time (or a fell time with no stump) is a log that never lies down
602+
* or never gets hauled. Checked at mid-day too, where `felled` is partial. */
603+
for (const [id, t] of end.felled ?? []) {
604+
if (chopDone.get(id) !== t) bad(`(j2) felled[${id}] = ${t}, chop-done at ${chopDone.get(id)}`);
605+
if (end.trees.get(id) !== 'stump') bad(`(j2) felled[${id}] but tree is ${end.trees.get(id)}`);
606+
}
607+
for (const [id, st] of end.trees) {
608+
if (st === 'stump' && !(end.felled ?? new Map()).has(id))
609+
bad(`(j2) tree ${id} is a stump at t=24 with no fell time`);
610+
}
611+
{
612+
const mid = snapshotAt(map, tl, 12);
613+
for (const [id, t] of mid.felled ?? []) {
614+
if (!(t <= 12 + 1e-9)) bad(`(j2) felled[${id}] = ${t} present in the t=12 snapshot`);
615+
if (mid.trees.get(id) !== 'stump') bad(`(j2) t=12: felled[${id}] but tree is not a stump`);
616+
}
617+
}
618+
596619
/* (l) road felling: nothing is left standing on finished road surface */
597620
for (const r of map.roads) {
598621
const list = roadEv.get(r.id) ?? [];

src/components/designs/genesis/Catalog.tsx

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -742,14 +742,19 @@ const COVERAGE: { head: string; body: string }[] = [
742742
'seasonCanvas runs over the six DECIDUOUS pools and the ground tint. crop, haystack, reeds and flowers are summer art in every month, so a winter valley still has a green cornfield in it.',
743743
},
744744
{
745-
head: 'Trees have two states and a nudge',
745+
head: 'A tree still falls in one nudge',
746746
body:
747-
'standing / stump, with felling drawn as the standing sprite shoved ±2px. No half-fallen trunk, no felled log on the ground, no sapling.',
747+
'The ladder now runs standing → felling → stump → stump with its log → stump, and three to five saplings come up on cleared ground in the evening. The fall itself is still the standing sprite shoved ±2px: there is no half-fallen trunk, no lean, and nothing between “upright” and “gone”.',
748748
},
749749
{
750-
head: 'Props are one sprite each',
750+
head: 'Crops ripen; nothing else does',
751751
body:
752-
'crop never ripens, haystack never shrinks, crates never stack higher as a town grows. Progress is expressed by buildings alone.',
752+
'crop is three sprites now and moves through them on the clock. haystack, crates, barrels and cart are still one sprite for the whole day — a haystack never grows as it is built up, and a store’s crates never stack higher as the town around them does. Everything except the fields still expresses progress through buildings alone.',
753+
},
754+
{
755+
head: 'The log is hauled by nobody',
756+
body:
757+
'A felled trunk lies for 90 world-minutes and then stops existing. No carrier walks out to it, no cart takes it, and the lumber pile in the yard does not grow by one when it goes — the two halves of the valley’s timber economy are drawn, and never joined up.',
753758
},
754759
];
755760

@@ -919,6 +924,40 @@ export default function Catalog() {
919924
const props = useMemo(() => poolItems(PROP_POOLS, 'pp'), [pools]);
920925
const treasure = useMemo(() => poolItems(TREASURE_POOLS, 'tz'), [pools]);
921926

927+
/* ---- living details (additive) ---------------------------------------- */
928+
929+
/** One field at three ages. Pool index is shared, so a plot keeps its
930+
* colour and its earth as it ripens; two of the four seeds shown. */
931+
const cropStages = useMemo(() => {
932+
const out: Item[] = [];
933+
const stages: [string, string][] = [
934+
['crop-sprout', 'sprout · before 10:00'],
935+
['crop', 'rows · 10:00–16:00'],
936+
['crop-tall', 'tall · after 16:00'],
937+
];
938+
for (let v = 0; v < 2; v++) {
939+
for (const [kind, sub] of stages) {
940+
const p = pools[kind] ?? [];
941+
if (p[v]) out.push(item(`cs-${v}-${kind}`, kind, spriteCanvas(p[v]), { sub }));
942+
}
943+
}
944+
return out;
945+
}, [pools]);
946+
947+
/** What a felling leaves on the ground, and what comes up after it. */
948+
const treeAfter = useMemo(() => {
949+
const out: Item[] = [];
950+
(pools.log ?? []).forEach((sp, i) =>
951+
out.push(item(`lg-${i}`, 'log', spriteCanvas(sp), { sub: TREE_KINDS[i] ?? `kind ${i}` }))
952+
);
953+
(pools.sapling ?? []).forEach((sp, i) =>
954+
out.push(item(`sa-${i}`, 'sapling', spriteCanvas(sp), { sub: `seed ${i + 1}` }))
955+
);
956+
return out;
957+
}, [pools]);
958+
959+
/* ---- end living details (additive) ------------------------------------ */
960+
922961
const extras = useMemo(() => {
923962
const out: Item[] = [];
924963
out.push(item('ex-nameboard', 'nameboard', spriteCanvas(buildNameBoard(ACCENT))));
@@ -1286,8 +1325,8 @@ export default function Catalog() {
12861325

12871326
const counts = {
12881327
structures: structures.length + materials.length + stages.length + roofs.length,
1289-
trees: trees.length + treeStates.length,
1290-
props: props.length + treasure.length + extras.length,
1328+
trees: trees.length + treeStates.length + treeAfter.length,
1329+
props: props.length + treasure.length + extras.length + cropStages.length,
12911330
inhabitants: anims.length,
12921331
wildlife: wildlife.length,
12931332
infra: bridges.length + roads.length + grounds.length,
@@ -1349,23 +1388,37 @@ export default function Catalog() {
13491388
id="trees"
13501389
title="Trees"
13511390
count={counts.trees}
1352-
blurb="buildTree(kind, seed) — seven kinds, four seeds each, plus the two states a tree can be caught in."
1391+
blurb="buildTree(kind, seed) — seven kinds, four seeds each, the states a tree can be caught in, and what its felling leaves behind."
13531392
>
13541393
<Row title={`Kinds (${TREE_KINDS.length})`} items={trees} />
13551394
<Row
13561395
title="States"
13571396
note="A tree under the axe is the standing sprite offset ±2px; once felled it is replaced by a stump."
13581397
items={treeStates}
13591398
/>
1399+
{/* ---- living details (additive) ---- */}
1400+
<Row
1401+
title={`After the axe (${treeAfter.length})`}
1402+
note="The trunk lies beside its stump for 90 world-minutes and is then hauled off to the yards — one log per tree kind, because the bark is all that tells them apart at this size. Three to five saplings come up on the day's cleared ground after 18:00, each beside a stump the day actually made."
1403+
items={treeAfter}
1404+
/>
1405+
{/* ---- end living details (additive) ---- */}
13601406
</Section>
13611407

13621408
<Section
13631409
id="props"
13641410
title="Props & scatter"
13651411
count={counts.props}
1366-
blurb="Every pooled kind from makePools(), all pool variants, plus the four built on demand by propSprite(). “unused” means gen.ts never emits it."
1412+
blurb="Every pooled kind from makePools(), all pool variants, plus the four built on demand by propSprite(). “unused” means gen.ts never emits it. The log and sapling pools are shelved with the trees, whose felling is the only thing that asks for them."
13671413
>
13681414
<Row title={`Pools (${PROP_POOLS.length} kinds)`} items={props} />
1415+
{/* ---- living details (additive) ---- */}
1416+
<Row
1417+
title="Crop stages (3)"
1418+
note="The one prop that is not a single sprite. A plot is sprouts before ~10:00, the rows that have always been here until ~16:00, and tall gold after — the stage is a pure function of the clock and a ±40-minute jitter off the plot's own seed, so a valley's fields do not all turn on the same stroke. All three stages share a pool index, so a field keeps its colour and its earth as it ripens."
1419+
items={cropStages}
1420+
/>
1421+
{/* ---- end living details (additive) ---- */}
13691422
<Row
13701423
title={`Treasure (${TREASURE_POOLS.length} states)`}
13711424
note="Pooled, but never a PropSpec: chests are their own list on the map. The timeline walks a found chest along all three — mound, prised open, emptied."

src/components/designs/genesis/fixture.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,9 @@ export function fixtureEmptySnapshot(map: GenesisMap): WorldSnapshot {
610610
return {
611611
t: 0,
612612
trees: new Map(),
613+
/* ---- living details (additive) ---- */
614+
felled: new Map(),
615+
/* ---- end living details (additive) ---- */
613616
buildings,
614617
roads: new Map(),
615618
bridges: new Map(),
@@ -657,6 +660,9 @@ function applyEvent(snap: WorldSnapshot, ev: GenesisEvent): void {
657660
break;
658661
case 'chop-done':
659662
snap.trees.set(ev.treeId, 'stump');
663+
/* ---- living details (additive) ---- */
664+
snap.felled.set(ev.treeId, ev.t);
665+
/* ---- end living details (additive) ---- */
660666
break;
661667
case 'survey':
662668
snap.buildings.set(ev.buildingId, { status: 'surveyed', progress: 0.05 });

0 commit comments

Comments
 (0)