The World Vol 5 · Bodies and Brains
ch 47 / 105
Chapter 47

Nine Lines of Sight

The ray result

Every velocity a creature has had so far was typed into a bench. Take the bench away and a forty-gram body can pay for a crossing, but it has no evidence for choosing one.

A ray hands back two numbers and nothing else: how far it went, and a code for what stopped it. It never returns the plant, a pointer to the plant, or any value with a method on it.

The price has to stay fixed as the valley fills. Asking every plant where it stands gets slower every year the valley grows; nine fixed directions cost the same in a bare valley and in a crowded one.

Sight is a line walked one step at a time. The ray leaves the eye at a fixed angle off the heading, steps through cells, and stops at the first thing solid enough to matter or at the sight cap. Nine lines over a hundred and twenty degrees make the fan.

The restriction is what makes the sense useful later. The controller reads numbers by multiplying them by weights and adding them. A sense designed against that limit from its first line still works when the network replaces the hand-written rule.

One ray on paper

The bench founds a valley the way the terrarium's own two-century run does, out of world seed 5, three seedlings of every row, and stops it at the midsummer of its first year: twelve plants standing, 1,517.5 grams between them, a pond through the middle and a rim of rock all round. Nothing in this chapter changes a gram of it. Every run below is a reading taken of ground that is holding still.

Stand a creature on cell 8,2, facing due east, and walk one of its nine rays with a calculator before writing a line of Go. Ray 1 is a good one to pick, because the arithmetic along it is the tidiest of the nine: at forty-five degrees a ray moves exactly as far across as it does up.

∑ Math Interlude — where a sample lands

The fan opens a hundred and twenty degrees and holds nine rays, so there are eight gaps between them and each gap is 120 ÷ 8 = 15 degrees. Number the rays 0 to 8. Ray 4 is the middle one and runs straight along the heading, so ray i leaves at the heading plus (i − 4) × 15 degrees. For ray 1 that is 0 + (1 − 4) × 15 = −45 degrees. Rows count downward on this grid, so a minus angle points up the page: ray 1 is the creature's second ray from the left, aimed north-east.

An angle becomes a direction through the two functions that turn a corner of the unit circle into a pair of lengths. One cell along a ray at −45 degrees is cos(−45) = 0.707107 of a cell east and sin(−45) = −0.707107 of a cell south, and a minus south is a north.

The eye sits in the middle of the cell the creature is on. Cell 8,2 runs from 8 to 9 across and 2 to 3 down, so its middle is 8.5 and 2.5. Samples are taken every quarter of a cell out along the ray, so sample n sits at distance n × 0.25, and its position is the eye plus that distance times the direction:

x = 8.5 + d × 0.707107
y = 2.5 + d × (−0.707107)

At d = 0.25 that is x = 8.676777 and y = 2.323223. Throw away the fraction of each and what is left is the cell: 8,2. That is the cell the creature is standing on, so the sample is dropped and the walk carries on. At d = 0.50 it is 8.853553 and 2.146447, still 8,2, dropped again. At d = 0.75 it is 9.030330 and 1.969670, which is cell 9,1, and 9,1 is bare soil with nothing on it. Five more samples land in the same cell. Then at d = 2.25 the position is 10.090990 and 0.909010, cell 10,0, and row 0 is the rim of rock the valley is cut out of. The walk stops there. Nine samples, and the answer is two numbers: 2.25 cells, rock.

hthe heading, in degrees clockwise of east; 0 for a creature facing east
iwhich ray of the fan, 0 for the leftmost through 8 for the rightmost
gthe gap between one ray and the next: 15 degrees, from 120 over eight gaps
ah + (i − 4) × g, the angle ray i leaves at; −45 degrees for ray 1
cos a, sin ahow far east and how far south one cell along that ray goes; 0.707107 and −0.707107
ex, eywhere the eye is, in cells; 8.5 and 2.5 for a creature on cell 8,2
sthe stride, how far apart two samples sit along a ray; 0.25 of a cell
dhow far out a sample sits: s, then 2s, then 3s, up to the sight limit
x, yex + d × cos a and ey + d × sin a: the sample's position, in cells

Only two of those numbers are choices. The stride is one of them, and a quarter of a cell is short for a reason: a ray at 45 degrees moves 0.707107 across and 0.707107 down for every cell it travels, so a walk that took whole cells at a time would jump the corner between two cells and never look at either of the two it passed through. Anything shorter than half a cell catches them, and a quarter leaves room to spare. The sight limit is the other, and it is not a choice this file makes: it is Sight off the creature's own row, twelve cells for a browser, so a kind with better eyes gets them by changing a number in a table.

