From a1852076fe95cf626c1b3e0e1fa505c5b2cdd7f7 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 28 Sep 2026 18:53:47 +0200 Subject: [PATCH] Start a named draw group from an empty hold, and state the decision's goal and cost exactly --- docs/decisions.md | 13 ++++++------- holdset.go | 2 +- todo.md | 3 --- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/docs/decisions.md b/docs/decisions.md index 8358cc6..56d0024 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -244,13 +244,12 @@ carry both rules and both scopes anyway, and tell them apart at every step. ## A hold set makes its draw maps on the first read -Every render starts from the zero `holdSet`, per goal 2's one spelling. Making the maps -where the set is declared kept a record's on its frame's stack, but a `Fake`'s set lives -on the `Generator`, so there it cost every render two heap allocations and about half the -cheapest one's time, measured 2026-09-28. Lazily, a render reading a reference path pays -those two allocations, a record or struct included, and one reading none pays nothing; -goal 11 holds either way. The allocation gate over a repeat of a reference path and over -a named draw group prices that, and pins what keeps a hold set off the heap. +Every render and every draw group starts from an empty hold, since goal 2 prices a reader +meeting two spellings of one empty set. Making the maps where the set is declared kept a +record's on its frame's stack, but a `Fake`'s set lives on the `Generator`, so there it +cost the cheapest render two heap allocations and 240→390 ns, measured 2026-09-28. +Lazily, a render reading a reference path pays those two allocations, a record or struct +included, and one reading none pays nothing; goal 11 holds either way. ## A category never references itself, and a record's fences run at load diff --git a/holdset.go b/holdset.go index 83dbab0..eeba3cb 100644 --- a/holdset.go +++ b/holdset.go @@ -63,7 +63,7 @@ func (sc renderScope) hold() *hold { if sc.set.named == nil { sc.set.named = map[string]*hold{} } - d = &hold{variant: map[string]node{}, value: map[string]readValue{}} + d = &hold{} sc.set.named[strings.Clone(sc.group)] = d } return d diff --git a/todo.md b/todo.md index 615586a..296a607 100644 --- a/todo.md +++ b/todo.md @@ -300,9 +300,6 @@ because pairing a street with its exact postnummer rewrites shipped rows. flag, an exit code, an exported name, a fence, a builtin and the lowest Go. - Measure the allocation gate over the shipped `geo` trees too: `pinSet.inline [8]` is sized for them, and `perf_test.go` checks a synthetic five-deep tree only. -- Make a named draw group's hold lazy, `&hold{}` in `renderScope.hold`: it escapes - to the heap anyway, so its two eager maps buy no stack and cost two allocations per - group that may never read through them. Check it against the alloc tests. - Name goal 2's four criteria with the axes the panel scores, so a recorded score maps back to the clause it came from: the goal spells them out in prose while `todo.md` records `Navigation`, `Locality`, `Shape` and `Self-sufficiency`.