Every rule so far had one knob. Real rules have many: a house price responds to floor area and distance to the station; a pixel’s class responds to every other pixel. This chapter grows the guess from one number to three — and the lesson is how little that changes. The loop of chapter 3 survives untouched; what grows is the shape of the storage, and Mica keeps that shape in the type, where the compiler can check it before anything runs.

The working file is examples/ManyKnobs — run it with make -C examples/ManyKnobs run.

A rule with three knobs

The observations now come from y = 1.5·x₁ + 0.25·x₂ + 2: two influences with a weight each, plus a bias — the base level the rule answers when every influence is zero. The guess is a weight vector and a bias scalar:

w := (0.0, 0.0);     { the literal fills the tracked vector's value half }
b := 0.0;

and one verb pairs every weight with its influence in a single stroke — the dot product:

                  x = (x₁, x₂)          the data: one observation
                  w = (w₁, w₂)          the guess: one weight per influence

   Dot(w, x) + b  =  w₁·x₁ + w₂·x₂ + b  the prediction, one number again

Dot is the workhorse of everything that follows — a layer of a neural network is dot products in bulk, and attention is dot products asking questions. Its shape law is Mica’s own: the vector lengths live in the types, so a dot product over mismatched lengths is refused at build, not discovered mid-training. The vectors and matrices chapter owns that story in full.

The window grows, the loop does not

Four observations, four recorded misses, one loss — chapter 3’s window with Dot inside it:

tape
    e1 := Dot(w, x1) + b - 4.0;
    e2 := Dot(w, x2) + b - 5.0;
    e3 := Dot(w, x3) + b - 3.0;
    e4 := Dot(w, x4) + b - 5.5;
    loss := e1 * e1 + e2 * e2 + e3 * e3 + e4 * e4;
end;

Backward(loss);

w := w - Rate * Gradient(w);
b := b - Rate * Gradient(b);

Gradient(w) now answers a whole vector — one blame per weight, from the same single backward walk; the update subtracts it in one line of vector arithmetic. Notice what fan-out did quietly: w appears in all four recorded predictions, so each weight’s gradient already sums its blame across the whole batch. Nothing needed to be said.

The run

the descent
  round 1: loss 80.250000
  round 2: loss 47.894531
  round 10: loss 2.754853
  round 100: loss 0.041051
  round 2000: loss 0.000000

the learned rule: y = 1.500000·x1 + 0.250000·x2 + 1.999999   (made by 1.5, 0.25, 2)

The machine recovers the rule that made its observations — two weights exact to six places, the bias one step of rounding away. It is worth pausing on the rounds column: three knobs took two thousand rounds where one knob took six. Knobs interact — stepping one changes what the others should be — and the plain gradient step handles that interaction slowly. The optimization chapter returns to exactly this, and makes it fast.

What to take forward

The learning loop is now complete and final: a tracked guess of whatever shape, a recorded loss, Backward, a step. From here to the GPT, that loop never changes again — every remaining chapter only makes the forward story richer. The next enrichment is forced by an honest wall: some patterns no weighted sum can express, no matter how its knobs are set. The machine needs a bend — the next chapter’s subject.