▣ Build · stage 1 — what a ray is, and how wide the fan opens
// internal/beast/look.go

// Hit is what one ray met.
type Hit uint8

// The five things a ray can come back with, in the order the codes are
// written down.
const (
	Nothing Hit = iota
	Plant
	Creature
	Water
	Rock
)

// Ray is one line of sight after it has been walked: how far it went in
// cells, and what stopped it.
type Ray struct {
	Dist float64
	What Hit
}

// Stride is how far along a ray one sample sits, in cells. It is short
// enough that no cell the ray passes through is stepped over.
const Stride = 0.25

// Fan is how many rays one creature looks along, Arc is how wide the
// whole fan opens in radians, and Gap is the angle between one ray and
// the next. Nine rays over a hundred and twenty degrees leaves eight
// gaps of fifteen.
const (
	Fan = 9
	Arc = 120 * math.Pi / 180
	Gap = Arc / (Fan - 1)
)

Hit is five constants and the order they are written in is a fact other files will index by, so it is fixed here once and never re-sorted for tidiness. Ray is the whole of what a walked line reports. Arc is the only angle typed anywhere and Gap is divided out of it, so a fan of eleven rays over the same arc is one edited digit and no arithmetic anywhere else. And the fan has an odd number of rays on purpose: an even one has no middle, and a creature whose heading is not one of the directions it can see is a creature that cannot look where it is going.

$ go run ./cmd/eyes -mode step -at 8,2 -eye 1
eyes: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams

  4 creatures founded on stream 12, at 5,2 9,5 10,2 0,5
  the one looking is stood on 8,2 by hand, eye at 8.5000,2.5000 in cells
  facing 0 degrees clockwise of east, sight 12 cells, stride 0.25 of a cell

  ray 1 leaves at 0 + (1 - 4) x 15 = -45.0 degrees, so one cell along it
  is 0.707107 east and -0.707107 south

      n        d           x           y     cell   what the cell answers
      1     0.25    8.676777    2.323223      8,2   the cell it is standing on, skipped
      2     0.50    8.853553    2.146447      8,2   the cell it is standing on, skipped
      3     0.75    9.030330    1.969670      9,1   nothing
      4     1.00    9.207107    1.792893      9,1   nothing
      5     1.25    9.383883    1.616117      9,1   nothing
      6     1.50    9.560660    1.439340      9,1   nothing
      7     1.75    9.737437    1.262563      9,1   nothing
      8     2.00    9.914214    1.085786      9,1   nothing
      9     2.25   10.090990    0.909010     10,0   rock

  the caster hands back ray 1 as 2.25 cells, met rock

The table is the Interlude, digit for digit, and the last line is the caster agreeing with it. Two of the nine samples were spent inside the cell the creature was standing on and both were thrown away. Six landed in 9,1, which is the price of a stride short enough to be safe: most samples are re-asking a question the previous sample already answered. And the ray stopped after nine, having walked 2.25 cells of a possible twelve, which is the number that makes this sense affordable. A ray that met nothing would take forty-eight samples, and nine of those is 432 lookups for one creature for one tick. Almost no ray ever gets there.

One ray walked sample by sample across the cells of the grid, and the fan of nine those rays belong to The left panel shows twenty cells of the valley, columns 7 to 11 and rows 0 to 3, drawn as a grid of squares. The creature stands at the centre of cell 8,2, which is highlighted. A straight line leaves that centre heading up and to the right at forty-five degrees, with nine dots along it a quarter of a cell apart. The first two dots are still inside cell 8,2 and are marked in red as thrown away. The next six all fall inside cell 9,1, which is empty soil. The ninth falls inside cell 10,0, which is rim rock, is outlined in red as the cell that stopped the ray, and is labelled 2.25 rock. Two lines of text below the grid say the eye is at 8.5, 2.5 in the middle of cell 8,2, and that 9,1 is bare soil while 10,0 is the rim. The right panel shows nine straight rays leaving one point, spread evenly over a hundred and twenty degrees, with the outermost two and the middle one labelled ray 0, ray 4 and ray 8, the middle ray drawn in gold, and a note that the fan is a hundred and twenty degrees across with fifteen degrees between neighbours. ONE RAY, NINE SAMPLES, ONE ANSWER 7,0 8,0 9,0 10,0 11,0 9,1 8,2 2.25 rock the eye is at 8.5, 2.5, the middle of 8,2 9,1 is bare soil; 10,0 is the rim of rock NINE RAYS, ONE HEADING ray 0 ray 4 ray 8 120 degrees across, 15 apart ray 4 runs along the heading
Figure 47.1 — the walk on the left and the fan on the right. Every dot is one lookup. The two red ones were spent on the cell the creature is standing on and thrown away; the green one is where the ray stopped, and the only two numbers that leave this picture are its distance and its code.

