Day 5 Quest
Zombie Fence: Two Neurons and a Boss
Quest: Why is one straight fence not enough, and what do two neurons add?
Today Craft Lab grows a Pasture.
Walk to the Shepherd Golem (new yellow-green field, south-west). Bring toys and a piece of string first. One string is one neuron. It can only be a straight fence.
Then play the quest. Level 1 is easy. Level 2 is the real lesson: sheep in the middle, zombies on both sides. One fence gets stuck. Two fences plus a boss neuron can finish the job.
If one string cannot wrap the sheep, can two strings do it?
Craft Lab World Β· Day 5 quest
CRAFT LAB WORLD
Arrows / WASD or on-screen pad. Walk near the Golem, then TALK (or E).
Unplugged (10 minutes, before the screen)
- Put toy sheep in a middle strip on the floor. Put "zombies" (any other toys) on both sides.
- Lay one piece of string as a straight fence. Can it keep every sheep safe and every zombie out?
- Try two strings. A sheep is safe only if it sits inside both fences.
That last rule is the boss neuron.
Young Scientist Track
- Walk to the Pasture. Talk to the Shepherd Golem.
- Level 1: tap to drop sheep and zombies (or load the easy field). Press TRAIN. Watch the green fence slide.
- Level 2: guess first. Will one fence work?
- Watch it get stuck. Press ADD A NEURON. A second fence (cyan) appears. The boss combines them.
- Level 3: build your own puzzle. Guess again. Train. Draw what you saw.
Score to read:
Sheep safe: 5/5, Zombies blocked: 4/4
Dad Track
A single logistic neuron on (x, y) is a linear separator:
z = w1Β·x + w2Β·y + b
p = sigmoid(z)
fence: w1Β·x + w2Β·y + b = 0
River Valley is not linearly separable (same shape as XOR along x). Best one-neuron accuracy is 6/9.
A 2-2-1 network:
h1 = sigmoid(w1Β·x + b1)
h2 = sigmoid(w2Β·x + b2)
out = sigmoid(voΒ·h + bo)
Each hidden unit is still one straight fence. The output neuron is the boss: it learns a combination such as "inside fence 1 AND inside fence 2."
Backprop for BCE + sigmoid: dL/dz_out = p β y. Chain rule through h*(1βh) updates the hidden fences.
Start from mulberry32 seed 43 (see day05_two_neurons.py). Learning rates: one-neuron 1.2, MLP 2.5. After 80 one-neuron steps the quest is stuck near 6/9. After 360 MLP steps it reaches 9/9.
Young Scientist's Question
Is the boss neuron like a teacher who looks at both fences?
Yes. It does not draw a third fence on the grass. It reads the two fence answers and decides.
Could the Young Scientist explain it back?
"One brain cell can only draw one straight fence. If zombies are on both sides, you need two fences and a boss who checks both."
Scientific method (tiny)
- QUESTION β Can one neuron solve River Valley?
- HYPOTHESIS β No. Two hidden neurons plus a boss can.
- EXPERIMENT β Train one neuron 80 steps, then a 2-2-1 for 360 steps. Seed 43.
- CONTROL β Same nine points. Same start seed.
- MEASUREMENT β Hits out of 9.
- RESULT β One neuron 6/9. MLP 9/9. See quest and Python.
- CONCLUSION β Linear vs not-linear. Hidden units add extra fences.
- NEXT QUESTION β What if we add more hidden units? (Day 6.)
Item crafted
Item crafted!
Twin Fence Totem
Two hidden fences plus a boss neuron. Proof that one straight fence cannot save a middle strip of sheep.
- Second Neuron I
- Boss Cell I
- Linear Sight II
Achievement Get!
Two Fences
The command block
day05_two_neurons.py"""
Day 5: one logistic neuron vs a 2-2-1 network on River Valley.
Same dataset and seed-43 start as the browser quest.
No framework. One neuron gets stuck. Two neurons plus a boss solve it.
Fence of a neuron: w1*x + w2*y + b = 0
"""
import math
# Sheep in the middle (1). Zombies on both sides (0).
RIVER = [
(0.10, 0.20, 0.0),
(0.15, 0.50, 0.0),
(0.10, 0.80, 0.0),
(0.45, 0.25, 1.0),
(0.50, 0.50, 1.0),
(0.48, 0.75, 1.0),
(0.85, 0.20, 0.0),
(0.90, 0.50, 0.0),
(0.85, 0.80, 0.0),
]
# mulberry32 seed 43. Same numbers the game uses.
ONE = {"w": [0.9996221815235913, -0.44719531247392297], "b": 0.058831702917814255}
MLP = {
"h": [
{"w": [0.9996221815235913, -0.44719531247392297], "b": 0.058831702917814255},
{"w": [-0.8817753526382148, -0.8732963190414011], "b": 0.48883361276239157},
],
"o": {"w": [-0.9128561620600522, -0.8759447997435927], "b": 0.7592634987086058},
}
def sigmoid(z):
z = max(-20.0, min(20.0, z))
return 1.0 / (1.0 + math.exp(-z))
def one_step(n, lr=1.2):
gw0 = gw1 = gb = 0.0
for x, y, lab in RIVER:
err = sigmoid(n["w"][0] * x + n["w"][1] * y + n["b"]) - lab
gw0 += err * x
gw1 += err * y
gb += err
m = float(len(RIVER))
return {
"w": [n["w"][0] - lr * gw0 / m, n["w"][1] - lr * gw1 / m],
"b": n["b"] - lr * gb / m,
}
def mlp_step(m, lr=2.5):
g = {
"h": [{"w": [0.0, 0.0], "b": 0.0}, {"w": [0.0, 0.0], "b": 0.0}],
"o": {"w": [0.0, 0.0], "b": 0.0},
}
for x, y, lab in RIVER:
z0 = m["h"][0]["w"][0] * x + m["h"][0]["w"][1] * y + m["h"][0]["b"]
z1 = m["h"][1]["w"][0] * x + m["h"][1]["w"][1] * y + m["h"][1]["b"]
h0, h1 = sigmoid(z0), sigmoid(z1)
out = sigmoid(m["o"]["w"][0] * h0 + m["o"]["w"][1] * h1 + m["o"]["b"])
d = out - lab
g["o"]["w"][0] += d * h0
g["o"]["w"][1] += d * h1
g["o"]["b"] += d
d0 = d * m["o"]["w"][0] * h0 * (1 - h0)
d1 = d * m["o"]["w"][1] * h1 * (1 - h1)
g["h"][0]["w"][0] += d0 * x
g["h"][0]["w"][1] += d0 * y
g["h"][0]["b"] += d0
g["h"][1]["w"][0] += d1 * x
g["h"][1]["w"][1] += d1 * y
g["h"][1]["b"] += d1
n = float(len(RIVER))
nxt = {"h": [{}, {}], "o": {}}
for i in (0, 1):
nxt["h"][i] = {
"w": [
m["h"][i]["w"][0] - lr * g["h"][i]["w"][0] / n,
m["h"][i]["w"][1] - lr * g["h"][i]["w"][1] / n,
],
"b": m["h"][i]["b"] - lr * g["h"][i]["b"] / n,
}
nxt["o"] = {
"w": [
m["o"]["w"][0] - lr * g["o"]["w"][0] / n,
m["o"]["w"][1] - lr * g["o"]["w"][1] / n,
],
"b": m["o"]["b"] - lr * g["o"]["b"] / n,
}
return nxt
def one_hits(n):
hits = 0
for x, y, lab in RIVER:
p = sigmoid(n["w"][0] * x + n["w"][1] * y + n["b"])
if (p >= 0.5) == (lab >= 0.5):
hits += 1
return hits
def mlp_hits(m):
hits = 0
for x, y, lab in RIVER:
h0 = sigmoid(m["h"][0]["w"][0] * x + m["h"][0]["w"][1] * y + m["h"][0]["b"])
h1 = sigmoid(m["h"][1]["w"][0] * x + m["h"][1]["w"][1] * y + m["h"][1]["b"])
out = sigmoid(m["o"]["w"][0] * h0 + m["o"]["w"][1] * h1 + m["o"]["b"])
if (out >= 0.5) == (lab >= 0.5):
hits += 1
return hits
n = {k: (v[:] if isinstance(v, list) else v) for k, v in ONE.items()}
n["w"] = ONE["w"][:]
for _ in range(80):
n = one_step(n)
print("one neuron after 80 steps:", one_hits(n), "/ 9 (stuck)")
m = {
"h": [
{"w": MLP["h"][0]["w"][:], "b": MLP["h"][0]["b"]},
{"w": MLP["h"][1]["w"][:], "b": MLP["h"][1]["b"]},
],
"o": {"w": MLP["o"]["w"][:], "b": MLP["o"]["b"]},
}
for _ in range(360):
m = mlp_step(m)
print("2-2-1 after 360 steps:", mlp_hits(m), "/ 9 (two fences + boss)")
python3 content/lab/days/day-005/day05_two_neurons.pyNo packages. Expect 6 / 9 then 9 / 9.
Dad's Lab Notes (tap to open)
- Wanted to learn
- See why one neuron is one straight fence, and how two hidden neurons plus a boss solve a not-linear pasture.
- Learned
- Linear separability. Hidden units are extra decision boundaries. 2-2-1 forward pass and backprop through one hidden layer. XOR-shaped River Valley.
- Built
- Pasture on the world map, Shepherd Golem, Zombie Fence quest with live fences, twoNeurons.ts, Twin Fence Totem, Twin Cell Antenna, EXP-001, day05_two_neurons.py.
- Confused me
- The boss does not draw a third line on the field. It lives in hidden-activation space.
- Failed
- One neuron on River Valley stays at 6/9. That failure is the lesson, not a bug.
- Taught the Young Scientist
- Young Scientist: one string is one fence. Two strings plus a boss rule can save a middle strip of sheep.
- Tomorrow
- Golem Brain Workshop: let the kid pick how many fences, then train a wider MLP.
Tomorrow
Day 6: Golem Brain Workshop. Build your own MLP with more fences.
Your personal notes
Loadingβ¦
