The generator's working state (graph, attachment pool, rng) moves into
a holmeKimBuilder struct so each algorithm step is a small named
method: attachNewNode, link, pickMutualFriend, degreeProportionalSample.
Max nesting drops from five levels to two. The RNG call order is
untouched, and the golden test (99/70/7 reached) plus the fixed-seed
determinism test prove behaviour is bit-for-bit identical.
Go bits: pointer receivers ((b *holmeKimBuilder)) let methods mutate
the builder; (value, ok) multiple returns are the idiom for 'may not
exist', as in pickMutualFriend.
Config is the single source of truth for parameters (TS types will be
generated from these structs in milestone 2); all randomness flows from
its three seeds, so identical configs give identical results. Golden
test pins the default world: none=99/120 (82%), random=70/120 (58%),
most-connected=7/120 (6%). Same story as the prototype's 85/64/8 with
different dice; the ordering and the collapse are asserted explicitly,
exact Python numbers are out of scope by design.
'go run ./cmd/spreadlab' prints the three-scenario comparison.
This completes milestone 1 (engine ported, parameterised, tested).
Two ways to spend the same education budget: a uniform random sample
(spray and pray) vs the highest-degree hubs, ties broken stably towards
lower node numbers so the pick is deterministic. The origin is never
educated; they post the fake. rng.Shuffle does a seeded Fisher-Yates,
so the random pick is reproducible too. min() is a builtin since
Go 1.21.
One random threshold per DIRECTED edge, drawn once per world: scenarios
sharing thresholds differ only in who is educated, the prototype's
'same world, different lever' trick. The cascade is plain BFS in rounds;
an educated student receives the fake but never forwards it, which is
the entire effect of the lever. The result stores the first-reached
round per node (exactly what a frontend animation needs).
Tests hand-craft a 4-node line graph with exact thresholds, so every
expectation is exact: spread, directional blocking, educated cutoff.
Preferential attachment via an attachment pool that holds one entry per
edge endpoint, so uniform draws are degree-proportional: that is the
whole hub-forming mechanism. A triangle step closes friend-of-a-friend
links with probability triangleProb, giving friend-group clustering.
Semantics ported from networkx, NOT its RNG stream (per the handoff,
no cross-language number matching).
Tests are property-based: size, edge bounds, connectivity, hub
formation across seeds, plus exact determinism for a fixed seed.
'go test ./...', gofmt and golangci-lint all clean.
Adjacency lives in slices, not maps, on purpose: Go randomises map
iteration order between runs, and the engine's determinism guarantee
needs every graph walk to visit neighbours in the same order. AddEdge
mirrors networkx semantics (duplicates and self loops are no-ops) so
the Holme-Kim port can lean on the same behaviour.
Receiver names stay short per Go convention (g *Graph); everything
else uses descriptive names.
math/rand/v2 (Go 1.22+) replaces the old math/rand: PCG generator,
no global Seed(), and rand.New(rand.NewPCG(seed, 0)) gives an isolated
deterministic stream. Each randomness consumer (graph, thresholds,
education sampling) will get its own stream so levers vary independently.
Tests live next to the code as *_test.go; 'go test ./...' runs them all.
'for i := range 100' is Go 1.22 range-over-int.
go.mod declares the module path (github.com/JustinZeus/spreadlab); every
import inside the repo is spelled relative to it. Layout follows the
standard Go shape: cmd/<binary>/main.go per executable, internal/ for
packages other modules may not import (the compiler enforces this).
No engine code yet, just a placeholder main that proves 'go build' works.