The eye point and the cell answer

A caster cannot start without settling two things the earlier chapters left open. Where does the ray leave from, and what does a cell say when it is asked.

The first one is a real conflict and it has to be closed before anything walks. A creature arrived here carrying two positions. One was a cell, set when it was stamped onto the ground. The other is the position inside its moving body, in world pixels, which is what steering pushes and what the movement charge is priced off. Both were true, and they went on being true for exactly as long as nothing moved. The moment a body takes a step, the pixels change and the cell does not, and from then on the world holds two answers to one question and no rule about which to believe.

The cell goes. Position lives in the body, because the body is the thing that changes it, and the cell of ground a creature is on is worked out from that position every time it is asked. One of the two constructors goes with it, for the same reason: it stamped a creature onto a named cell and left the moving body unmade, mass and all, so a creature made that way divided by a mass of zero the first time anything pushed it. The survivor is the one that stands the body up, and Centre is the single bridge from the name of a cell to a place in the world.

▣ Build · stage 2 — one position, one constructor
// internal/beast/body.go

// Beast is one creature standing somewhere: the row it was stamped
// from, the moving body it stands in, and the store, which is the only
// number a quiet tick changes. Everything else on it is a running total
// or a fact about when it started.
//
// There is exactly one answer on here to "where is it". Position and
// velocity live in the embedded field.Body, in world pixels, because
// that is where everything else in this world that moves keeps them,
// and Cell divides that position down to the cell of ground it lands
// on. A second field naming a cell would be a second answer, and the
// two would agree until the first thing walked.
//
// The body is embedded rather than named so that both spellings read:
// the body as a whole is b.Body, and the two vectors on it are b.Pos
// and b.Vel.
type Beast struct {
	Name  string
	Kind  *Kind
	Store float64 // energy units in the store right now
	Born  int     // the tick it was born on
	Ate   float64 // grams it has taken off the valley since it was born
	Dead  bool    // its store reached nothing and it stopped
	Last  Ledger

	field.Body         // where it is and how fast, in world pixels
	Face       float64 // the heading its eyes look along, radians clockwise of east

	// Greedy is the bite as it was first written: a whole mouthful
	// taken whether or not the plant has one to give. Blind is the
	// caster without the line that lets a ray leave the cell it starts
	// on. Both are kept behind flags so the failures can be run instead
	// of described, and both are false in every creature this book
	// ships.
	Greedy bool
	Blind  bool

	// Unpaid is the bite with the repayment switched off: the grams
	// still leave the plant, but the minerals they were holding go
	// nowhere instead of landing on the cell that lent them. It is
	// named for the ground's books and not for the animal, because a
	// creature carrying it eats exactly as it always did and the whole
	// of the loss is the bed's. It is kept behind a flag so what that
	// missing line costs can be run instead of described, and it is
	// false in every creature this book ships.
	Unpaid bool
}

// Centre is the middle of a cell in world pixels: the position a
// creature put on a cell by name is stood up at, and the only bridge
// this package has from the name of a cell to a place in the world.
func Centre(c sim.Coord) field.Vec2 {
	return field.Vec2{X: (float64(c.X) + 0.5) * terra.Tile, Y: (float64(c.Y) + 0.5) * terra.Tile}
}

// Cell is the cell of ground the creature is standing on: its position
// in world pixels, divided down by the pixels a cell is wide. It is
// worked out on every call and never stored, so it cannot be stale.
func (b *Beast) Cell() sim.Coord {
	return sim.Coord{
		X: int(math.Floor(b.Pos.X / terra.Tile)),
		Y: int(math.Floor(b.Pos.Y / terra.Tile)),
	}
}
// internal/beast/beast.go

// Spawn turns a row of numbers into one creature standing somewhere,
// with a full store and a body of the row's own mass. Every trait is
// copied into the individual and the row is never consulted for a rate
// again, so nothing one creature does can reach back and edit the kind
// it came from.
func (k Kind) Spawn(at field.Vec2, t int) *Beast {
	kind := k
	return &Beast{Name: k.Name, Kind: &kind, Store: k.Full,
		Body: field.NewBody(at, k.Bulk), Born: t}
}

The body is embedded and not named, which is what lets both spellings the earlier chapters used go on reading. Written as a whole it is b.Body, and the two vectors on it come through as b.Pos and b.Vel without the middle word. Face is new here and it is the only thing on a creature the fan is measured from: an angle in radians, counted clockwise of east, which is the direction the eyes point and not necessarily the direction the legs are carrying the body.

