Twenty Browsers Beside the Village
Twenty controllers beside twenty-four people
The first speaker roll is 222617 bytes, and the contract compares every one of them. It holds twenty controllers, not twenty bodies, because the body is still the browser row: Bulk 40, Sight 12, Reach 1, a store of 100 grams at founding, and no new action a six-output controller can name.
A speaker is an ordinary browser whose controller came from a pinned arena, whose goal stands only for its span, and whose fallback is the same evolved animal when no goal stands. The model is absent on this page. The database is absent too. A bench presses the goal off a flag so the seam can be measured before any prompt exists.
One champion on the bare plate proves the painter; a village needs twenty roots. Chapter 60 measured one-seed lineages collapsing early, so this roll takes one champion from each of twenty arena seeds, 5 through 24, instead of the top twenty from one board. The body block is pinned after every generation and the mutation draw count still runs across it; what survives is a spread of controllers and temperaments on the same browser body.
$ go run ./cmd/speakers -mode roll -write configs/speakers.json
speakers: 20 pinned arenas, 10 generations each, tournaments of 4, 1 champion kept, first seed 5, off the flags
the roll configs/speakers.json, rebuilt here and compared by the contract
the body browser row pinned to 1.00 over all ten body factors
the driver beast.NewWits over each champion's 378 weights, wrapped by speaker.Paint
seed best board digest weights digest aggression wariness tameness
5 267.7500 fd9219ac238c82ab b67d97ce187d6963 0.2982 0.0353 0.8700
6 270.0000 92f8841713182102 8078a8e294a1b928 0.3405 0.1264 0.4637
7 282.5000 1202d947c4f3578f 1e7fe725a98a84a1 0.8573 0.3860 0.2253
8 275.2500 c1d2fa29c7237062 2cd078e80a9b23fa 0.3371 0.0663 0.5973
9 265.7500 cb4c72264b092577 a26d6dd664d31b7f 0.3542 0.5841 0.9268
10 253.7500 89ed48aad3b2ddbe be7f0ba846826ad2 0.4351 0.4512 0.1303
11 277.7500 6ec503beb7cdff9e 34d953e8134ef0a3 0.1495 0.5411 0.2667
12 268.0000 9c4f5517d8ffe5df 909e389cc7fb223f 0.4934 0.6852 0.4947
13 283.7500 4debc02eb53a45d3 90ba01d2ab0f2f9e 1.0000 0.3874 0.1780
14 277.0000 f36595bbaf85b59b 66fe39e641ef9758 0.1684 0.7455 0.0311
15 261.7500 c280ea75e0ef21e8 04c61c4302e2257c 0.1636 0.9790 0.6895
16 274.0000 08f5db86ee993c25 6bb57d779036df38 0.5959 0.2942 0.4864
17 277.0000 8915b0cf60acdeda b6dab1280a31f2ed 0.0938 0.1162 0.3613
18 274.7500 da8a1e069fdbdf6c 9260bf1538708511 0.4115 0.8753 0.3509
19 281.2500 a2329f176d4da8f8 97c18037cd253be4 0.4099 0.4771 0.0735
20 280.0000 15077b9b6bdc564d 3a9e9ac988a6dd12 0.3936 0.8162 0.5275
21 279.2500 5608b0deadc0a4e2 024b2b7ccd261483 0.2358 0.2429 0.2395
22 266.0000 23c7deb583804cdb fd2966765542f532 0.2620 0.0635 0.9341
23 278.0000 f45ebcc54bbad971 17d4ca4914cc4785 0.4287 0.9701 0.1119
24 263.2500 f23748db8570b07f c859a47a51fb978d 0.5352 0.8556 0.1044
seeds recorded 20
controllers recorded 20
bytes written 222617
roll sha256 fa9583936e568f48
configs/speakers.json: byte for byte the roll the bench wrote
Ten generations is the chapter's settled count. It is long enough for every champion in the table to outscore the opening browser reflex on the pinned plate, and short enough that the contract can rebuild all twenty controllers rather than treating the file as hand-written data. The best scores run from 253.7500 to 283.7500 grams over four trials. That range is the roll's floor, not a claim about one chosen animal.
The configs/speakers.json reader
The file is embedded beside the village roll because later benches read it by name. Generated
data is rebuilt by cmd/speakers, and a byte change is a red contract.
// configs/configs.go
//go:embed genesis.json models.json village.json importance.json thought.json reflect-questions.tmpl reflect-statement.tmpl plan.tmpl taboo.json line.tmpl village-24.json speakers.json
var Files embed.FS
// Speakers is the roll of twenty browser controllers chapter 104
// writes from the pinned-body arena. It is data, not an exemplar: a
// contract run makes it again and compares the bytes.
const Speakers = "speakers.json"
// internal/speaker/roll.go
type Controller struct {
Seed uint64 `json:"seed"`
Gen int `json:"gen"`
Score float64 `json:"score"`
Board string `json:"board"`
Weights string `json:"weights"`
Body []float64 `json:"body"`
Mind []float64 `json:"mind"`
Temper []float64 `json:"temper"`
Aggression float64 `json:"aggression"`
Wariness float64 `json:"wariness"`
Tameness float64 `json:"tameness"`
}
type Roll struct {
Seeds []uint64 `json:"seeds"`
Speakers []Controller `json:"speakers"`
}
// internal/speaker/roll.go, continued
func DecodeRoll(b []byte) (Roll, error) {
var r Roll
if err := json.Unmarshal(b, &r); err != nil {
return Roll{}, fmt.Errorf("speaker: speakers.json: %w", err)
}
if len(r.Speakers) != 20 {
return Roll{}, fmt.Errorf("speaker: speakers.json holds %d controllers, want 20", len(r.Speakers))
}
if len(r.Seeds) != len(r.Speakers) {
return Roll{}, fmt.Errorf("speaker: %d seeds for %d controllers", len(r.Seeds), len(r.Speakers))
}
seen := map[uint64]bool{}
for i, s := range r.Seeds {
if seen[s] {
return Roll{}, fmt.Errorf("speaker: seed %d appears twice", s)
}
seen[s] = true
c := r.Speakers[i]
if c.Seed != s {
return Roll{}, fmt.Errorf("speaker: row %d says seed %d under seed %d", i, c.Seed, s)
}
if len(c.Body) != gene.Bodies {
return Roll{}, fmt.Errorf("speaker: seed %d body has %d numbers", s, len(c.Body))
}
for j, v := range c.Body {
if v != 1 {
return Roll{}, fmt.Errorf("speaker: seed %d body factor %d is %.4f, not pinned", s, j, v)
}
}
if len(c.Mind) != gene.Weights() {
return Roll{}, fmt.Errorf("speaker: seed %d mind has %d weights", s, len(c.Mind))
}
if len(c.Temper) != gene.Tempers {
return Roll{}, fmt.Errorf("speaker: seed %d temper has %d numbers", s, len(c.Temper))
}
}
return r, nil
}
The reader checks the part a later run cannot repair. It refuses the wrong count, a repeated root, a seed mismatch, a body factor other than 1.00, a mind whose 378 weights are not present, and a temperament row of the wrong width. The file also carries the board and weight digests; those name the controller, while the checks keep the body from sliding away from the browser.
$ go test ./internal/speaker/ -run 'TestTheSpeakerRollHasTwentyPinnedControllers' -v
=== RUN TestTheSpeakerRollHasTwentyPinnedControllers
roll_test.go:29: 20 controllers read; 7560 mind weights and 200 body factors pinned to 1.00
--- PASS: TestTheSpeakerRollHasTwentyPinnedControllers (0.00s)
PASS
ok theworld/internal/speaker 0.00s
The test reads the embedded file and counts the two numbers that matter before a creature is founded from it: 7560 weights, which is twenty times 378, and 200 body factors, which is twenty times ten. It is a file test rather than a generation test. The generation test is the contract's byte comparison.
Creature 25 through creature 44
Founding does not ask the arena again. It reads the roll, stamps each controller through the browser row, wraps the wits with the painter, and scatters those twenty bodies on stream 12, the same founding-creatures stream earlier volumes used for browsers. The twenty-four people already own identities 1 through 24, so the first browser in the roll becomes creature 25 and the last becomes 44.
// cmd/speakers/main.go
func foundSpeakers(v *terra.Valley, r *beast.Roster, sr speaker.Roll, set setup) []*beast.Beast {
rng := rand.New(rand.NewPCG(set.first, streamHerd))
ground := beast.Ground(v)
taken := map[sim.Coord]bool{}
var out []*beast.Beast
for _, row := range sr.Speakers {
c := ground[rng.IntN(len(ground))]
for taken[c] {
c = ground[rng.IntN(len(ground))]
}
taken[c] = true
g := row.Genome()
b := g.Stamp(beast.Fauna[0]).Spawn(beast.Centre(c), v.Now)
b.Mind = beast.NewWits(g.Net(), b)
p := speaker.Wrap(b)
if set.goal != (sim.Coord{}) {
p.Goal = speaker.Goal{At: set.goal, Sign: 1, Gain: 1}
}
r.Add(b)
out = append(out, b)
}
r.Seen = func(view *beast.View) { speaker.Watch(r.Live, view) }
return out
}
The scatter has one new file and no new stream. The seed is the roll's first seed, printed off
the flag, and the stream number is 12 because this is still founding creatures. The roster's
Seen hook is the one chapter 103 needed: a wits behind a painter still gets the
view before it reads its row.
$ go run ./cmd/speakers -mode found
speakers: twenty browsers founded beside the village, off configs/speakers.json
the ground 24x16 cells, tick 4450, 119 plants standing after 4000 settle ticks
the village 24 people, off configs/village-24.json
the speakers 20 browsers, scattered on stream 12 in roll order
the next id 45; no new stream is claimed
row seed creature cell carrying weights digest tameness
0 5 25 15,5 100.0000 b67d97ce187d6963 0.8700
1 6 26 3,11 100.0000 8078a8e294a1b928 0.4637
2 7 27 13,6 100.0000 1e7fe725a98a84a1 0.2253
3 8 28 3,10 100.0000 2cd078e80a9b23fa 0.5973
4 9 29 1,12 100.0000 a26d6dd664d31b7f 0.9268
5 10 30 7,13 100.0000 be7f0ba846826ad2 0.1303
17 22 42 5,3 100.0000 fd2966765542f532 0.9341
18 23 43 13,3 100.0000 17d4ca4914cc4785 0.1119
19 24 44 12,10 100.0000 c859a47a51fb978d 0.1044
... 11 rows between omitted from the listing
The listing shows nine rows because the page needs the identity boundary, not every weight. The
omitted rows are in configs/speakers.json and in the contract transcript. The useful
number is the next identity, 45: twenty-four people and twenty speakers are one roster, and a
transfer search sees bodies, not classes.
1600 speaker-ticks with a goal standing
A goal in this chapter is still not a model answer. The bench presses cell 16,8 through the same values the seam later writes, with gain 1.0000 and a span of eighty ticks. While the span stands, the painter paints. At the boundary after tick 80 the bench clears the goal, and from then on each browser is the evolved animal the roll founded.
$ go run ./cmd/speakers -mode lapse -span 80 -ticks 200
speakers: a goal pressed through the seam off a flag, then lapsed
the goal cell 16,8, toward, gain 1.0000, span 80 ticks, off the flags
the lapse at tick 4530, then the browser is its evolved animal again
no model nothing here asks anything; the goal is pressed by this bench
no database nothing here opens anything
ticks run 200
speakers 20
speaker-ticks with a goal standing 1600
speaker-ticks with a ray painted 1211
living speakers at the close 20
first speaker closed on 22,5
first speaker store 93.2137
Twenty speakers times eighty ticks gives 1600 speaker-ticks with a goal standing. Only 1211 of those paint a ray, because the goal cell is past Sight for some bodies on some ticks. The other 389 leave the row as the world read it. That is the same limit chapter 103 printed on a bare plate: a goal about what the eyes cannot reach is not painted.
The run does not save a goal anywhere. If the process dies while one stands, the value is lost with the process and the speaker is an animal on restart. That is a limit of this chapter, and it is kept because the fallback is the design, not an error path.
Twenty more mouths leave twenty-two bodies living
The console ground fed twenty-four people badly in chapter 102, and the roll adds twenty more mouths to that same ground. The page does not tune the ground to hide the cost. It runs an hour of the world's time with the people alone and then with the speakers founded too.
$ go run ./cmd/speakers -mode mouths
speakers: what twenty more mouths do to the console ground
run people only people and speakers
people founded 24 24
speakers founded 0 20
bodies dead after 36000 ticks 15 22
living bodies 9 22
plants standing 277 286
standing plant grams 12178.7 12135.5
With people alone, fifteen of twenty-four bodies die and nine live. With the speakers added, twenty-two of forty-four bodies die and twenty-two live. The ground has slightly more plants standing and slightly fewer grams in them, because dead bodies stop eating and the plants keep growing. The added mouths are real, but the hour is not a clean hunger proof: the population also changes who dies soon enough to leave food behind. The negative result stays on the page.
The tick measurement is the other cost, and the only one here that belongs to the machine. The contract flattens the duration fields, but the page records the run that produced the figures.
$ go run ./cmd/speakers -mode pace -ticks 200
twenty-four people, no speakers 200 ticks in 113ms, 1770 ticks a second (measured here; yours will differ); living 22
twenty speakers, no goal standing 200 ticks in 150ms, 1332 ticks a second (measured here; yours will differ); painted 0 of 4000 speaker-ticks
twenty speakers, goals standing 200 ticks in 134ms, 1497 ticks a second (measured here; yours will differ); painted 3131 of 4000 speaker-ticks
The rate moves with the host and the contract says so, but the counts beside it are the engine's: 4000 speaker-ticks in each speaker run, no painted ray when no goal stands, and 3131 painted rays when every speaker opens with the goal. The painter adds arithmetic inside the tick; it does not add a transaction, a goroutine or a model call.
The first broken roll is small: one body factor in the first row is changed from 1.00 to 1.25. That is enough to make the file describe a different animal. The reader refuses it before any body is founded.
$ go run ./cmd/speakers -mode broken
broken roll refused: speaker: seed 5 body factor 0 is 1.2500, not pinned
exit=1
The symptom is the refusal, and the cause is the first body factor. A roll reader that accepted it would let the body drift while every paragraph still called the row a browser. The fix is not to clamp the value at founding, because that would turn a bad file into a different file silently. The fix is the reader's check: every body factor in the shipped roll must already be 1.00, and a file that says otherwise stops.
What the speaker roll can now do
- Say why the roll takes one champion from each of twenty seeds instead of twenty champions from one seed, and point to the number in the run that proves there are twenty roots.
- Given one row of
configs/speakers.json, name the fields that identify the controller and the fields that prove the body stayed pinned. - Explain why creature 25 is the first speaker founded beside the village, and what the next identity 45 says about the people and speakers sharing one roster.
- Work out why eighty ticks over twenty speakers gives 1600 speaker-ticks with a goal standing, then explain why only 1211 painted a ray.
- State the limit this chapter keeps about a goal in memory when the process dies.
Exercise 1 — five roots. Run go run ./cmd/speakers -mode roll -count 5
and compare the byte count and digest with the page's twenty-root roll.
The command prints five controllers and does not write the shipped file unless you pass
-write. The byte count and roll sha256 differ because the file has a different
number of rows; the body check still expects twenty when the embedded reader opens the
shipped file.
Exercise 2 — a longer span. Run go run ./cmd/speakers -mode lapse -span 120 -ticks 200
and predict the speaker-ticks with a goal standing before reading the output.
Twenty speakers times 120 ticks gives 2400 speaker-ticks with a goal standing. The painted count is lower when bodies spend part of that span with the goal past Sight.
Exercise 3 — the broken file. Change a copy of the roll so one mind has 377 weights and write down which check should refuse it.
DecodeRoll refuses the row with mind has 377 weights. The browser body
can be right and the controller still be unusable, because beast.NewWits reads
exactly 378 weights.