Now the second question. A ray lands on a cell and needs one answer out of five, which means somebody has to decide what a cell holding a plant, a creature and a puddle answers. The decision is an order, and the order has to be written down because it is a claim about what blocks a line of sight.

▣ Build · stage 3 — the index, and the one lookup a ray makes
// internal/beast/look.go, continued

// View is the valley as it stood when a phase opened: the ground
// itself, and an index of what is on which cell, built once so that
// three thousand creatures walking four hundred samples each do not
// walk the stand list four hundred times over.
type View struct {
	Valley *terra.Valley

	stand map[sim.Coord]*terra.Stand
	herd  map[sim.Coord][]*Beast
}

// NewView indexes one valley and one roster of creatures by cell.
func NewView(v *terra.Valley, herd []*Beast) *View {
	w := &View{Valley: v,
		stand: make(map[sim.Coord]*terra.Stand, len(v.Stands)),
		herd:  make(map[sim.Coord][]*Beast, len(herd))}
	for _, st := range v.Stands {
		w.stand[st.At] = st
	}
	for _, b := range herd {
		c := b.Cell()
		w.herd[c] = append(w.herd[c], b)
	}
	return w
}

// Mass is the grams standing on one cell, and nothing at all where
// nothing is standing.
func (v *View) Mass(c sim.Coord) float64 {
	if st, ok := v.stand[c]; ok {
		return st.Plant.Mass
	}
	return 0
}

// Stand is the plant on one cell, and whether there is one.
func (v *View) Stand(c sim.Coord) (*terra.Stand, bool) {
	st, ok := v.stand[c]
	return st, ok
}

// At is what a ray meets on one cell, with one creature left out of the
// answer because a creature cannot see itself. A cell off the grid is
// the rock the valley is cut out of.
func (v *View) At(c sim.Coord, self *Beast) Hit {
	if !v.Valley.Bed.In(c) {
		return Rock
	}
	for _, b := range v.herd[c] {
		if b != self && !b.Dead {
			return Creature
		}
	}
	switch v.Valley.Bed.Kind(c) {
	case sim.Water:
		return Water
	case sim.Rock:
		return Rock
	}
	if _, ok := v.stand[c]; ok {
		return Plant
	}
	return Nothing
}

View is built once and read many times, which is the entire reason it exists. A creature walking 432 samples through a list of stands would walk that list 432 times; walking it through a map costs one hash per sample and one pass over the stands per phase, however many creatures there are. The two maps are unexported because nothing outside this file has any business editing what is standing where in the middle of a reading.

At asks its questions in a fixed order. Off the grid first, and off the grid answers rock, because the valley is cut out of rock and a creature looking over the rim is looking at the inside of a wall. Then the creatures, before the ground, because a living animal standing on a cell is what a ray runs into and the soil under its feet is not. Then the ground itself. Then, last, whatever is rooted there. And the creature doing the reading is left out of every one of those answers, which is the whole of what self is for.

$ go run ./cmd/eyes -mode codes -at 8,2
eyes: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams

  4 creatures founded on stream 12, at 5,2 9,5 10,2 0,5
  the one looking is stood on 8,2 by hand, eye at 8.5000,2.5000 in cells
  facing 0 degrees clockwise of east, sight 12 cells, stride 0.25 of a cell

  what one cell answers, asked of every cell of the grid and four off it
                on grid  creature   ground   standing   answers
          here     true       yes     soil     0.00 g   nothing
       a plant     true         -     soil   383.56 g   plant
    a creature     true       yes     soil     0.00 g   creature
      the pond     true         -    water     0.00 g   water
       the rim     true         -     rock     0.00 g   rock
     bare soil     true         -     soil     0.00 g   nothing
  off the grid    false         -        -          -   rock

  the creature reading is left out of its own answer: cell 8,2 holds it
  and comes back nothing to it and creature to anybody else

  over the whole 12x8 grid: 29 nothing 12 plant 4 creature 16 water 35 rock

Read the first and third rows against each other. Both cells have a creature standing on them and they answer differently: the one the reader is standing on comes back nothing, and the other comes back creature. Ask the same cell on behalf of somebody else and it answers creature to them. A cell has no single truth about what is on it, because the question a ray asks is not "what is here" but "what is here that is not me".

The nine-ray fan

One ray is a loop. Nine of them is the same loop with an angle that changes, and the whole caster comes to about twenty lines.

▣ Build · stage 4 — the fan, walked into a slice the caller owns
// internal/beast/look.go, continued

// Look walks the whole fan and fills the caller's slice, one Ray per
// eye. Ray 0 is the leftmost of the fan and ray Fan-1 the rightmost,
// so the middle one looks straight along the heading: ray i leaves at
// Face plus (i minus four) gaps.
//
// The eye is the body's own position, which for a creature stood on a
// cell by name is the middle of that cell. A ray steps Stride cells at
// a time and stops at the first cell that is not empty, ignoring the
// cell the creature is standing on: a creature is not blocked by the
// ground under its own feet. A ray that reaches the row's Sight without
// meeting anything comes back at that distance with nothing on it.
func (b *Beast) Look(v *View, eyes []Ray) {
	from := b.Pos.Scale(1.0 / terra.Tile)
	self := b.Cell()
	for i := range eyes {
		a := b.Face + (float64(i)-float64(Fan-1)/2)*Gap
		dx, dy := math.Cos(a), math.Sin(a)
		r := Ray{Dist: b.Kind.Sight, What: Nothing}
		for d := Stride; d <= b.Kind.Sight; d += Stride {
			c := sim.Coord{
				X: int(math.Floor(from.X + dx*d)),
				Y: int(math.Floor(from.Y + dy*d)),
			}
			if c == self && !b.Blind {
				continue
			}
			if w := v.At(c, b); w != Nothing {
				r = Ray{Dist: d, What: w}
				break
			}
		}
		eyes[i] = r
	}
}

// Eyes is the slice one creature's fan is read into. A caller that
// keeps one for the life of a run allocates nothing on a tick.
func Eyes() []Ray { return make([]Ray, Fan) }

The signature is the thing to look at twice. Look is handed the slice it fills and allocates nothing, so a caller that keeps one slice for the life of a run pays for it once instead of three thousand times a tick. The ray is set to its full sight before the walk starts, so the case where a ray meets nothing at all needs no branch at the bottom: it is what the loop already holds when it runs out of distance. And a ray that stops carries the exact distance the sample sat at, not the distance of the cell boundary it crossed, because the sample is the only place the walk actually looked.

$ go run ./cmd/eyes -mode fan -at 8,2
eyes: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams

  4 creatures founded on stream 12, at 5,2 9,5 10,2 0,5
  the one looking is stood on 8,2 by hand, eye at 8.5000,2.5000 in cells
  facing 0 degrees clockwise of east, sight 12 cells, stride 0.25 of a cell

    ray   degrees       east      south  samples     cells   and met
      0     -60.0   0.500000  -0.866025        3      0.75   plant
      1     -45.0   0.707107  -0.707107        9      2.25   rock
      2     -30.0   0.866025  -0.500000        3      0.75   plant
      3     -15.0   0.965926  -0.258819        3      0.75   plant
      4       0.0   1.000000   0.000000        2      0.50   plant
      5      15.0   0.965926   0.258819        3      0.75   plant
      6      30.0   0.866025   0.500000        3      0.75   plant
      7      45.0   0.707107   0.707107       15      3.75   rock
      8      60.0   0.500000   0.866025       17      4.25   plant

  nearest 0.50 cells on plant, furthest 4.25 on plant, and the fan cost
  58 lookups against the 432 a fan that met nothing would

Six of the nine rays stop inside a single cell, which says the creature has plants pressed up against it on almost every side; ray 1 gets 2.25 cells north-east before the rim stops it, and ray 8 gets 4.25 south-east before it finds something to eat. The whole fan cost 58 lookups against the 432 a fan staring across an empty valley would have spent, and that ratio is not a coincidence: a ray stops at the first thing it meets, so the more crowded the ground is, the less the sense costs. It is the opposite of how the price of asking every plant behaves.

Distances and codes are exact and hard to picture. The same fan laid over the ground is neither.

$ go run ./cmd/eyes -mode map -at 8,2
eyes: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams

  4 creatures founded on stream 12, at 5,2 9,5 10,2 0,5
  the one looking is stood on 8,2 by hand, eye at 8.5000,2.5000 in cells
  facing 0 degrees clockwise of east, sight 12 cells, stride 0.25 of a cell

  @ the one looking   : a cell some ray crossed   P C W R what a ray stopped on
  . soil   ~ water   # rock   o a plant and c a creature no ray reached

    ##########R#
    #.o..oo.P:o#
    #....co.@Pc#
    #.~~~~~.::.#
    #.~~~~~~o::#
    c...~~~~.c:R
    #...~o..ooP#
    ############

  the fan crossed 11 cells of the 96 on the grid, and stopped on 9 of them

Eleven cells out of ninety-six, and the creature can say nothing at all about the other eighty-five. The pond starts a little over two cells to the south-west and the fan is pointed the other way. All four of the other creatures are invisible. None of that is a defect being confessed: a fan is a narrow, cheap, directional sense, and a creature that wants to know what is behind it has to turn round and spend the ticks.

There is one line in the caster that looks like a special case and is load-bearing. Take out the test that lets a ray leave the cell it started on and the sense stops working, in a way that produces no error and no crash.

⚠ Worked failure — blocked by the ground under its own feet
// internal/beast/look.go — the caster with the own-cell test taken out

		for d := Stride; d <= b.Kind.Sight; d += Stride {
			c := sim.Coord{
				X: int(math.Floor(from.X + dx*d)),
				Y: int(math.Floor(from.Y + dy*d)),
			}
			if w := v.At(c, b); w != Nothing {
				r = Ray{Dist: d, What: w}
				break
			}
		}
$ go run ./cmd/eyes -mode blind -at 9,2
eyes: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams

  4 creatures founded on stream 12, at 5,2 9,5 10,2 0,5
  the one looking is stood on 9,2 by hand, eye at 9.5000,2.5000 in cells
  facing 0 degrees clockwise of east, sight 12 cells, stride 0.25 of a cell

  standing on 26.46 grams of plant, on cell 9,2

    ray      cells      and met      cells      and met
           shipped                   blind             
      0       1.00        plant       0.25        plant
      1       0.75        plant       0.25        plant
      2       0.75     creature       0.25        plant
      3       0.75     creature       0.25        plant
      4       0.50     creature       0.25        plant
      5       0.75     creature       0.25        plant
      6       0.75     creature       0.25        plant
      7       2.25         rock       0.25        plant
      8       3.00         rock       0.25        plant

  the blind fan walked 9 samples against 42, and every ray of it
  came back the same two numbers, 0.25 and plant

Every ray returns 0.25 cells and the word plant. Nine directions, nine identical answers, and the creature is standing on a plant of its own so it will get those same two numbers wherever it turns and however far it walks, for as long as it stays on a cell with anything on it. It is not blind in the sense of seeing nothing. It sees one thing, enormous, in every direction, and it is the ground it is already standing on.

The shipped column beside it is what those nine directions actually hold: a plant one cell out, another a bit nearer, five rays that stop on a neighbouring creature, and two that run 2.25 and 3.00 cells to the rim. The failure did not lose a little accuracy. It replaced the entire contents of the sense with a constant, and the total cost of the broken fan is nine lookups against forty-two, so the bug also looks like an optimisation if the only thing being watched is the clock.

The rule underneath is about where a measurement starts. An instrument sitting inside the thing it is measuring will report the thing it is sitting in. The eye is at the middle of a cell that is occupied by definition, since the creature is in it, so the first sample of every ray is guaranteed to land on an occupied cell. Any sense that begins inside its own subject has to say so and step past it, and the alternative fixes are worse: starting the ray at the cell boundary makes the distance depend on which way the creature happens to face, and skipping the first two samples by count breaks the day a creature stands anywhere but the middle of a cell.

Why two numbers are enough

The claim at the top of the chapter was that a ray hands back a code and never the thing it met. The cheapest way to test whether that is enough is to try to draw the fan with nothing but what the fan returned, into the same buffer the client already paints the valley into.

▣ Build · stage 5 — nine lines, drawn from nine pairs of numbers
// cmd/eyes/main.go — the fan, drawn from what the fan returned

var (
	wants = render.Color(0xFFE8D8A0)
	warns = render.Color(0xFFC05A4A)
	empty = render.Color(0xFF8A9096)
)

// ink is the colour one finished ray is drawn in.
func ink(h beast.Hit) render.Color {
	switch h {
	case beast.Plant, beast.Creature:
		return wants
	case beast.Water, beast.Rock:
		return warns
	}
	return empty
}

// draw paints the valley the client already knows how to paint and puts
// the fan on top of it: one line per ray, from the eye to the pixel the
// ray stopped at, in a colour that says what stopped it.
func draw(v *terra.Valley, at sim.Coord, face float64, seed uint64, n int, path string) {
	b, w, herd := looker(v, at, face, seed, n)
	who(b, herd, face)
	eyes := beast.Eyes()
	b.Look(w, eyes)

	buf := scene.Paint(v)
	for _, h := range herd {
		dot(buf, h.Pos, empty)
	}
	for i, r := range eyes {
		a := angle(b, i)
		end := field.Vec2{
			X: b.Pos.X + math.Cos(a)*r.Dist*terra.Tile,
			Y: b.Pos.Y + math.Sin(a)*r.Dist*terra.Tile,
		}
		buf.Line(int(math.Round(b.Pos.X)), int(math.Round(b.Pos.Y)),
			int(math.Round(end.X)), int(math.Round(end.Y)), ink(r.What))
	}
	dot(buf, b.Pos, wants)

	fmt.Printf("  the valley %d by %d pixels, %s\n", buf.W, buf.H, buf.Hash())
	if path == "" {
		return
	}
	big := scene.Scale(buf, 3)
	if err := big.SavePNG(path); err != nil {
		fmt.Fprintf(os.Stderr, "eyes: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("  wrote %s at 3 times, %d by %d\n", path, big.W, big.H)
}

// dot marks one body with a two-pixel square, which is as small as a
// mark can be and still be found on a printed page.

Nothing in that loop can reach any object in the valley. It has an angle it worked out from the ray's index, a distance the caster returned, and a code it turns into a colour through a table with five entries. The endpoint is the eye plus the direction times the distance, in pixels, and the line routine is the one the client has been drawing plant stems with since the plants learned to branch.

▣ Build · capture the nine-ray diagram
$ go run ./cmd/eyes -mode draw -at 1,1 -face 30 -shot assets/frames/fan-nine-rays.png
eyes: 12x8 valley, tick 901, year 1 summer, 12 plants standing at 1517.5 grams

  4 creatures founded on stream 12, at 5,2 9,5 10,2 0,5
  the one looking is stood on 1,1 by hand, eye at 1.5000,1.5000 in cells
  facing 30 degrees clockwise of east, sight 12 cells, stride 0.25 of a cell

  the valley 192 by 128 pixels, 36fd69da93c2ec40422b97d6a66b8b6712995c523c5b7b175b6e9fcb14b3efc9
  wrote assets/frames/fan-nine-rays.png at 3 times, 576 by 384
A pixel drawing of the valley: brown soil, a large blue pond through the middle, a grey rim, and dark green plant stems scattered over it. Near the top left a small pale square marks a creature, and nine straight lines fan out from it to the lower right over a hundred and twenty degrees. The five upper lines are pale gold and stop almost immediately against a plant just beside the creature. Four lower lines are red: three run a short way down to the top edge of the pond and stop there, and the last runs straight down the left side of the picture for most of its height before stopping at the bottom rim.
Figure 47.2assets/frames/fan-nine-rays.png: the same nine rays the table printed, drawn into the client's own buffer at three times size. Gold is a ray that stopped on something a browser eats, red is one that stopped on something it cannot. Every pixel of it was drawn from a distance and a small whole number.

That picture is the argument finished. A drawing is about as demanding a consumer as a number can have, and it never needed the plant. It needed to know that something was 0.75 of a cell away in a particular direction and that the something was of a kind the table calls to inspect. Any other consumer of the fan is strictly easier to satisfy than the renderer, and one of them is coming: a row of numbers that gets multiplied by weights and added up, which cannot follow a pointer, cannot call a method, and would not know what to do with a plant if it were handed one.

The pattern generalises past creatures and past this book. A measurement crossing a boundary should be reduced, at the instrument, to the smallest thing the far side can act on. The far side then cannot accidentally depend on anything else: not on the plant's mass, not on its species, not on whether it is still alive by the time the reading is used. Every one of those would have come free with a pointer, and every one of them would have been a way for a reading taken at the top of a phase to change underneath the code reading it. Distance and code cannot go stale, because they are copies of nothing.

The cost of the discipline is real and it is paid in the same place every time. Five codes cannot tell a moss from a canopy tree, and a creature that walks up to a plant on the strength of a ray may find a gram and a half where it hoped for four hundred. That information is dropped on purpose: one slot per ray, one number, and the number the fan chose to spend it on is which of five kinds of thing is there. What a ray is bad at is quantity, and a creature that needs quantity is going to have to buy a second sense that answers a different question entirely.

Checkpoint

✓ Checkpoint — what the nine rays can now be asked
  • Given a heading and a ray number, produce the angle it leaves at, and say why ray 4 is the only one that agrees with the heading.
  • Handed a stride and an angle, work out where sample n lands in cells, turn that into a cell of the grid, and say what a stride of a whole cell would step over on a diagonal.
  • Name the five things At asks about a cell in the order it asks, and say why a creature's own cell answers differently to it than to anybody else.
  • Shown a fan whose nine rays all report the same distance and the same code, look at where the ray starts rather than at the angles.
  • Say what a ray costs when it meets something at half a cell against what it costs when it meets nothing, and why a crowding valley makes this sense cheaper.
  • Draw a creature's whole field of view into a frame holding nothing but a distance and a code per ray, and say what the drawing never had access to.
⚡ Exercises — try first, then reveal
Exercise 1 — the long way round. Ray 8 of the fan at 8,2 runs 4.25 cells, the furthest of the nine. Work out its angle and its first sample by hand, then walk the whole thing with go run ./cmd/eyes -mode step -at 8,2 -eye 8.

Ray 8 leaves at 0 + (8 − 4) × 15 = 60 degrees, so one cell along it is cos(60) = 0.5 east and sin(60) = 0.866025 south. The first sample sits at 8.5 + 0.25 × 0.5 = 8.625 and 2.5 + 0.25 × 0.866025 = 2.716506, which is still cell 8,2 and thrown away. Seventeen samples in all, and the interesting part is the middle: five in a row land in 10,5 without anything changing. A ray at sixty degrees needs two whole cells of travel to move one column east, so it lingers, and the further a ray leans off the axis the more of its samples re-ask a question the sample before it already answered.

$ go run ./cmd/eyes -mode step -at 8,2 -eye 8 | tail -8
     12     3.00   10.000000    5.098076     10,5   nothing
     13     3.25   10.125000    5.314583     10,5   nothing
     14     3.50   10.250000    5.531089     10,5   nothing
     15     3.75   10.375000    5.747595     10,5   nothing
     16     4.00   10.500000    5.964102     10,5   nothing
     17     4.25   10.625000    6.180608     10,6   plant

  the caster hands back ray 8 as 4.25 cells, met plant
Exercise 2 — a creature at the water's edge. Cell 1,4 is on the west shore of the pond. Run go run ./cmd/eyes -mode fan -at 1,4 and say what the fan can tell the creature about how big the pond is.

Nothing whatever. Seven of the nine rays stop on water between half a cell and one cell out, and the two that miss the pond run 3.75 and 3.00 cells to rock. The whole reading is "water, close, in seven directions". The pond covers sixteen cells of this valley and a browser standing at the edge of it cannot tell that from a puddle one cell wide, because a ray stops at the first thing it meets and never reports how much of that thing there was. This is the exact case where the fan is spending nine expensive walks to say one cheap thing.

$ go run ./cmd/eyes -mode fan -at 1,4 | tail -13
    ray   degrees       east      south  samples     cells   and met
      0     -60.0   0.500000  -0.866025        4      1.00   water
      1     -45.0   0.707107  -0.707107        3      0.75   water
      2     -30.0   0.866025  -0.500000        3      0.75   water
      3     -15.0   0.965926  -0.258819        3      0.75   water
      4       0.0   1.000000   0.000000        2      0.50   water
      5      15.0   0.965926   0.258819        3      0.75   water
      6      30.0   0.866025   0.500000        3      0.75   water
      7      45.0   0.707107   0.707107       15      3.75   rock
      8      60.0   0.500000   0.866025       12      3.00   rock

  nearest 0.50 cells on water, furthest 3.75 on rock, and the fan cost
  48 lookups against the 432 a fan that met nothing would
Exercise 3 — take the others away. The fan cast from 9,2 stops on a neighbouring creature five times out of nine. Predict what those five rays do when the population is founded empty, then run go run ./cmd/eyes -mode fan -at 9,2 -herd 0.

Four of them carry on to the rim behind where the neighbour was standing, at 1.75, 1.50, 1.75 and 1.75 cells instead of stopping at half a cell or three quarters of one. The fifth, ray 2, finds a plant at 1.25 that the neighbour had been hiding. Two things fall out of that. A creature blocks a line of sight like anything else, so a herd standing between an animal and its dinner makes the dinner invisible; and a fan is a reading of one moment, so the index it walks is built at the top of a phase and thrown away at the bottom of it.

$ go run ./cmd/eyes -mode fan -at 9,2 -herd 0 | tail -13
    ray   degrees       east      south  samples     cells   and met
      0     -60.0   0.500000  -0.866025        4      1.00   plant
      1     -45.0   0.707107  -0.707107        3      0.75   plant
      2     -30.0   0.866025  -0.500000        5      1.25   plant
      3     -15.0   0.965926  -0.258819        7      1.75   rock
      4       0.0   1.000000   0.000000        6      1.50   rock
      5      15.0   0.965926   0.258819        7      1.75   rock
      6      30.0   0.866025   0.500000        7      1.75   rock
      7      45.0   0.707107   0.707107        9      2.25   rock
      8      60.0   0.500000   0.866025       12      3.00   rock

  nearest 0.75 cells on plant, furthest 3.00 on rock, and the fan cost
  60 lookups against the 432 a fan that met nothing would

Eighteen numbers now, two per ray, and every one of them is about something a straight line ran into. The water in the ground is not something a straight line runs into, and neither is the difference between a moss and a scrub standing on the same cell. Those are the questions the fan is worst at, and answering them turns out to cost four lookups instead of four hundred.