Outside

A touch-first 3D adventure in the spirit of Inside: a boy of the 1910s, flat cap and faded blue overalls, in a desert of dunes, ruins and pyramids. Godot 4.7, built for phones and the web (the Scareathon arcade).

There are two levels: the desert (desert.tscn, where the game opens), which is laid out with the level editor inside the game, and the test yard (test_yard.tscn), where every mechanic has a station.

Controls

Gamepad

Everything can be done with a gamepad alone, from starting the game to leaving it: play, the notebook, whatever comes up over the game, and both editors as they are laid out for a desk. Any pad Godot knows the layout of will do (Xbox, PlayStation, Switch Pro: SDL’s mappings), plugged in before the game starts or after; every action listens to every pad. In a browser a pad is only owned up to once one of its buttons has been pressed (that is the browser’s rule): press one, and it works from then on. The touch controls are not drawn for a gamepad.

The buttons are named here as an Xbox pad names them, by where they are: A is the bottom one of the four (Cross), B the right (Circle), X the left (Square), Y the top (Triangle). The keyboard and touch do what they did.

  Play The notebook A panel over the game The level editor The tomb editor
Left stick He moves: a little walks, all the way runs, held there sprints The mark, from thing to thing; on the colour wheel, points at a hue The mark Moves the cross over the ground (the view goes with it) Moves the tomb about
Right stick Turns the camera On the colour wheel: paler, stronger, lighter, darker   Turns the view and tips it  
D-pad   The mark; left and right slide a slider; on the wheel, as the right stick The mark Left, right: what the page offers to put in. Up, down: the pages. In the settings: the rows, and left and right change a number or a choice The mark: buttons, the row of rooms, the panel, the marks on a floor plan
A Jump (held: higher) Works what the mark is on; takes hold of the colour wheel, and keeps its colour Says the name; presses the button; puts a leaf away Chooses the thing at the cross; puts in; joins. Held while the cross moves: carries the thing (or a river’s point) Presses what the mark is on
B Duck, crawl Back: out of the wheel (its colour put back), then to the tab, then away Step back Not now: gives up putting in, ends joining, unchooses; out of the settings Closes a sheet; gives up moving or joining a room
X His hand: pick up, throw, pull a lever, turn a mirror, fire; held, climb a rope     Removes what is chosen Removes the chosen room
Y       The settings of what is chosen (of the desert, with nothing chosen) The mark to the chosen room’s panel
Shoulders   The part before, the part after   The page before, the page after The room before, the room after
Triggers Right: his hand, as X (to fire)     Left nearer, right further off Left nearer, right further off
Start The notebook Puts it away   Every button of the editor’s own (Play, Save, More…), for the d-pad and A The mark to Play
Back (Select) In a tomb: the mark on Stuck, Edit and the map        
Left stick pressed in Drops him as a ragdoll, and stands him up (as R)     Undo Undo
Right stick pressed in The camera round behind him (as two taps)     Redo Redo

Before this a gamepad could move him (the pad numbered 0 only), jump, duck, use his hand and turn the camera; take the notebook out with Start, move its mark and turn its tabs; and move the mark on a guardian’s panel. It could not choose anything in the notebook or say a name (A was bound to nothing there), step back, bring the camera round, or reach a tomb’s cards, its Stuck and Edit, or anything in either editor.

scripts/gamepad.gd (Gamepad) has the actions, which was used last, and the marks. To check it:

godot --headless --path . --fixed-fps 60 --script tools/gamepad_test.gd
godot --path . --resolution 1280x720 --script tools/gamepad_shots.gd -- <folder>

The first plays the game with joypad events and nothing else (walks, runs, jumps, ducks, picks up, throws, turns the camera, answers a guardian; visits every tab of the notebook, changes a setting, paints his shirt on the wheel; puts in, carries, changes, joins, names, removes and undoes in the level editor; goes to the tombs, works the card, and makes a room in the tomb editor) and ends PASSED or FAILED. The second takes a picture of each thing the mark is drawn on. Both play with a gamepad of their own number and listen to no other (Gamepad.listen_to): a real one on the desk is heard whether the window has the focus or not, and somebody may be playing. The other tools that open a window listen to none (Gamepad.device = 99). It has not been tried with a real pad in a browser.

The colour wheel

Wherever a colour is chosen it is chosen on a wheel (ColourWheel, scripts/colour_wheel.gd): a ring of every hue, and inside it a square of that hue from white across to its full strength and down to black. A finger or a mouse that comes down on the ring turns it, and goes on turning it wherever it is dragged; one in the square moves about the square. With a gamepad: A on the wheel takes hold of it, the left stick points at a hue, the right stick or the d-pad moves in the square, A keeps the colour and B puts back the one it had. The only colours chosen in the game are the boy’s, in the notebook (Me): his skin, hair and eyes, and each thing he wears. (No thing in either editor has a colour to choose; if one is given one, this is what it is to be chosen on.) The few ready-mixed paints for skin, hair and eyes beside his photograph are still there, as a quick way to a likely colour: a touch on one paints him and turns the wheel to that part of him.

What he can do

test_yard.tscn is a yard among dunes with a station for each of these set out round it, a sign over each. The camera orbits there: drag anywhere on the right of the screen off the buttons (an eye mark shows where; two quick taps there bring the camera back behind him; jump is its own button), hold the right mouse button, or use a right stick.

Each of the newer moves has a switch on the Player (dive_enabled, flip_enabled, spin_enabled, rest_enabled, gun_handling), all on.

The yard also has everything sand does (footprints, sand running down steep faces, wind and its weather plate, sand pouring into heaps, two lumps of wet sand), water as a tank and as a pond lying in the sand, a fire, the guns on a bench with things to shoot, a dig (tools to pick up, the helmets on stands, someone wearing one), his brother, a cat, and a pen each for the hounds and the mummy, with a plate that lets them loose and calls them off; and birds (a station of them to run at, more at the pond, and vultures overhead). North of the start the kinds of ground are laid side by side in a row to walk along.

Sand, and the other kinds of ground

The ground of a level is a SandGround (scripts/sand_ground.gd), drawn by the one sand shader (scripts/sand.gd).

tools/sand_sheets.gd walks and runs him over all of it in the test yard and saves pictures (see the top of the file).

Dunes

The desert’s dunes are made by SandDunes (scripts/sand_dunes.gd) from the wind the level has, and DesertTerrain makes the ground from them. Every dune is one line on the ground, its brink. Up wind of it the back rises gently (about 11 degrees at its steepest), and down wind the slip face falls away at the angle sand lies at, 32 degrees: he walks up the one, and on the other the sand lets go under him. There are five forms:

How they are set out is the level’s “Kind of dunes” (on the editor’s Level page; dunes in the layout):

The shipped desert has crescents: there is little open ground between its places, and crescents fill what there is with a dozen big dunes.

A field is set out as the wind sets one out: in rows across it, each row a dune’s length and some flat ground behind the last (“Flat ground between them”), the dunes of a row much of a size and standing opposite the gaps of the row before, no two alike. Everything comes from the level’s seed through whole numbers: the same level is the same ground every time, on every device. In a field the flat ground between dunes is painted harder and a little paler (packed, pale and a little white sand). A dune runs out to nothing, over a dozen metres, where it would stand on level ground, a pond, rock, a person, a door or a checkpoint, and one with four fifths of it gone is left out; a palm, a stone or any other prop does not move a dune, and stands on it as it finds it. The Ground page also has a Star dune and Linked dunes to place by hand, beside the Dune and the Ridge.

A field of dunes is smooth everywhere. The dunes were rough and spiky, and these were the causes and the cures:

What dunes look like has a great deal to do with where the sun is: a slip face that is turned from the sun is dark and its crest is a hard line, and one turned to it is nearly lost. The shipped desert’s wind was turned right round (it blows to the west-south-west now) so that its slip faces are turned from the sun.

A level saved before this (one made in the editor on a phone, or pasted in) says nothing of the kind of dunes, and so keeps them exactly as they were: scattered, round at the crest, the same dunes in the same places, nothing moved under what was put on them, drawn as flat triangles, with soft waves for ripples. What does change in it: things stand on the ground exactly as it is drawn and stood on, where they used to stand on the height the dunes were worked out from, which between two corners of the ground’s grid could differ by a hand’s breadth. To give such a level the new dunes, choose a kind on its Level page; the ripples and the far hills are there too.

The desert has no edge. The level is still a square of ground (size across: its pads, its water and everything that stands in it are there), but it is no longer closed in by a rim with a wall nothing could see inside it. He can walk out of the square any way he likes and the dune sea goes on, for as far as he cares to walk:

An old level may still say rim_height and rim_width: they are not used, and the row for them is gone from the editor’s Level page. (The test yard makes ground of its own and is as it was: it ends in a plain.) The sea is one square repeated, 800 m on in the shipped desert: from the ground that cannot be told, from high above it could be.

The ground is made again by the editor a moment after any change: that takes about a quarter of a second for the shipped desert on a desk (it was asked of the dunes corner by corner, and is now worked out dune by dune).

tools/terrain_sheets.gd takes pictures of all of this (see the top of the file): crests, start, looks (each look of the ripples, and where two meet) and relief (the shape of the ground alone, as a map) among them.

Rock: cliffs, the White Desert, the Valley of the Kings

All of it is drawn with one rock shader (scripts/rock.gd, Rock.material(colour, seed)): beds lying level, each a tone of its own with a dark parting under it; streaks run down steep faces; joints; sand lying on whatever is nearly level; and scree. It comes in chalk, limestone, sandstone and dark basalt (Rock.STONES). On the editor’s Stone page:

And on the Level page, Far hills: mesas and a long low cliff standing all round the level in the haze (DesertHorizon), so that the desert does not end at an edge. One draw call; 0 is none.

What is a model and what is made from numbers. The rock that has a fixed shape was modelled in Blender, by a script, and can be looked at and changed there: tools/rocks.blend. The game reads models/rocks/rocks.glb (scripts/rock_models.gd). To make them again:

blender --background --python tools/build_rocks.py
godot --headless --path . --import

(or change tools/rocks.blend by hand and export it: the top of tools/build_rocks.py says how. Running the script again makes the .blend afresh.) The game goes by the names of the objects:

Object What it is
mushroom_a _b _c, dome_a to _d, haystack_a to _c, cone_a _b, shelf_a to _c, outcrop_a to _c The rock formations. Each is modelled 1 across (and only shown bigger in the .blend); the game picks one of the kind by its seed, turns it, and makes it the width asked for, a little taller or squatter. Its bank of sand is deepest on +X, which the game turns down wind. Add a dome_e and fields will use it
stone_a to _d Loose stones, scattered through a field
tomb_door, tomb_door_shut, tomb_door_open The doorway with its ramp, stair and walls (it looks out along Godot’s +Z, Blender’s -Y; the cliff is at its back); and what fills it, shut or open
number_board, rubble_wall, spoil_heap_a _b The board for a tomb’s number, a length of dry-stone wall, and heaps of chippings
el_qurn The peak, 1 from its middle to its foot
horizon_mesas The far hills: a ring 100 from its middle
anything with _hull1, _hull2… after its name What is solid of the thing named: the game puts a hull round the points of each. A thing with none is not solid

The materials say what each face is drawn with: rock (the rock shader, in whatever rock the level asks for), sand (the sand shader), dressed (cut stone), dark, wood. The colour of each point of a rock face tells the shader more: red is how light the rock is there, green how much sand lies over it, blue is 1 for scree and rubble.

What is still made from numbers, in code: the dunes, the ground and the ripples; and the cliff (scripts/cliff.gd). A cliff follows whatever line or outline it is given (the valley’s hills are one cliff, with the wadi cut out of its top), comes down to dunes that are a different height all along it, clears its scree away where a doorway is, and puts ledges where a way up is asked for. Lengths of modelled face set along the line could not do those things without their joins showing at every bend and their feet standing in the air or the sand, so it was left as it is.

The Valley of the Kings. KingsValley is a block of limestone hills with a wadi cut into it from the south (its own +Z): it bends as it goes, a branch leaves it on each side, and at its head stands the peak. Scree lies at the foot of the cliffs; a path is beaten along the floor (packed, pale sand on white dust) and up each branch; and in the cliff foot are the tomb doorways, up to eight, each where the cliff has cleared its scree back for it. A doorway is a square-cut entrance under a lintel, with a ramp of chippings up to its sill between two dry-stone walls, a short stair down inside to the door, a heap of spoil tipped to one side, and its number on a board. Size, the height of the cliffs, how many doorways, which valley (the seed) are on its panel. It stands on level ground of its own, which the desert makes for it.

Each doorway is a TombDoor, in the group tomb_doors. Shut, it is walled up. One of the valley’s may be open (“Which is open”), and “It leads to” says what walking in at it does: nothing yet; today’s tomb; a tomb of its own (from “Which tomb”, at “How hard”); or one that branches. A tomb is opened as the menu opens one (TombLevel.play, and tomb.tscn, walked through), and when it is done the card has Back outside, which brings him back to the desert standing before the door he went in at (TombLevel.back_to, Desert.play_from). The desert itself starts afresh when he comes back: nothing of it is kept while he is below. A door also sends entered, for whatever else a level’s maker wants of it. The shipped desert’s valley is in its north-west corner, with KV 9 open and leading to today’s tomb.

What is true to the place: the royal tombs are cut into the foot of limestone cliffs in a dry wadi with side branches, west of Thebes; the pyramid-shaped peak that stands over it is el-Qurn, “the horn”, which may be why the kings chose it; the entrances are plain square-cut doorways with a stair or ramp down, and were walled up and sealed; by 1910 the floor was a dig, with the excavators’ rubble retaining walls and heaps of limestone chippings about the doorways and beaten paths between; and the tombs are numbered KV 1 upward (Wilkinson painted the first numbers on them in 1827). The numbers on the boards are real tombs: KV 9 (Ramesses V and VI), 17 (Seti I), 11 (Ramesses III), 35 (Amenhotep II), 6 (Ramesses IX), 8 (Merenptah), 34 (Thutmose III), all open before 1910; and KV 62, which is Tutankhamun’s, and was not found until 1922: here it is the last, and shut. What is not true: the real valley is about a kilometre long and its cliffs many times higher, its tombs are not in this order nor in these places, the real peak stands well back from the head of the wadi and far above it, the stair of a real tomb goes down below the floor of the valley where this one goes up a ramp to its sill and down three steps inside, and nobody put the numbers on boards on posts: they were painted by the doors.

tools/terrain_test.gd checks all of this with nothing drawn (the same seed, the same ground; an old level loads and its ground is what it was; up a dune and over; the slip face lets go; thrown at every rock from all round, running and jumping, he does not get inside and can walk away; the ledges of a cliff are climbed; doors), and tools/terrain_sheets.gd -- <folder> takes the pictures (the top of the file lists them):

godot --headless --path . --fixed-fps 60 --script tools/terrain_test.gd
godot --path . --resolution 960x960 --script tools/terrain_sheets.gd -- <folder> [dunes kinds ripples rocks field cliff valley horizon level]

The level editor

In the desert, the notebook (top right), Places, then “Edit this level”. The game stands still and the view goes up over him.

It is laid out two ways. On a phone or a tablet, in the game or in its browser (wherever the touch controls are shown), it is laid out for a phone held sideways: see “The level editor on a phone”, below. Elsewhere it has panels for a desk, which are what the rest of this part describes. -- phone on the command line asks for the first anywhere, -- desk for the second. They are one editor (scripts/level_editor.gd) with two screens, do the same things to the same level, and keep it in the same form.

The layout is plain data (scripts/level_layout.gd says what is in it); scripts/desert.gd makes the level from it, and scripts/level_editor.gd is the editor. To check the editor without a phone (each needs a window, and a folder for its pictures; neither touches a level made in the game, which they keep clear of by saving to a file of their own, LevelLayout.saved_at):

godot --path . --resolution 1280x720 --script tools/editor_test.gd -- <folder>
godot --path . --resolution 1560x720 --script tools/editor_phone_test.gd -- <folder> phone
godot --path . --resolution 1560x720 --script tools/level_editor_shots.gd -- <folder>

The first works the panels for a desk. The second works the layout for a phone with nothing but touches (below). The third takes a picture of every state of the layout for a phone and measures what there is to touch in each; with desk after the folder it takes the same pictures of the panels for a desk, and only reports.

The level editor on a phone

A phone held sideways is about 1560 by 720 of the game’s pixels, and 720 of them are about 65 mm: a button 45 high is 4 mm, and a thumb wants 8. So on a phone the editor has a screen of its own (scripts/level_editor_phone.gd, LevelEditorPhone) with these rules, which the tomb editor keeps too:

What is done, by touch:

tools/editor_phone_test.gd does all of that with touches sent through the game’s own input, finding each button on the screen by its word and touching its middle; tools/touch_audit.gd is the measure both it and the pictures use. It has only been tried so: with touches made up by a test, in a window on a desk, with the desk’s renderer and the web’s (--rendering-method gl_compatibility). How the keyboard of a phone’s browser behaves over the page for words, and how the pages feel under a real thumb, have still to be seen on a phone.

Puzzle parts

More to make puzzles of, each a script of its own that is whole from new(), on the editor’s Puzzle page, and with a station in the test yard (west of the pool, and east of the low bars). Those that are on or off work whatever a pressure plate works: choose the door (or bridge, sand fall, mummy…) and “Choose what works it”.

godot --headless --path . --fixed-fps 60 --script tools/puzzle_test.gd
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/puzzle_sheets.gd -- <folder> [parts light water yard]

The first runs every part with nothing drawn, alone and then wired to doors in the desert as the editor makes them, and ends PASSED or FAILED. The second draws them.

How he moves

Nothing is a clip; scripts/character_rig.gd poses him every frame.

The menu is the boy’s field notebook. It lies in the top right corner of the screen: touch it (or Esc, or Start on a gamepad) and the book comes up over the stopped game, its cover swings open, and its pages are the menu. Touching anywhere off the book, the corner again, the “Put away” strap or Esc puts it away. Tabs down its edge turn to each part (a finger drawn across the pages, Page Up and Page Down, or a gamepad’s shoulder buttons turn too); the arrow keys, or a gamepad’s d-pad or stick, move a red mark from one thing to the next, and Enter or A works it (B goes back: see Gamepad, above).

What is chosen lasts until the game is closed.

The game writes in the journal through Notebook (scripts/notebook.gd), from anywhere:

Notebook.note("The door in the east wall will not move.", "Wednesday")   # a paragraph, under a heading if given
Notebook.task("Find what works the east door", "east_door")              # a box to tick...
Notebook.done("east_door")                                               # ...and ticking it
Notebook.find("Pot, red ware", "By the fallen column. 7 in. high.", "pot")  # numbered as it comes, drawn small beside
Notebook.sketch("eye", "Cut over the door.")                             # a drawing with a line under it
Notebook.translation(signs, "An offering which the king gives...", "North wall")

A drawing (and the signs of a translation) is the name of one of NotebookInk.SKETCHES, a Texture2D, or a Callable(on: CanvasItem, rect: Rect2) that draws it: so whatever reads real hieroglyphs can hand over a picture of the signs, or a function that draws them, with what they say. The five entries in a new game are placeholders (Notebook.placeholders).

Everything in the book is drawn in code (scripts/notebook_ink.gd: paper, ruling, stains, pencil lines that waver, paint and cloth). The writing is Patrick Hand, by Patrick Wagesreiter, under the SIL Open Font Licence (fonts/PatrickHand-Regular.ttf, licence in fonts/OFL.txt). To check the notebook without a phone:

godot --path . --resolution 1280x720 --script tools/notebook_test.gd -- <folder>

Going between levels

Every change of level goes through Loading (scripts/loading.gd), and nothing else changes the scene: the notebook’s map and list, “Start this place again”, the tomb’s card and its “Back outside”, a doorway of the Valley of the Kings, both editors’ Play and the ways back from them.

Loading.go("res://desert.tscn")            # to a level; the page names it from its file
Loading.go("res://tomb.tscn", "Tomb 12")   # or says what it is told
Loading.again()                            # this level again
await Loading.cover("The desert, as it came", work)   # the page over something long done to the level that is there

A page of the notebook comes across the picture from the right in a quarter of a second: squared paper, a pencil drawing of the place (the pyramids, the yard, a tomb’s doorway), “On my way to” and its name, and under that what is being done just now. A scarab walks along the foot of the page the whole time, drawing a pencil line after it, and the line under the name grows as the level gets on. Behind the page the old level is put away, the new one’s file is read (on another thread where there is one; in one go, with the page saying so first, where there is not, as in a web build without threads) and the level builds itself. The page is turned away to the left, in a third of a second, only when the level is built, has stood under it for a few frames with nothing slow in them and the camera still (so everything in sight has been drawn once, the camera is where it starts and he is on the ground), and the page has been up for four tenths of a second, so that a quick change is not a flash. A level that cannot be opened is said so on the page, and he is back where he was (or, at the first start, in the desert).

Most of the wait is the level making itself in code, which stops everything while it runs. So the levels that take long build in stages and let a frame go by between them: await Loading.stage("Raising the cliffs", 0.4) always does, and await Loading.breathe("Setting out the dig", 0.6) does when 40 ms have gone by since the last; at the end of its _ready the level says Loading.built(). With no page up these do nothing and take no time. The desert is staged where scripts/desert.gd calls its builders (each thing it stands on the ground, then the wind and the far hills), the test yard between its stations. What cannot be split is a step: the page says what is being made, stands still while it is, and moves on. On a desk that is the desert’s ground (0.3 s), the first hound of the test yard (0.7 s) and the whole of a tomb (0.1 to 0.5 s); a phone will be several times that.

While the page is up Loading.busy is true, and that is the one place to ask. The game is stopped until the level is built; for the frames it then stands under the page it runs, but nothing pressed is passed on (the page takes it all), the Player reads no keys or stick, and a second change asked for is refused. So a tomb’s clock, which starts at his first step, cannot start under the page. A key still held when the page goes works at once.

The game opens with start.tscn, which is nothing but the page with the first level building behind it. Loading is not an autoload: the page is a node it puts under the root itself the first time one is wanted. With Loading.instant set there is no page and no waiting, and the scene is changed as it always was: it is set when nothing is drawn (--headless), and by the tools that work the menu and the editors in a window. The tools that change scene themselves still do.

godot --headless --path . --fixed-fps 60 --script tools/loading_test.gd
godot --path . --resolution 1560x720 --script tools/loading_sheets.gd -- <folder>
godot --path . --script tools/loading_times.gd -- res://desert.tscn [through]

The first goes every way from each level to the others with the page asked for, and checks that it was across before the old level went and until the new one stood ready, that nothing pressed meanwhile did anything, and that a tomb’s clock waited for his first step. The second saves a strip of pictures through each change. The third says where the time goes: on a desk, drawn as the web build draws, the desert took 1.1 s to open before and has the page fully up for 1.3 s now; the test yard 3.8 s and 3.9 s; a tomb 0.4 to 0.5 s and 0.5 to 0.6 s.

Layout

File What it does
scripts/player.gd The controller: movement, jumping, stairs, pushing crates, respawn
scripts/character_rig.gd Animates the model in code: gait, leg IK, a jointed back, hands, looking about, and the swing of hair and hem
scripts/touch_controls.gd Floating stick and jump area
scripts/follow_camera.gd The camera: a fixed side view (the tomb seen from the side) or a third-person orbit (the yard), which with indoors set keeps itself this side of walls and roofs (the tomb walked through)
scripts/desert.gd The desert (desert.tscn): makes the level from its layout, and runs its puzzles, checkpoints and people
scripts/level_layout.gd, scripts/level_editor.gd What a level is made of, as data; and the editor inside the game that changes it
scripts/level_editor_phone.gd The editor’s screen for a phone: a bar, and one sheet or page at a time, all sized for a thumb
tools/editor_test.gd, tools/editor_phone_test.gd, tools/level_editor_shots.gd, tools/touch_audit.gd Not part of the game: work the level editor’s panels for a desk, and its layout for a phone by touch alone; take a picture of every state of either; and measure what there is to touch on a screen
scripts/desert_terrain.gd The desert’s ground: dunes, level ground, and water cut into it
scripts/sand_dunes.gd Dunes as the wind makes them: crescents, ridges, linked crescents, star dunes, and whole fields of them in rows across the wind
scripts/rock.gd, scripts/rock_models.gd Living rock drawn by a shader (beds, weathering, joints, sand on its ledges, scree), and what builds meshes of it; and the rock that was modelled (models/rocks/rocks.glb)
scripts/rock_formation.gd, scripts/rock_field.gd A rock of the White Desert (mushroom, dome, haystack, cone, shelf, dark outcrop); and a field of them in one mesh
scripts/cliff.gd A cliff or a mesa made from numbers: a flat top, a cliff band, scree, buttresses and gullies, ledges to climb, cuttings for doorways
scripts/kings_valley.gd, scripts/tomb_door.gd The Valley of the Kings: hills with a wadi cut into them, the peak, the path, and the tomb doorways; and a doorway, which can lead to a tomb
scripts/desert_horizon.gd, scripts/desert_scenery.gd The far hills round the level; and what makes all of this rock from a level’s layout, paints the ground round it, and puts its rows on the editor’s Level page
tools/build_rocks.py, tools/rocks.blend, models/rocks/rocks.glb Not part of the game: builds the rock models in Blender; the models to look at and change; and what the game reads
tools/terrain_test.gd, tools/terrain_sheets.gd Not part of the game: check the dunes and the rock with nothing drawn; and take pictures of them
scripts/pyramid.gd A pyramid made from numbers: stepped core, smooth casing, gold cap, ruin (fallen corners, rubble, missing and slipped blocks), a doorway; its mesh and what is solid
scripts/sandstone.gd Stone drawn by a shader: blocks in courses, strata, weathering, cracks, rubble; and gold (Toon.sandstone, Toon.gold)
scripts/sphinx.gd A sphinx: a statue to climb on that wakes when he comes between its paws, asks a riddle, is on for good when it is given the thing that answers it, knocks a wrong thing away, and can lie buried in sand until something clears it
scripts/sphinx_mesh.gd Its stone: puts together the things of the model a sphinx wants (the lion, and a king’s, a ram’s or a falcon’s head), at its size and as worn as it is; and what of it is solid
tools/build_sphinx.py Builds the sphinx in Blender (tools/sphinx.blend, models/sphinx.glb): one metre long, separate named things and named materials, no bones
tools/sphinx_test.gd, tools/sphinx_sheets.gd Not part of the game: run the sphinx with nothing drawn and say what happened; and draw it
scripts/temple.gd A temple made from numbers: pylon, courts, halls of columns, sanctuary, enclosure wall; set out as Edfu, Luxor, Kom Ombo or your own; ruin, sand, paint
scripts/temple_parts.gd, scripts/temple_stone.gd Reads the temple’s models and places them, builds its plain walls and floors, and keeps what is solid; the shader all its stone is drawn with (courses, rows of relief, paint, granite), the light through its roof, and the figures cut in its pylon
tools/build_temple.py, tools/temple.blend, models/temple/temple.glb The temple’s parts as models: the script that builds them in Blender, the file to look at and change them in, and what the game reads
tools/temple_sheets.gd, tools/temple_test.gd Not part of the game: pictures of every plan and every model, and what each costs; and a run with nothing drawn that walks him in to each sanctuary and throws him at the stone
scripts/abu_simbel.gd The temples of Abu Simbel, set out from the models in models/abu_simbel/abu_simbel.glb: what is solid, the sand drift, the writing, the dark inside, the sun down the axis
tools/build_abu_simbel.py, tools/abu_simbel.blend The Blender script that makes those models, and the file it saves to look at and change by hand
scripts/heat_mirage.gd Heat over the desert: far things near the level of the eye swim, and pools of sky lie on the far sand
scripts/tomb_parts.gd Torches, pressure plates and stone doors
scripts/test_yard.gd The test yard (test_yard.tscn)
scripts/rope.gd A rope to swing on and climb: a pendulum under whoever is on it, a chain otherwise
scripts/grapple.gd A grappling hook on a coil of rope: finds what it will catch, flies there, hangs a rope from it, is wound back in
scripts/grapple_point.gd Somewhere a hook will catch; marks them on props of its own accord
scripts/ladder.gd, scripts/pool.gd A ladder; a box of water to swim in
scripts/dust.gd Puffs of dust, drawn all at once as one MultiMesh
scripts/train.gd, scripts/train_vehicle.gd A train: vehicles coupled and run along a line, and carrying whoever rides them; one vehicle, whose wheels turn and rods work
scripts/train_scenery.gd, scripts/train_smoke.gd, scripts/train_signal.gd What goes by a train that stands still; an engine’s smoke, steam and sparks; a semaphore signal
tools/build_train.py, tools/build_train_scenes.gd Build the railway’s models in Blender and their scenes in props/
tools/train_sheets.gd, tools/train_test.gd Not part of the game: draw the railway and the boy riding it; ride it with nothing drawn and say what happened
scripts/toon.gd Cel shading: two flat tones for the figures, and optionally the world; a shader for hair; the pinstripe in his shirt
scripts/menu.gd, scripts/settings.gd The in-game menu, which is his notebook, and what it remembers
scripts/notebook.gd, scripts/notebook_ink.gd What the game has written in the notebook’s journal (Notebook.note(...)); and the pencil, ink and paper its pages are drawn with
tools/notebook_test.gd Not part of the game: takes the notebook out, works every page of it with touches and keys in both levels, checks each took effect, and saves pictures
scripts/loading.gd, scripts/start.gd Going from one level to another behind a page of the notebook (Loading.go(...)), with the level building in stages behind it; and what the game opens with (start.tscn), which is that page over the first level
tools/loading_test.gd, tools/loading_sheets.gd, tools/loading_times.gd Not part of the game: go every way between the levels and check the page covered each change and nothing got past it; pictures of each change; and where the time goes when a level is opened
scripts/worn.gd What a figure wears that is not part of its model: the backpack and the helmets (Worn.put_on(figure, &"helmet_anubis"))
scripts/gear_prop.gd A prop whose gold, bronze and brass shine
tools/worn_sheets.gd Not part of the game: draws what is worn on everyone who can wear it, and the boy moving in it
scripts/mummy.gd The mummy: dormant until disturbed, then lurches after the player; within reach it rears back and swipes an arm at him, and catches him only if the arm finds him
scripts/mummy_rig.gd Its own way of moving, laid over the boy’s rig: a step and a dragged leg, the swipe, and its loose bandages swinging as chains; and what the other kinds’ rigs share
scripts/mummy_priest_rig.gd, mummy_brute_rig.gd, mummy_crawler_rig.gd, mummy_child_rig.gd, mummy_royal_rig.gd How each of the other kinds of mummy walks and strikes
tools/build_mummy.py Builds the mummies in Blender: their proportions, the face under the wrappings, a chain of bones down each loose end, and what each kind has of its own
tools/mummy_sheets.gd Not part of the game: drives a mummy of any kind through each thing it does, saves pictures of it, and says which bones shook and whether its feet slid
scripts/medjed.gd Medjed: a shroud with two eyes and two feet who stands and looks from side to side; the boy seen in his gaze, drawn as a fan on the floor, is warned and then smitten. The figure is read from its model; its gaze and its sounds are made here
tools/build_medjed.py Builds Medjed in Blender (tools/medjed.blend, models/medjed.glb): the shroud, each eye with its shape keys, each foot, and named materials; no bones
tools/medjed_test.gd, tools/medjed_sheets.gd Not part of the game: run Medjed with nothing drawn and check each thing he does, and count his rays; and pictures of him and of each of those things
scripts/hound.gd A hound, of either breed: chases the player, leaps obstacles and gaps, bays and barks (recordings of dogs, in audio/hounds/), braces and barks under what it cannot reach, and plays, sits and sleeps when left alone
scripts/hound_rig.gd Animates a hound in code: walk, trot and rotary gallop on three-jointed legs, spine flex, a jaw that keeps time with its voice, a head that watches things, sitting, lying and the bow, a tail on springs, and ears that hang and swing or stand and turn
scripts/fur.gd The hound’s coat: streaked along the lie of the hair, with a few shells stood off it
scripts/cat.gd A cat: wanders, watches the boy, follows him, rubs round his legs, stalks, sleeps in the sun, and goes up out of reach of anything in the group pursuers (recordings of cats, in audio/cat/)
scripts/cat_rig.gd Animates a cat in code, on the hound’s rig: the walk that puts each hind paw in the fore paw’s print, trot and bound, stalking and springing, jumping up and down, sitting, lying, curled asleep, stretching, washing, and a tail, ears, eyes and whiskers that say what it feels
tools/hound_sheets.gd Not part of the game: drives the hounds through each thing they do and saves pictures of it
tools/cat_sheets.gd Not part of the game: the same for the cat
tools/cat_test.gd Not part of the game: runs a cat with nothing drawn and checks that it wanders, watches, flees a hound up onto something and comes down
scripts/birds.gd Birds: a flock of one kind that finds its own perches, ground and water, feeds, goes up when the boy comes near or a gun goes off, wheels, and comes down again; soarers that gather over what lies dead
scripts/bird_kinds.gd, scripts/bird_mesh.gd What each kind of bird is, as numbers; and the mesh built from them, with the shader that beats its wings and bends its neck and legs for a whole flock in one draw
tools/bird_sheets.gd, tools/bird_test.gd, tools/bird_place_check.gd Not part of the game: pictures of every kind of bird and of what each does, and what they cost; a run with nothing drawn that checks they settle, go up, come down, and stay in range and above the ground; and one that puts them into the desert as the editor would
scripts/ba.gd, scripts/ba_mesh.gd The Ba, a bird with a man’s head that leads the boy from place to place, calls him and comes back for him, and comes and goes with a switch; and its model read and made ready to draw
tools/build_ba.py Builds the Ba in Blender (tools/ba.blend, models/ba.glb): separate named things and named materials, no bones
tools/ba_sheets.gd, tools/ba_test.gd Not part of the game: pictures of the Ba and of each thing it does; and a run with nothing drawn that checks them
scripts/apophis.gd Apophis, the great serpent: a head that hunts the boy by sight and by his trail, and a body laid along the path the head took and bent to it by a shader; held off by fire and sunlight, stopped by a shut door, sent home when what let him out goes off
tools/build_apophis.py, tools/apophis.blend, models/apophis.glb Builds his model in Blender: his head on bones, one length of his body, a knife; the same to open and change; and what the game reads
tools/apophis_test.gd, tools/apophis_sheets.gd Not part of the game: run him with nothing drawn and check each thing he does; and pictures of him doing them, with what a frame of him costs
tools/build_boy.py Builds the boy in Blender: one continuous body, modelled in a T-pose, in shirt, patched overalls and curls, and his cap as a separate object
tools/pose_sheets.gd Not part of the game: drives the Player through each thing he does and saves pictures of it
tools/build_hound.py Builds the two hounds in Blender from two lists of measurements
tools/cut_hound_audio.py Not needed to run the game: cuts the hounds’ voices out of the recordings they come from
tools/build_cat.py Builds the two cats in Blender, the way the hounds are built
tools/cut_cat_audio.py Not needed to run the game: cuts the cat’s voice out of the recordings it comes from
tools/build_character.py Holds the tools build_boy.py, build_hound.py and build_mummy.py use
scripts/gate_guardian.gd, scripts/guardian_mesh.gd A keeper of a gate of the underworld: seated, with the head of a beast and knives; asks its name, is on for good when it is said, strikes when it is not; and its model read into one thing to draw for each part that moves
tools/build_guardian.py Builds the gate guardians and the leaf of papyrus in Blender (tools/guardian.blend, models/guardian.glb): the figure in wrappings and in a kilt, the nine heads, the knives; separate named things and named materials, no bones
scripts/book_page.gd, scripts/book_of_the_dead.gd A leaf of the Book of the Dead to find, with the name of a keeper on it; and the names themselves, the drawings of the keepers, and which leaves he has found in this level
scripts/guardian_panel.gd The leaf to read, and the three names to choose from, over the stopped game
scripts/gamepad.gd The gamepad: its actions, which of a gamepad, the keys and a finger was used last, and the marks drawn for its buttons
scripts/pad_focus.gd, scripts/pad_keyboard.gd A gamepad’s mark on a screen made of buttons and sliders (a tomb’s cards, the editors), and letters to choose for a line of writing
scripts/level_editor_pad.gd, scripts/tomb_editor_pad.gd The two editors worked with a gamepad, on a desk
scripts/colour_wheel.gd The colour wheel: a ring of hues and a square inside it, for a finger, a mouse or a gamepad
tools/gamepad_test.gd, tools/gamepad_shots.gd Not part of the game: play everything with a gamepad and nothing else, and say what happened; and pictures of the gamepad’s mark on everything
tools/guardian_test.gd, tools/guardian_sheets.gd Not part of the game: run the guardians and the leaves with nothing drawn, by touch, and say what happened; and pictures of them, of the names on the screen of a phone and of the leaf
scripts/khepri.gd, scripts/akhet.gd Khepri and the sun: a big scarab that rolls a ball of sun over a floor of squares, turning at whatever is in its way; and the horizon it is brought to, which is then on for good
scripts/khepri_court.gd The court Khepri is tried in: the test yard’s station for it, and what its test and its pictures are run in
tools/build_khepri.py Builds Khepri’s beetle, the sun’s ball, the horizon, the swarm’s small scarab and the scarab amulet in Blender (tools/khepri.blend, models/khepri.glb): named things hinged at their origins and named materials, no bones
tools/khepri_test.gd, tools/khepri_sheets.gd Not part of the game: run Khepri with nothing drawn and check each thing it does; and draw it

Tuning values are exported properties on the Player, Camera and TouchControls nodes. Set move_mode to SIDE_SCROLL on the Player to lock movement to a line.

The models

models/boy.glb, models/hound.glb, models/hound_pharaoh.glb, models/mummy.glb and the other mummies (models/mummy_priest.glb, mummy_brute, mummy_crawler, mummy_child, mummy_royal) are generated by Blender scripts (editable copies are saved to tools/*.blend):

blender --background --python tools/build_boy.py
blender --background --python tools/build_hound.py
blender --background --python tools/build_mummy.py

scripts/character_rig.gd reads each figure’s proportions from its skeleton, so the boy and the mummy can differ freely. It also uses whichever of the optional bones a figure has (chest, neck, fingers, hem, hair). The mummy has a back, a neck and fingers, and bones of its own (drape_...) that only scripts/mummy_rig.gd knows about.

The boy’s colours are the MATERIALS list at the top of tools/build_boy.py. A material named hair is given the hair shader (Toon.HAIR_SHADER): strands of differing tone, darker roots, and a broken band of light across the curls. One named hairwavy is given the other (Toon.WAVY_HAIR_SHADER), in the same colour: the strands swing from side to side in long S-curves, the surface is shaded as if it rose and fell with them, and each crest carries one broad band of light. Which a cut of hair has is decided where it is built (CUTS in tools/build_boy.py).

The boy and the townspeople are one model, and what can be chosen for it is in scripts/character_look.gd: faces, cuts of hair, clothes and colours (tools/build_boy.py builds an object for each choice, under “Other turn-outs”).

tools/look_sheets.gd draws all of it (faces, eyes, hair, outfits, skin, random, crowd, menu; see its header).

To see what a change to the model or the rig has done, without playing through it:

godot --path . --fixed-fps 60 --resolution 960x960 --script tools/pose_sheets.gd -- <folder> [hang climb sneak ...]

writes a sheet of pictures for each (see the list in the script’s run); with no names, all of them. Add lo to the names for the demade model. Its window is kept off the screen and takes no input, so it can run while you work. stairs among the names prints how fast he gets up a flight and draws him going up and down two flights (one shallow, one steep) at a walk and a run; jump also draws every other frame of a jump; and rope, kick, swim, ladder, bat, crawl, tired, throw, sprawl, scramble and hatless draw what their names say, most of them as several sheets (swim_in, swim_out, ladder_top, rope_climb and so on). dive, dive_edges, flip, spin, sit, sleep, gun (add revolver, rifle, shotgun or flare_pistol for just that one), wade and stairs_sprint draw the newer moves as strips a few frames apart, and print the edge cases they test. A picture cannot show a joint that shakes, so the same run also watches every bone on every frame and prints, under each sheet, a line SHAKE <sheet> and the bones that turned back on themselves from one frame to the next (shakes), or whose turning changed by more than a few degrees in one frame (jump, a flip if it is near 180), and the frame it was worst on. SHAKE_TRACE=<bone> in the environment prints that bone’s turn on every frame. jump also draws jump_close and jump_round: the knee coming up, from close and from all round. roll draws the two rolls from close to, every other frame (roll_land, roll_dive, and each again from three quarters), and prints a line LEGS for every frame: how high each knee, ankle and toe is off the ground, how far each is from his chest and his head, and whether his ankles have crossed (LEGS_WHERE=1 adds where his head and knees are from his hips). Whatever he does by chance (which knee goes down, how his arms fly) he does the same way each time a sheet is drawn, so two runs can be compared. The hounds’ rig reads their joints from their skeletons too, so the two breeds can differ in any measurement.

The hounds

There are two breeds (breed on the Hound; left at ANY, the hounds of a scene take turns, so a pair is one of each):

They are separate models built by one script from two lists of measurements (tools/build_hound.py), on the same bones. Their legs are three bones and a paw (upper arm, forearm, pastern; thigh, shank, hock), and at a gallop they are clear of the ground twice a stride. Their voices are recordings of dogs (audio/hounds/, credited in CREDITS.md there). They are a little faster than the player; being caught knocks him down as a ragdoll, and that, or falling, restarts the chase.

Until they are let loose they play (bows, barks, jumps aside, a run round each other), then sit, then lie down and sleep; play_time, settle_after and sleep_after on the Hound set how long each lasts. Hunting, a hound that is getting nowhere (he is up on something) drops onto braced forepaws under him and barks. They watch the player first, otherwise each other or anything in the group interest.

Their gaits follow measurements of dogs (the sources are in the comments at the top of scripts/hound_rig.gd). To see them:

godot --path . --fixed-fps 60 --resolution 960x960 --script tools/hound_sheets.gd -- <folder> [walk trot gallop bay bow play sit sleep look fur ...]

Add pharaoh for the prick-eared hound, lo for the demade models, pale for a light coat that shows the shape; the names beginning m_ (m_wag m_ears m_breath m_bark m_gallop m_stop) are strips of consecutive frames, for watching how a thing moves.

The mummies

Mummy.new() is the first mummy. Setting kind before it goes into the scene makes one of the others (mummy.kind = Mummy.Kind.PRIEST); the level editor has it as “Kind” on a mummy, and the test yard has one of each in a row, woken and put back by the one plate. Each has its own model (and a demade _lo), its own rig, and its own numbers (KINDS in scripts/mummy.gd, which sets the mummy’s properties of the same names):

Kind What it is How it walks How it strikes Speed Reach What stops it
SHAMBLER The first: long arms, a hump, loose bandages A falling step and a dragged leg, in lurches Rears, and swipes one arm (every third time both) 1.3 m/s 1.4 m A pit; steps up 0.3 m
PRIEST 2 m tall and gaunt, a long skull, a kilt apron and a red stole Stiff and upright on straight legs, arms crossed, even Bows from the hips and brings both hands down, far in front but only straight ahead; slow to turn 1.35 1.8 Any kerb; will not step down more than 0.5 m
BRUTE Squat and very heavy, knuckles near the ground Waddles: rolls over each foot in turn, stamps Sweeps one arm round level at chest height, so ducking gets under it; every third time both down from overhead 0.95 1.75 Narrow gaps (it is 0.84 m wide)
CRAWLER What is left from the thighs up, trailing its wrappings Hauls itself along on its hands, in surges Throws itself at his ankles: jump over it 2.3 1.6 Any kerb; but it is 0.7 m high and goes under things, and over any edge
CHILD 1 m, a big head, a sidelock Scuttles on its toes in bursts, and stops dead to look Springs at him from close to 2.9 in a burst, about 1.8 over all 1.0 Little: up 0.5 m, down 2 m
ROYAL A gold mask, striped headcloth, broad collar, crook and flail crossed on its chest Glides: slow, level, pointed steps; never hurries With the crook, without stopping; stands off at its length 0.8 2.3 A pit; steps up 0.3 m
blender --background --python tools/build_mummy.py [-- <folder for renders> [mummy_royal mummy_royal_lo ...]]
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/mummy_sheets.gd -- <folder> [turnaround face wake walk swipe dodge drapes stop m_walk m_swipe] [priest brute crawler child royal] [lo]

m_walk and m_swipe are strips of consecutive frames. Under each sheet it prints SHAKE (bones that turned back on themselves or jumped from one frame to the next, as tools/pose_sheets.gd does) and SLIDE (how far the place a foot stood on moved, and how far the ankle was from where it was meant to be).

The cat

Cat.new() is a whole cat (scripts/cat.gd). None is placed in a level yet; to put one in:

var cat := Cat.new()
cat.coat = Cat.Coat.BRONZE   # SILVER, BLACK, RUDDY: the lean temple cat. TABBY, GINGER: the heavier house cat
cat.tame = 0.6               # over a half, it comes and rubs round the boy's legs when he stands still or crouches; under a quarter, it will not be come up to
cat.curiosity = 0.6          # over a half, it follows him about at a distance
cat.position = Vector3(x, y, z)
add_child(cat)

Left alone it wanders within roam of where it was put, sits and watches the boy, washes, now and then stalks something in the group prey or interest and springs on it, and after nap_after seconds finds a place the sun reaches and sleeps for nap_length, waking with a yawn and two stretches. Anything in the group pursuers that comes within fear_distance sends it up onto the nearest flat thing that is higher than the pursuer can jump and no higher than its own jump_height (it looks for one itself; a Marker3D in the group perches points one out), where it stays, bristling and then merely cross, until they have been gone a while. With nothing to get up on it runs. cat.hear(place) turns its ears to a sound. voice = false makes it silent.

There are two models built by one script (tools/build_cat.py: models/cat.glb and models/cat_tabby.glb, and a demade _lo of each), on the hound’s bones plus a back in six pieces, a tail of seven, shoulder blades, toes, eyelids, pupils and whiskers. Their markings are not a texture: the fur shader draws spots, bars, rings and a pale belly from the lie of the hair, where the model says each goes. CatRig (scripts/cat_rig.gd) extends HoundRig and uses its leg solver, paw path, springs and breathing unchanged; the gaits, the back, the postures and everything about its face and tail are its own. The sources it follows are in the comments at the top of that file. To see it:

godot --path . --fixed-fps 60 --resolution 960x960 --script tools/cat_sheets.gd -- <folder> [walk trot bound stalk sit loaf side curl wake groom tails look rub fur flee m_walk m_bound m_pounce m_up m_down m_lash m_blink m_ears ...]
godot --headless --path . --fixed-fps 60 --script tools/cat_test.gd

Add a coat’s name (silver, black, tabby…) or lo to the first. Under each gait it prints how far ahead of the fore paw’s print the hind paw of that side came down.

The camels

Camel.new() is a whole camel (scripts/camel.gd): a dromedary, about 1.7 m at the withers and 2 m at the top of its hump. The level editor places one (“Camel” on the People page: saddled, carrying packs, tethered, couched, and how far it roams), and there are two in the test yard. To put one in by hand:

var camel := Camel.new()
camel.saddled = true                     # a riding saddle on a blanket; `packed` for saddlebags, `haltered` for a rope halter
camel.couched = true                     # it starts couched, and stays so for `rest_for` seconds
camel.tether = Vector3(x, y, z)          # tied to a peg there, on `tether_length` of rope (and so haltered)
camel.position = Vector3(x, y, z)
add_child(camel)
camel.follow(someone)                    # led on its rope at a walk; `follow(another_camel)` puts it in a string behind that one

Left alone it stands, shifting its weight and resting one hind leg and then the other, chews the cud (the jaw goes round sideways), wanders within roam of where it was put, puts its head down to browse (on anything in the group fodder that is in reach, or else at its feet), and after rest_after seconds on its feet kneels and couches for rest_for, dozing if no one is near. It turns its head to the boy within notice_distance, and shies from anything in the group pursuers within fear_distance: up, groaning, and away. A caravan is a string: lead the first, and have each of the others follow the one in front.

It walks and it paces: both legs of one side go forward together, so it rolls from side to side as it goes, and its neck pumps. Flat out it gallops. It gets down forelegs first (onto its knees, rump in the air, then its hind legs fold, then it settles onto its chest) and gets up hindquarters first. The sources for all this, and what is measured and what is only chosen to look right, are in the comments at the top of scripts/camel_rig.gd; CamelRig extends HoundRig and uses its leg solver, foot path, springs and ears unchanged. The model is built by tools/build_camel.py (models/camel.glb and a demade camel_lo.glb) on the hound’s bones plus a neck in four pieces and eyelids; its tack is separate meshes on the same skeleton. Its voice is two recordings of a camel groaning (audio/camels/, credited in CREDITS.md there); voice = false makes it silent.

It is not ridden yet. camel.seat() is where a rider would sit, and mount() / dismount() are there to be written. Like the hounds and the cat, it and the player pass through each other.

godot --path . --fixed-fps 60 --resolution 960x960 --script tools/camel_sheets.gd -- <folder> [turnaround tack walk pace gallop couch browse look led tethered shy fur m_walk m_pace m_gallop m_kneel m_rise m_chew m_idle m_turn ...]
godot --headless --path . --fixed-fps 60 --script tools/camel_test.gd

Add lo for the demade model or bare for no tack. Under each gait it prints when each foot came down in the stride (at a pace the fore foot lands 0.02 to 0.05 of a stride after the hind foot of its own side, and the two sides half a stride apart), how far a planted foot slid, and how far it rolled; and after each m_ strip, any bone that shook or jumped.

The mummified jackals

JackalMummy.new() is a whole one (scripts/jackal_mummy.gd): one of the dogs and jackals that were given to Anubis, dried, bound in linen and stacked in his catacombs at Saqqara, set to guard a tomb. The level editor places one (“Mummified jackal” on the People page: how it waits, what it wears, how near he may come, and what wakes it), and two lie on plinths in the test yard. To put one in by hand:

var jackal := JackalMummy.new()
jackal.rest = JackalMummy.Rest.SPHINX        # lying like the jackal on a shrine; STANDING, as it was propped in a niche
jackal.finery = JackalMummy.Finery.MASK      # a gilded mask and a broad collar; COLLAR; PLAIN
jackal.wake_within = 5.0                     # it wakes when he comes this near (0: only when `wake()` is called)
jackal.position = Vector3(x, y, z)
add_child(jackal)
jackal.caught.connect(...)                   # as a hound's

It lies dormant until then, its eyes dark. Woken, it gets up forequarters first, in jerks, its eyes come alight, and it comes after him. It is not a hound and does not move like one:

  A hound A mummified jackal
Speed 5.4 m/s, faster than he runs 3.8 m/s, slower than he runs (4.4), and it never tires or gives up
Gait Walk, trot, and a gallop with its back gathering and stretching A stalk and a short, quick, stilted trot on legs that hardly bend; its back is a plank; no gallop
Head Up, watching him Low, on a neck stretched out in front, weaving from side to side in starts, jaw hanging and clacking
Catches him By reaching him Only by springing: it sinks back with its jaw wide for 0.4 s (the warning), and springs straight at where he was. Step aside
Obstacles Jumps 1.3 m, leaps 2.8 m Hops a kerb of 0.5 m, crosses a gap of 1.5 m, will not enter water. Up on something he is safe, and it prowls beneath
Hurt Yelps, circles, loses its nerve and runs A blow knocks it aside; enough gunfire (toughness) knocks it down for down_time, and it gets up again
Left alone Plays, sits, sleeps Nothing: it lies where it was put
Voice Barks and bays None (see below)

Several together come at him from different sides, keep apart, and spring in turn.

The model is built by tools/build_jackal_mummy.py (models/jackal_mummy.glb, and a demade jackal_mummy_lo.glb) with the hounds’ tools on the hounds’ bones: a body dried onto its bones, bound in strips that cross at a slant in two tones of linen (the lozenge pattern the embalmers put on animals), its limbs wound round and round, hollow eyes with a point of light in each, a bare jaw, and loose ends of bandage, each on a chain of bones. JackalMummyRig (scripts/jackal_mummy_rig.gd) extends CanineRig (scripts/canine_rig.gd), which extends HoundRig and uses its leg solver, springs and the places it works out for a dog to sit and lie; the loose ends swing as the human mummies’ do.

It is silent. voice = true gives it a dry rasp made from the recordings of hounds panting played at half their pitch, which was made without being heard and is off until someone has listened to it.

The hyenas

Hyena.new() is a whole striped hyena (scripts/hyena.gd): the hyena of Egypt, a scavenger of the dusk. The level editor places one (“Hyena” on the People page: how bold it is, and how far it roams), and a pair hang about in the dunes beyond the jackals in the test yard. To put one in by hand:

var hyena := Hyena.new()
hyena.bold = 0.4                 # 0..1: under a quarter it never rushes him; a very bold one may bring him down
hyena.roam = 12.0                # how far it wanders from where it was put while no one is about
hyena.position = Vector3(x, y, z)
add_child(hyena)

It does not hunt him. It hangs about him at a distance (keep_distance at its most wary, near_distance at its boldest), standing and watching, then going round him at a walk or a lope. Now and then it makes a rush at him with its mane up, breaks off short, and makes away. How near, how often and how close all go by its nerve: its own bold, more when he is down (limp, crawling or crouched) or alone, less when he is near a fire, a lit torch or a flare. It gives ground to anyone who runs at it. A thrown stone that lands by it, a blow, a gunshot it hears, or fire brought up to it sends it off at a gallop, and it keeps well away for shy_time. Only one whose nerve is very high carries a rush through and emits caught. With no one about it wanders and noses at the ground. Put two or three together: they keep apart, go round him different ways, and do not rush at once. There is no clock in the game, so “at dusk” is where and when the level puts them.

The model is built by tools/build_hyena.py (models/hyena.glb and a demade hyena_lo.glb) as the hounds are: 77 cm at the withers on long forelegs, a back that slopes to low hindquarters, a heavy neck and a big blunt head, large pointed ears, a short brush. Its stripes are not a texture: as on the cat, the fur shader draws the bars on its flanks, the bands on its legs and its dark muzzle and throat where the model says each goes. Its mane is a mesh of its own in locks, whose tips three bones lift to stand it on end. HyenaRig (scripts/hyena_rig.gd) extends CanineRig: it walks shuffling, with its head low, and faster it does not trot but lopes, a rolling three-beat canter that rocks it like a rocking horse with its rump tucked under; flat out the lope opens into a gallop. What that follows is in the comments at the top of the file.

Its voice is recordings (audio/hyenas/, credited in CREDITS.md there, which must be kept: they are CC BY). They are of the spotted hyena, no free recording of a striped one having been found: giggles when it rushes and breaks off, and a whoop from a way off. voice = false makes it silent.

To see both animals:

blender --background --python tools/build_jackal_mummy.py [-- <folder for renders>]
blender --background --python tools/build_hyena.py [-- <folder for renders>]
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/canine_sheets.gd -- <folder> [turnaround finery dormant stalk trot pack prowl felled m_rise m_stand m_trot m_stalk m_lunge m_strips h_turnaround h_coat h_walk h_lope h_run h_loiter h_crest h_flee m_h_walk m_h_lope m_h_run m_h_crest m_h_dart m_h_idle] [lo]
godot --headless --path . --fixed-fps 60 --script tools/canine_test.gd

The names beginning m_ are strips of consecutive frames. Under each gait it prints when each paw came down in the stride and how far a planted paw slid (SLIDE), and after each m_ strip any bone that shook or jumped (SHAKE) and the fastest any bone turned in one frame. In the fast gaits the leg bones are listed: they swing back and forth four or five times a second, which is what it counts; none turns more than about 35 degrees in a frame.

The crocodile

Crocodile.new() is a whole Nile crocodile (scripts/crocodile.gd), about 3.4 m long. It lives in water: the Pool it is put in, or the nearest within water_within of where it is put (in the desert, any pond or river). The level editor places one (“Crocodile” on the People page: docile or not, and how far from the water it will come), and there is one in the test yard’s pond, docile until the plate beside the pond is trodden on.

var crocodile := Crocodile.new()
crocodile.docile = true                  # scenery: it floats, basks and watches, and never hunts
crocodile.reach = 5.0                    # how far from the water it will come after him, metres
crocodile.position = Vector3(x, y, z)    # in the water, where it will float; or on the bank, where it will bask
add_child(crocodile)
crocodile.caught.connect(...)            # it has him (the levels knock him down and start him again, as for a hound)

Left alone it floats like a log, at a slant, with only its eyes and its nostrils out of the water, and every so often (float_for) hauls out and walks up the bank to bask on its belly with its mouth open (bask_for), and slides back in. It watches the boy within notice.

When he comes within edge of its water, or into it, it sinks and comes at him under water. What gives it away is the line of rings it draws on the surface as it comes, and in shallow water its back; a few metres off it stops, its eyes come up with a splash, and after wind_up (under half a second) it lunges, up the bank if need be, jaws wide. If its jaws close on him it sends caught, shakes its head, and in the water rolls. It swims faster than he does (chase_speed against his swim_speeds), so the water is not safe while it is in it; on land it is slower than he runs, follows for reach metres or stamina seconds, gives up, walks back and rests. It is in the group pursuers only while it is hunting.

A burning flare, a lit torch, a shot or a blow (shot, struck, as the hounds have) and anything thrown that comes down near it each send it under and keep it there for a while. It is never killed. With no water near it lies about on land round home, and lunges from there.

On land it has three ways of going: the belly crawl (sprawled, one foot at a time, its back and tail bending), the high walk (lifted clear on straightened legs, in diagonal pairs) and a fast belly run for the water or after him. The sources, and the note that none of the numbers is a measurement, are at the top of scripts/crocodile_rig.gd; CrocodileRig extends HoundRig and uses its leg solver and foot path unchanged, with the legs told to fold out to the side. The model is built by tools/build_crocodile.py (models/crocodile.glb, and a demade crocodile_lo.glb): nothing is fused or textured, the belly, flanks and tail bands are materials cut along the hide’s own edges, and the scutes are geometry. Its voice (a hiss, a bellow, the clap of its jaws) is made up in code; voice = false makes it silent. Like the hounds, it and the player pass through each other.

godot --path . --fixed-fps 60 --resolution 960x960 --script tools/crocodile_sheets.gd -- <folder> [turnaround floating bask look hunt m_crawl m_high m_run m_turn m_swim m_dive m_slide m_return m_haul m_lunge m_gape m_thrash m_roll ...]
godot --headless --path . --fixed-fps 60 --script tools/crocodile_test.gd

The stage is a pond with a sloping bank. Add lo for the demade model. Under each gait it prints when each foot came down and how far a planted foot slid, and after each m_ strip any bone that shook or jumped.

Apophis

Apophis (scripts/apophis.gd) is the chase: a very big snake, 12 to 30 m long and half a metre thick, with a head bigger than the boy’s chest. He lies coiled, or out of sight down a hole, until he is let out; then he comes after the boy and does not give up. Apophis.new() is a whole one.

Where he comes from. Apep (Apophis is the Greek of it) is the serpent of chaos, who lies in wait in the underworld every night to stop the sun god’s boat. He is first named in the Coffin Texts of the Middle Kingdom, and the books painted in the royal tombs of the New Kingdom, the Amduat and the Book of Gates, show him in the seventh hour of the night: a huge snake in many coils on a sandbank, bound, and with knives stuck in him. Set spears him from the prow of the boat; in the Book of the Dead (spell 17, and the tomb of Inherkha at Deir el-Medina) Ra as a great cat cuts him with a knife under a tree. A papyrus now in London (Bremner-Rhind) is a whole ritual for overthrowing him: he is spat on, trampled, speared, cut and burnt in wax, every day. And he is there again the next night: he is never killed. So what is true to that here is what he is and how he looks (one enormous snake, yellow, in coils, knives in him), that light and fire drive him back, that he cannot be killed, and that he is back the next time. What is ours: that he is after a boy and not the sun, that a hand torch is light enough, how he finds his way, and the hole. His markings are made up too; the paintings give him none to speak of.

How he is made. His head is this node, and is what the world stops. Behind it is kept the path the head has taken, and his body is laid along that path: every part of him passes where his head passed, round the same corners, over the same kerbs and up the same faces, with a side-to-side wave over it that stands still on the ground while he slides through it. At rest the path is a coil, so he leaves it as a snake does, sliding out along himself; called home, he winds himself back in. He is drawn as one tube, which its shader bends along 80 places on the path: nothing is built each frame but those 80 places.

What he looks like is a model, models/apophis.glb, built in Blender by tools/build_apophis.py, which also writes tools/apophis.blend to open and change. In it are his head (on bones: head, jaw, tongue, fangs), one length of his body (1.15 m of it, a saddle and the plates under it), and a knife. There is no picture on him: his colours are materials, a face at a time (back, mark, edge, belly, belly_shade, mouth, tooth, tongue, eye, pupil…). The game reads the model when he is made: it lays the one length end to end for as long as he is, thins it at his neck and down his tail, and makes each of the three one thing to draw, with the materials’ colours on its points. The bones are not used as bones: which bone a point hangs from tells the shader that it goes with the jaw (which drops), is the tongue (which flicks), or is a fang (which is drawn out as the jaw drops). So to change how he looks, change the .blend (a material’s colour, which faces have it, the shape of his head, where a bone starts) or the numbers at the top of the script, and build him again:

blender --background --python tools/build_apophis.py [-- <folder for pictures of the model>]
godot --headless --path . --import

(A model changed by hand in Blender is exported from there as models/apophis.glb, glTF binary, +Y up, with its skin, and without applying the Array modifier that shows a length of him. The body’s pattern must meet itself end to end. He has no demade model: he is about 4000 triangles as he is.)

The chase.

What stops him, and what does not.

In the level editor he is “Apophis” on the People page: his length, his speed, how near the boy wakes him (0: only what he is linked to does), whether he waits coiled or down a hole, whether he fears light, and “Let out by”. The test yard has him at the east end of its north side, coiled at the end of forty metres to be chased along: a torch to pick up, a kerb, a pillar and a block, a ledge, a brazier, and a passage whose gate the plate beyond it shuts.

godot --headless --path . --fixed-fps 60 --script tools/apophis_test.gd [-- trace]
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/apophis_sheets.gd -- <folder> [model head wake hole chase behind kerb ledge torch strike door home daylight cost yard]

The first runs him with nothing drawn and ends PASSED or FAILED: each thing above, and that no part of him is ever under the ground nor his head through a wall. The second draws him (add --rendering-method gl_compatibility to see him as the web build does). A frame of him costs about 0.02 ms to move and 0.06 ms to lay his body (on a desk), and he is 3000 to 6200 triangles, by his length, in three things to draw (body, head, knives) and their shadows.

The birds

Birds.new() is a flock (scripts/birds.gd): so many of one kind, living within so far of where it is put.

var doves := Birds.new()
doves.kind = Birds.Kind.DOVE     # SPARROW, DOVE, HOOPOE, SWALLOW, IBIS, HERON, EGRET, GOOSE, KESTREL, KINGFISHER, VULTURE, GRIFFON, KITE
doves.count = 14
doves.roam = 25.0                # how far from here they go
doves.position = Vector3(x, y, z)
add_child(doves)

In the level editor they are “Birds” on the People page (which kind, how many, how far they range). The test yard has a station of them (doves and sparrows to run at, posts and a broken column for them to go up onto, a hoopoe, a kestrel), waders, geese, a kingfisher and swallows at its pond, and vultures and kites over it.

The kinds were chosen from the birds of the Nile valley as they were in the boy’s day and as tombs show them: Charles Whymper, Egyptian Birds (1909), which is of the boy’s own decade and has plates of nearly all of these; the birds of the marsh scenes of the tomb of Nebamun and the geese of Meidum; and the gods’ birds (the ibis of Thoth, the falcon of Horus, the vulture of Nekhbet, and the Egyptian vulture and the hoopoe that are hieroglyphs).

godot --path . --resolution 640x480 --script tools/bird_sheets.gd -- <folder> [kinds beat acts takeoff flock flush water soar far yard cost] [sparrow heron ...]
godot --headless --path . --fixed-fps 60 --script tools/bird_test.gd

Demade models

The same scripts also build low-poly, flat-shaded versions (models/boy_lo.glb, models/hound_lo.glb, models/hound_pharaoh_lo.glb) on the same skeletons. Switch to them from the menu.

Ragdoll

Player.ragdoll(impulse) drops the body as jointed rigid bodies from whatever pose it is in; Player.recover() stands it back up where it lies, and respawn() also ends it.

Guns

Four guns of the 1910s, each a scene in guns/ to drag into a level or instance in code, and each a Gun (scripts/gun.gd): a RigidBody3D in the groups throwable, interest and guns, its origin at the grip, its barrel along -Z. Whoever holds one calls gun.fire(self, aim); the gun does the rest (works its own action, reloads from its spare rounds when the trigger is pulled on nothing, and says so with the signals fired, cycled, reload_started, reloaded, dry_fired).

Scene What it is Holds / spare Between shots Reload Damage Kick
revolver A break-top service revolver 6 / 18 0.45 s 2.6 s 35 0.5
rifle A bolt-action magazine rifle (two hands) 10 / 30 1.1 s 3.2 s 80 1.0
shotgun A double-barrelled hammer gun (two hands) 2 / 12 0.35 s 2.4 s 8 pellets of 12 1.4
flare_pistol A brass signal pistol: fires a Flare that flies, bounces, and burns for nine seconds, lighting a room 1 / 6 0.4 s 1.8 s 15 0.35

They are modelled by tools/build_guns.py and made into scenes by tools/build_gun_scenes.gd, which also holds each gun’s numbers:

blender --background --python tools/build_guns.py [-- name ...]
godot --headless --path . --import
godot --headless --path . --script tools/build_gun_scenes.gd
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/gun_sheets.gd -- <folder> [looks fire reload rounds targets flare] [revolver ...]
godot --headless --path . --fixed-fps 60 --script tools/gun_test.gd      # every gun fires, runs dry, reloads, hits things
godot --headless --path . --script tools/gun_audio_check.gd              # length, peak and level of every recording

The railway

An engine and tender, two carriages, six kinds of goods vehicle, track, and a halt with what stands by a line (PROPS.md, “The railway”, lists them; the level editor’s Railway page places them). A Train (scripts/train.gd) couples vehicles and runs them along a line or a Path3D: wheels and rods turn, the bodies rock, the chimney smokes. He boards from a platform, goes through a carriage or up the ladder at its end and along the roofs, jumps the gaps between vehicles, and must duck under a low bridge or be knocked down. A ride is staged either with the train really moving or with the train standing still and the world going by it (TrainScenery); the test yard’s halt has a plate for each.

godot --path . --fixed-fps 60 --resolution 960x960 --script tools/train_sheets.gd -- <folder> [vehicles train dusk moving rods ride world lineside yard]
godot --headless --path . --fixed-fps 60 --script tools/train_test.gd

Web build

godot --headless --path . --export-release Web build/index.html

build/ is committed and served by GitHub Pages at https://sclondon.github.io/Outside/build/index.html

Fire and the torch

Fire (scripts/fire.gd) is made of particles: a few dozen soft licks of flame, each lit pale at the foot of the fire, carried up, cooling through orange to dark red and coming apart, each showing the frame for its age from an animated sheet of flame pictures (textures/fire_sheet.res, 8 x 8 frames of flame in red and of smoke in green, painted by tools/bake_fire_sheet.gd; run that again to change how a lick looks). A carried fire streams out behind (streams). Burning oil draws its flames with the same shader (Fire.flame_shader()). Smoke is soft puffs from the same sheet; sparks stream up and wink, and now and then it spits a handful of embers that fall (sparks, spits); it glows (glow, glow_reach: a soft light drawn in the air round it); and the light it casts gutters (flicker) and reaches now further and now less far (breathing). tools/fire_sheets.gd -- <folder> draws each kind by day and in the dark, a few frames apart.

A HandTorch (scripts/torch.gd) is a burning torch to carry: act picks it up, and he holds it up in front of him as he goes; act throws it, duck and act puts it down, and it goes on burning where it lies. There is one by the fire in the test yard, and “Torch to carry” is on the level editor’s Puzzle page.

Oil

Lamp oil, to spill and to set light to (scripts/oil.gd, oil_jar.gd, oil_spill.gd, oil_mark.gd). Nothing new to press:

In the level editor’s Puzzle page: “Oil jar” (how much it holds), “Oil spill” (oil already poured: a trail of a length and width along the way it faces, that can bend; with no length, a puddle) and “Fire dish” (a soot-black stone dish in the floor). All three are triggers, to be touched when a door or anything else is asked what works it: a jar when it is broken, a spill when the fire has reached its far end, a dish from the moment oil burns in it. The test yard has a station out to the east: two jars, a torch and a campfire, a fuse under a cobweb to a heap of jars, and a dish that opens a gate.

For other code:

Oil.is_burning_at(point, 0.5)        # is oil burning within half a metre of there? (static: ask from anywhere)
Oil.is_flame_at(tree, point, 0.5)    # that, or a Fire (torch, brazier, campfire) or a flare
var oil := Oil.of(self)              # the level's oil (made when first asked for)
oil.pour(from)  oil.spill(at, 40)  oil.lay(from, to)  oil.ignite(at)  oil.douse(at)  oil.clear()

Oil is in the group flames and its burning says whether any of it is alight. To be lit by burning oil, be in the group kindling and have kindle(by): it is called, several times a second, while oil burns within 0.3 m of the node’s origin. (Cold torches, in torches, are lit without it.) Wherever oil burns a Fire stands on it, so anything that already minds a Fire (Nearby.fires) needs nothing more.

It is cheap: at most 320 patches of oil in a level (the oldest scorch gives way to new oil), drawn as three draws of one MultiMesh however many there are, one more for all the flames, and at most six whole fires, three of them casting light, moved to where the burning is. With all 320 alight it costs 0.04 ms a frame to work out and 0.13 ms to draw.

godot --headless --path . --fixed-fps 60 --script tools/oil_test.gd
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/oil_sheets.gd -- <folder> [stone] [yard]

The first checks all of the above with nothing drawn and ends with PASSED or FAILED. The second draws it: puddles on stone by day and by torchlight, a trail burning, a heap of jars bursting, and in the test yard the fuse on sand and the boy carrying a jar and pouring. Add --rendering-method gl_compatibility to see it as the web draws it.

Not done: oil is not slippery, does not float on water, and does not burn through wood or rope.

The dig, the backpack and the helmets

The tools and furniture of an excavation of the 1910s are props (PROPS.md lists them; the level editor’s Dig page places them): a pick, a shovel, a turia and a khopesh to swing, a trowel, brush, tape, lantern and basket to pick up, and a sieve, barrow, level, plumb line, poles, crates and a camp table to dress a site.

A backpack with a bedroll, and six helmets (Anubis the jackal, Horus the falcon, Sobek the crocodile, Bastet the cat, Thoth the ibis, Khnum the ram), are worn: Worn (scripts/worn.gd) puts one on anything that stands on the boy’s rig, in one line, Worn.put_on(figure, &"helmet_sobek"), and the dresser has a row for each on the boy. See “What is worn” in PROPS.md.

A GrappleHook (scripts/grapple.gd) is a grappling hook to carry; its station in the test yard has a gap between two decks to swing across and two gallows to swing from. A level says what a hook will catch in any of these ways:

add_child(GrapplePoint.new())                      # a node where the hook is to bite (`ring` shows an iron ring there)
GrapplePoint.mark(beam, Vector3(2.4, 0.9, 0.0))    # the same, on something, in its own space
GrapplePoint.auto(prop, "lintel")                  # wherever that kind of prop holds one (`GrapplePoint.HOLDS`)
anything.add_to_group(&"grapple_points")           # or any Node3D at all

Levels made in the editor get the third for nothing: every lintel, scaffold and awning holds a hook at both ends, every column, obelisk, statue and palm at its top, and any Marker3D named Grapple... in a prop’s scene is one. “Grappling hook” and “Grapple point” are on the editor’s Puzzle page. tools/grapple_test.gd runs the whole of it with nothing drawn (ends PASSED or FAILED); tools/rope_numbers.gd prints how a rope swings: how surely it is caught, how the swing grows when pumped and dies when left, what he carries off it at each point of the swing, and what climbing does to it. rope and grapple among the names given to tools/pose_sheets.gd draw both.

Scarabs

ScarabSwarm.new() is a whole swarm (scripts/scarab_swarm.gd): a hundred or two beetles that pour out of a hole (the node is the hole), run over the ground as a carpet, up and over anything lower than climb (0.7 m), and go for the boy. They are film scarabs, 10 cm long (beetle_size), modelled on the sacred scarab (scripts/scarab.gd: domed wing cases, the shield over the thorax, the toothed head shield, six legs that scurry), black with a blue-green sheen.

var swarm := ScarabSwarm.new()
swarm.count = 150
swarm.alert_distance = 8.0      # they come out by themselves when he is this near (0: only when let out)
swarm.position = the_hole
add_child(swarm)
swarm.caught.connect(...)
swarm.chasing = true            # out; `false` calls them home; `reset()` puts them back at once

While they are out, swarm.front (the nearest of them to him) is in the group pursuers: the boy watches it and the cat and the camels keep away. With harmless = true it is a few beetles (count) that wander within roam of where they are put and run from his feet, and with dung_ball one of them rolls a ball backwards, head down.

The beetle is a Blender model: the object scarab in models/khepri.glb, built by tools/build_khepri.py with Khepri (see there for how to build it again; tools/khepri.blend is the one to open). It is one unit long, and the game makes it the size it wants. Because there are so many, three things about it are not as free as in other models. Its legs are moved by a shader, which knows a leg by the mesh’s UV map: U is how far down the leg a point is (0 at the hip and all over the body, 0.45 at the knee, 1 at the foot) and V which three legs it steps with (1 or 0.5 in Blender). Points moved keep theirs, and points made from them take it; a leg built from nothing has to be given it. It is all one colour, the shader’s black: its four materials (scarab_shell, scarab_thorax, scarab_head, scarab_leg) say only how much each part shines, by their Roughness (0: wholly). And it should stay near its 124 triangles. Its normals are set by hand in the build script (the head and legs are lit as flatter than they are), so it is point for point the beetle the code used to make.

There is no body to a beetle. All of them are one MultiMesh (124 triangles each), moved in one loop and written to it in one piece; their legs are moved by the shader. The ground is found with a ray straight down, once for each 0.2 m square round the nest as a beetle first comes to it (28 a frame at most), and remembered; they keep apart by counting themselves into the same squares. Two hundred of them held in a ring by a torch cost about 0.5 ms a frame on a desk machine (50: 0.13 ms; 100: 0.26 ms; much the same running over new ground), and it is one draw call.

A ScarabAmulet (scripts/scarab_amulet.gd) is a scarab of gold and lapis the size of a hand, picked up and thrown like a rock; a ScarabSocket (scripts/scarab_socket.gd) is a stone with a hollow for it, and is a pressure plate that only the amulet presses, so it opens whatever a plate opens. The amulet is the object scarab_amulet in the same model, one unit long: what is amulet_gold there is drawn in the game’s gold in that colour, and what is amulet_stone in the amulet’s own stone colour.

The level editor has “Scarab swarm” (how many, how far they chase, whether fire holds them off, how near he comes before they come out, and what lets them out) and “Scarabs (harmless)” on its People page, and “Scarab amulet” and “Scarab socket” on its Puzzle page. The test yard has a station for them, north-west of the grappling hook.

scripts/nearby.gd (Nearby.fires(tree), Nearby.player(tree)) is how the scarabs and the cobwebs find every fire that is burning, and the boy, without either having to say it is there.

godot --path . --fixed-fps 60 --resolution 960x960 --script tools/scarab_sheets.gd -- <folder> [beetle pour step chase torch fire caught harmless webs far tear burn cost yard]
godot --headless --path . --fixed-fps 60 --script tools/scarab_test.gd

The first draws all of it (strips are consecutive moments) and, with cost, prints what a frame of the swarm costs for 50, 100 and 200 beetles. The second runs it with nothing drawn and ends PASSED or FAILED: they come out, go for him, are held off by a torch, catch him without one, give up when he is up on a block or across water or too far, go over a step, and come out when he comes near; and a web tears and a web burns.

Khepri and the sun

Khepri.new() (scripts/khepri.gd) is a big scarab of lapis and gold, 0.45 m long, rolling a ball of sun 0.7 m across: backwards, head down, its hind legs on the ball, as the harmless one of the swarm rolls its dung. The sun is a real light, so a dark room is lit as it comes, and it turns as far as it travels. Akhet.new() (scripts/akhet.gd) is where the sun is to be brought: two low hills of sandstone with a cradle of gold between them.

The model. The beetle, the ball and the horizon are a Blender model, models/khepri.glb, built by tools/build_khepri.py, which also keeps tools/khepri.blend to look at and change by hand (the small scarab of the swarms and the scarab amulet are in it too: see Scarabs). To build it again and have the game take it up:

blender --background --python tools/build_khepri.py
godot --headless --path . --import

(Changed by hand in tools/khepri.blend instead, it is exported from there as glTF to models/khepri.glb, +Y up; running the script again would write over that.) What is in it, each thing named for what it is:

In the model What it is
khepri An empty: the beetle is everything under it. It stands as it sits on a hill when the sun has risen, wing cases open and wings out, facing the game’s +Z (Blender’s -Y)
body The gold shield over the thorax, the dark back under the wing cases, the belly
head The head shield with its six teeth, and two eyes of lapis
wing_case_left, wing_case_right The wing cases (one mesh, turned over for the right). The origin is the hinge; how it is turned in the model is how far it opens. Shut, it is not turned at all
wing_left, wing_right The wings (one mesh): a fan of seven feathers in bands. The origin is the root; how it is turned is how it is held when it is out
leg_fore_left, leg_mid_left, leg_hind_left, and _right The six legs (three meshes). The origin is the hip, which the game swings the leg about; its lowest point is its foot
sun The ball. Its colours and the tongues of fire on it are drawn by the game’s shader, not taken from the model
akhet An empty: the horizon is everything under it
ground, hill_east, hill_west, cradle The slab with its strips of gold, the two hills (one mesh) with a band of gold at the foot of each, the dish of gold and its ring

The beetle has no bones in the model. The game (Khepri._read) makes one mesh of everything under khepri and gives each thing a bone of its own at its origin, so moving an origin moves the joint, and nothing has to be weighted. Its colours are the model’s materials (lapis, gold, gold_dark, and the paints of the wings, wing_gold, wing_lapis, wing_turquoise, wing_red, each with a _shade for every other feather): change a Base Color, export, and the beetle changes (sixteen colours at most). A material’s Roughness is how much it glints (1: not at all), and a material with Emission is drawn bright of itself and not lit, as the wings are. The horizon is drawn in the game’s own sandstone and gold, in the colours of the model’s hill_stone, ground_stone, horizon_gold and cradle_bronze. The beetle is modelled 0.45 m long and beetle_length makes all of it bigger or smaller.

What is still made in the code, and why: the sun’s glow and its light, and the column of light it stands on in the horizon (they are light, sized as the sun rises); the fire on the ball (a shader, so that it is seen to roll); how the legs step, the wings beat and the cases open (from the hinges the model gives); the sounds; and what is solid: the ball is a sphere of Khepri.SUN and each hill a box from Akhet.HILL, so a ball or a hill reshaped in the model looks different but is solid where it was. The beetle sits on its hill at Akhet.perch(), likewise.

Where it comes from. Khepri is the sun at dawn. The Egyptians watched the sacred scarab roll its ball of dung over the ground and saw in it what moves the sun over the sky; and because young beetles seemed to come out of the ball of themselves, the scarab is the sign for kheper, “to come into being”, and the god’s name means “he who comes into being”. In the books painted in the royal tombs the night’s journey ends with a scarab pushing the disc up out of the eastern horizon. The horizon, akhet, is written as the sun between two hills, which is what an Akhet is. Winged scarabs of gold and lapis holding up the sun are among the commonest jewels of the dead, and that is what the beetle is dressed as. How true the game is to it: the beetle is. A dung beetle does push backwards with its hind legs, head down; does keep a straight line; and does climb onto its ball and turn about on top of it, to take its bearings from the sky. In the paintings Khepri holds the disc before him in his fore legs: the game follows the beetle, not the paintings. That it turns right at a wall, that it can be steered with blocks, and that the horizon is a cradle that opens doors, are the game’s.

How it moves is simple, and can be learned by watching, like a wind-up toy on a floor of squares:

So the boy steers the sun through a room by pushing blocks into its way, standing in its way, opening and shutting doors on it, and working what lets it go. He can also turn it by hand: the act button beside it turns it a quarter turn (at once if it is stopped, at the next square if it is rolling). He cannot pick it up or push it: walking into it, he stops against it. If something comes into its way while it is between two squares it goes back to the one it left and thinks again. If he stands on it, it waits. It cannot be hurt and nothing chases it.

The horizon. When the sun comes to the cradle of an Akhet the beetle lets it go: it rises between the hills on a short column of light and stays, the room is lit for good (its light reaches nearly twice as far), and the Akhet is on, for good: it is a thing that is on or off like a pressure plate, so a door or anything else is linked to it. The beetle flies up onto a hill and sits there with its wings out. An Akhet takes three squares in a row (a hill, the cradle, a hill: the hills are solid) and the sun comes in along its Z, from either side; stand it on the sun’s squares.

What the sun does on the way, from being a light and being heavy:

reset() puts it back where it started, and an Akhet’s reset() turns it off and sends its sun home. Moved in the level editor, it starts from where it is put.

var sun := Khepri.new()
sun.turns = Khepri.Turns.RIGHT
sun.position = the_first_square      # and turned to face the way it is to set off
add_child(sun)
var horizon := Akhet.new()
horizon.position = sun.place_of(Vector2i(4, 2))
add_child(horizon)
horizon.changed.connect(func(on: bool) -> void: door.is_open = on)
lever.changed.connect(func(on: bool) -> void: sun.running = on)

The level editor has “Khepri and the sun” (which side it turns to, its pace, the size of a square, whether it starts itself, whether it turns back from a dead end, and what lets it go) and “Horizon (akhet)” (how high the sun rises; choose a door and then the horizon, as with a plate) on its Puzzle page. Where the ground is not level it goes as far as a slope of one in four and no further, so in the desert give it level ground.

The test yard has a station for it in the far south-west corner, beyond the jackals: a walled court with a floor of squares (scripts/khepri_court.gd, which says how it is done). The lever by the way in lets the sun go; left alone it goes round by the walls, through a dark alcove, past a sun disc and over a plate; push the two blocks onto their marks and it turns twice and comes to the horizon, which opens the door. The plate outside puts it all back.

It is one draw for the beetle, wings and all (about 900 triangles on a skeleton of twelve bones), one for the sun, one for its glow, and one light without shadows; the horizon is one mesh of four materials. What it asks of the world it asks only at the middle of a square (for each way it tries, six rays down and a shape swept along it) and once a step while it rolls (the shape again).

godot --headless --path . --fixed-fps 60 --script tools/khepri_test.gd
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/khepri_sheets.gd -- <folder> [model camera roll dark sunrise akhet court yard]

The first runs it with nothing drawn and ends PASSED or FAILED: it rolls straight at its pace, turns at a wall, the other way, back from a dead end, waits shut in, is turned by the act button, is turned and stopped by the boy, keeps off drops and out of water, goes over a low kerb and not a high one, presses a plate, lights a disc, is feared by scarabs; in its court it never finds the horizon alone, and does with the blocks pushed (by the boy too), the same way square for square each time; and it is made, linked, kept and moved in the desert as the editor does it. The second draws it (add --rendering-method gl_compatibility for the web’s renderer).

The Ba

Ba.new() is a Ba (scripts/ba.gd): the part of a dead man that flies out of his tomb by day, which the Book of the Dead draws as a bird with his own head. Here it is a friend, the spirit of whoever lies in the tomb, and it shows the boy the way. It is a falcon’s body half a metre high, pale, with painted wings (turquoise, a line of gold, long pale feathers tipped with blue), a man’s head in the striped headcloth, and two small arms that it holds up when it calls. It shines a little, with a soft light round it and a small one that reaches a couple of metres, so it is seen in a dark tomb. That light is not a fire: nothing that looks for fires finds it.

var ba := Ba.new()
ba.route = [first_place, second_place, the_last_place]   # in the world; or give it Marker3D children
ba.position = where_it_waits
add_child(ba)
ba.arrived.connect(...)     # it has landed at somewhere it was leading him to
ba.finished.connect(...)    # he has come to the last place with it
ba.lead_to(the_door)        # or: one thing, or one place, to lead him to from wherever it is
ba.show_way(points)         # or: fly these places once, look back, and come back (`came_back`): a hint
ba.on = false               # it flies off and is gone; true, and it flies in out of the air

In the level editor it is “Ba (spirit bird)” on the People page. Its way is a line of points, as a river’s is: drag each, add more with Points, take the last away (down to the one it sits at, which leaves it only there). It has Shy, how near he comes before it flies on, how far ahead it flies, how long before it comes back for him, whether it goes at the end, and “Called by”: link it to a plate, a lever or anything else that is on or off and it is not there until that is on (and, unless “Stays once it has come” is off, stays after). With nothing linked it is there from the start.

The test yard has a station for it, out beyond the scarabs and the grappling hook to the north (about -4, -76): one to follow round a wall to a block, a shy one on three posts, and two plates, one that calls a third out of the air and one that has it show the way round the wall.

godot --path . --fixed-fps 60 --resolution 960x960 --script tools/ba_sheets.gd -- <folder> [model wings far flight lead fetch appear shy show dark yard]
godot --headless --path . --fixed-fps 60 --script tools/ba_test.gd

The first draws it (strips are consecutive moments). The second runs it with nothing drawn and ends PASSED or FAILED: it waits, flies when he nears, lands ahead where he can see it, goes round a wall and under a roof, calls and comes back when he hangs back or strays, ends at the last place, comes and goes with its switch, goes as a spirit out of a shut room, flits when shy, shows a way, is made and linked in the desert as the editor makes it, and is never inside anything solid or under the ground.

Cobwebs

Cobweb (scripts/cobweb.gd) is old web for tomb tunnels, in four sorts (kind):

Fire burns them: a torch held within catch_distance (0.4 m), a brazier or a flare sets one alight where it touched, and it burns away from there in under a second behind a glowing edge, and sets light to any web that touches it.

Each is a few dozen triangles and one draw call, with nothing to load: the threads (spokes out from a hub, finer threads sagging between them; for what hangs, threads running down) are drawn by the shader, with a veil of dust over them. A thread narrower than a dot of the screen is drawn fainter instead of thinner, and far enough off the threads give way to the even grey they average to, so a web does not shimmer in the distance. It is lit from both sides by whatever light there is, so a torch carried past picks it out.

The level editor has “Cobweb: corner”, “Cobweb: across a passage”, “Cobweb: hanging” and “Cobweb: draped” on its Stone page, each sized by sliders. The test yard has a short stone passage hung with them at the scarabs’ station.

Medjed

Medjed.new() is a whole one (scripts/medjed.gd), facing along his own +Z: a sentinel who cannot be hurt and does not chase, and whose look is what has to be kept out of.

Where he comes from, and how true this is. Spell 17 of the Book of the Dead has one line about him: “I know the name of that smiter among them who belongs to the House of Osiris, who shoots with his eye, yet is unseen.” His name, Medjed, means the smiter. That line is nearly all that is known. The Greenfield papyrus (the Book of the Dead of Nestanebetisheru, of about 950 BC, in the British Museum) draws him, twice: a plain dome like a sheet over a post, two eyes looking out of it, two bare feet under the hem. So what is his own here is the look of him (kept as it is drawn: the off-white shroud 1.5 m tall, the almond eyes with their dark line and heavy brows, the feet, and nothing else), that he strikes with his eye, and that he can be unseen. The rest is made up for the game: that he stands guard and looks from side to side, that there is a moment’s warning, that he comes shuffling to look, that a plate or a mirror shuts his eyes. Nothing old says how his look works, or that anything turns it back.

var medjed := Medjed.new()
medjed.sweep = 120.0
medjed.follows = true
medjed.position = his_post
add_child(medjed)                 # he finds the boy himself (`Nearby.player`)
medjed.caught.connect(...)
medjed.worked = plate.pressed     # shuts his eyes; `reset()` puts him back at his post

What he costs: two rays in a step of the game at the very most. While the boy is in the cone, one to his chest, and one to his head if that one gets through; otherwise one spoke of the fan, each of the eleven in its turn (so the fan on the floor is about a sixth of a second behind him). Far from the boy (45 m past his reach) he casts none and draws no gaze. He is 456 triangles of shroud, 148 of eyes and 250 a foot, and six draws with the gaze.

The level editor has “Medjed” on its People page: how far he looks round and how fast, how far he sees, the warning, unseen, whether he comes to look and how far, and “Eyes shut by” (with “Shut until then” for the other way about). The test yard has a station for him out to the east, beyond the light and mirrors: two lanes with a wall between. In one he stands at the far end with pillars, a block to push, a mirror, a plate that shuts his eyes while it is stood on and a lever at his feet that shuts them for good; in the other is one that is unseen and comes to look, as far as a kerb.

godot --headless --path . --fixed-fps 60 --script tools/medjed_test.gd
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/medjed_sheets.gd -- <folder> [model eyes sweep smite cover unseen follow mirror side yard]

The first runs him with nothing drawn and ends PASSED or FAILED: he sweeps and waits; the boy in the open is smitten after the warning and not before; behind a block he is not, nor ducked behind a low one; he runs from block to block while Medjed looks away; a mirror shuts his eyes; one that follows comes, no further than he may and not over a kerb, and goes home; and in the desert, made as the editor makes him, a plate shuts his eyes and off it the boy is caught. The second draws him (by day and in the dark by a torch; add --rendering-method gl_compatibility for the web’s).

In the tombs he keeps a way that has no door (see “Tombs”): he stands at the far end of a plain stretch and looks back down it, and does not turn. In the tomb’s side view the fan is seen nearly edge on: it shows as a pale band along the floor and the two lines over it, which is enough to see where he looks and how far, but not how wide.

The sphinx

Sphinx.new() (scripts/sphinx.gd) is a sphinx of sandstone: a lion lying on a low base with its forepaws out in front, and on its shoulders a king’s head in the striped headcloth, a ram’s, or a falcon’s (head). It is a statue, and it is alive when it wants to be.

What it can ask for (wants), and the riddle that goes with each (riddle puts other words in its mouth and on its stele; the thing wanted stays the same):

It wants Its riddle
A burning torch I eat wood and am never full. I drink water and I die. Bring me, living, on a stick.
A torch that is out Yesterday I was a sun in the hand. Today I am black and cold. Bring me the stick that waits for fire.
The scarab amulet Khepri rolls the sun up out of the night, as a beetle rolls its ball. Bring me Khepri, in gold and blue stone.
The iron, bronze or gold key I have teeth and bite nothing. I open what is shut. Bring me the black one, of iron. (…the brown one, of bronze; …the one that shines like the sun.)
A canopic jar I wear a head for a lid. I keep safe what a dead man will want again.
A jar of oil The lamp drinks me. Poured out, I am a black road for fire to run along. Bring me in my jar, unbroken.
A stone to throw I am older than the kings. I have no wings, yet a boy’s arm makes me fly.
A pot I was river mud until the fire made me hard. I am all mouth and belly, and my belly is empty.
A shovel I have one iron tongue and a long wooden neck. I eat sand all day and am never fed.
A pickaxe I have two horns of iron and one leg of wood. Where I strike, stone opens.
A hand brush I have a hundred hairs and no head. I wake old kings gently, a grain at a time.
A lantern I am a house of glass with a roof of tin. A flame lives in me and the wind cannot find it.
A basket of spoil I was the leaves of a palm. Now I carry the desert away, one load at a time.
A khopesh I am bronze, and bent like the new moon. The king holds me over his enemies.
The grappling hook I have iron claws and a tail of rope. I climb first, and you come after.

Sphinx.what_is(thing) is the name it knows a thing by (a torch is torch or cold_torch as it burns or not, so a torch can be lit to make it right); anything else can be given a name with the meta sphinx_thing. A thrown jar of oil breaks on whatever it hits first, so that one is put down (duck and act).

In the level editor it is “Sphinx” on the Stone page: its length, head, how weathered, painted, what it wants, its own riddle, buried, opens itself, wrathful, and its stone. Choose a door and touch the sphinx as what opens it; choose the sphinx and “Sand cleared by” to say what uncovers it. (The “Great sphinx” beside it is the fixed model, 25 m long, that was called “Sphinx” before.) The test yard has a station out to the west, beyond the puzzle parts: a great one that wants the amulet and opens itself, with the things to give it in a row; a small ram-headed one that is wrathful; a painted falcon-headed one; and one buried, with its lever.

What is Egyptian, and what is the game’s. Egyptian: the sphinx itself, a lion with the head of the king (and it is the king: a guardian, not a monster); the ram-headed ones that line the avenue at Karnak, which are Amun’s, and the falcon-headed ones; the headcloth, the cobra and the beard, and that the Great Sphinx has lost the last and its nose; that it was painted; that it lay buried to its neck; and the Dream Stele that stands between the Great Sphinx’s paws, which says that Thutmose IV, a prince, slept in its shadow, and that it spoke to him in a dream, asked him to clear away the sand that lay on it, and promised him the throne. Offerings were laid before it. The game’s: the riddles. A sphinx that asks riddles and kills is the Greek one at Thebes, a different creature; no Egyptian sphinx is known to have asked anything but that the sand be cleared. The riddles here are written for the game, in the manner of the Egyptian wisdom texts (short sayings, things that speak for themselves), and so are the glowing eyes, the paw that moves, and the way in through its chest (though people have looked for one in the Great Sphinx for centuries).

The model is models/sphinx.glb, built in Blender by tools/build_sphinx.py, which also keeps tools/sphinx.blend to look at and change by hand. It is one metre long, lying on the ground and facing the game’s +Z (Blender’s -Y), and has no bones: it is separate things, each named for what it is, and a thing that turns has its origin where it is hinged, so moving an origin moves the joint.

In the model What it is
base The block it lies on (0.1 thick in the model; the game makes it as thick as suits the size)
body The lion: trunk with its shoulders, ribs and spine, the two haunches, the hind paws, the tail round the right haunch; and the way through it from chest to rump, which is always there
chest_plug, rump_plug The stone that shuts each end of that way in a sphinx that does not open itself
passage_frames The frame round each end of it in one that does
foreleg The right foreleg, the one that sweeps the slab: its origin is the elbow. The left is the same turned over (foreleg_mirror is only there to be looked at)
king_head Neck, ears and the nemes (cap, band, wings, lappets, tail). Its origin is where the neck turns, as is that of every other thing of a head
king_face, king_face_worn The face; and the same with the nose broken off (worn past six tenths)
king_uraeus, king_beard The cobra at the brow (gone past a third) and the false beard (gone past a half)
king_eye_l, king_eye_r The eyes, each its own thing so that it can open and glow: its origin is its middle, its own +Z the way it looks
ram_head, ram_eye_l, ram_eye_r The ram
falcon_head, falcon_beak, falcon_beak_broken, falcon_eye_l, falcon_eye_r The falcon, its beak, and the beak with its tip gone (worn past seven tenths)
slab, stele The offering slab and the stele, each about its own foot (the riddle is written on the stele’s flat front by the game)

Its colours are materials with plain names. stone, hollow (the dark of the way through) and broken are always stone, drawn by the stone shader (Toon.sandstone). The rest are paint: lapis and ochre (the stripes of the cloth, which are cut into it), skin, kohl (brows, the lines of the eyes), horn. A painted sphinx shows each in the colour the model gives it, so changing a material’s Base Color and exporting again repaints it; a bare one draws them as stone a little lighter or darker (SphinxMesh.TONE). To build it again, and have Godot take it up:

blender --background --python tools/build_sphinx.py
godot --headless --path . --import

(Changed by hand in tools/sphinx.blend instead, it is exported from there as glTF to models/sphinx.glb, +Y up, hidden things too: the other heads and the worn pieces are hidden in the file so that the king can be seen. Running the script again would overwrite both.)

What is still worked out in the game, and why. Its size: scripts/sphinx_mesh.gd makes the meshes size times bigger rather than scaling nodes, because the stone shader works in metres (its courses and cracks would stretch), and makes the base, slab and stele as thick as suits that size. Its weathering: every point is moved a little, by an amount that is a slider. What is solid: boxes and hulls from the numbers at the top of that script, which the model was built round (they are the same in the build script), so a lion made much fatter in Blender wants those changed too. And the riddle, the sand, the dust and the light of the eyes.

What it costs: about 6,600 triangles for a king and 4,800 for a ram or a falcon (it was 3,000: the face, the paws and the trunk are finer), in five draws bare (body, the paw that moves, head, two eyes) as before, besides the slab (three) and the stele (two, one fewer), with one light that is on only while its eyes are; painted, a few more. Two sphinxes that are the same in every way share their meshes.

godot --headless --path . --fixed-fps 60 --script tools/sphinx_test.gd
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/sphinx_sheets.gd -- <folder> [heads faces sizes wear wake right wrong opens wrath buried night yard] [banded]

The first runs it with nothing drawn and ends PASSED or FAILED. The second draws it: each head from all round and close to, every size beside the boy, weathered and painted, each thing it does as a strip of moments, by torchlight, and its station in the yard.

In a tomb the sphinx has one more thing said to it: sweep_to, the place in the world where a thing it refuses is put down (with none said, out beyond its paw as before). A tomb seen from the side has only one line a thing can lie on.

Not done: the paw that sweeps goes through him if he stands in its way (it does not push him); nothing but the things he carries can answer it; and the passage through it is a plain straight one.

Hieroglyphs

The writing on the walls is real. scripts/hieroglyph_signs.gd has 97 signs, each drawn in code as an outline (nothing comes from a font): the 28 of the Egyptian “alphabet” (one consonant each: the vulture, the reed, the owl, the ripple of water…), 44 that are two or three consonants or a whole word (the ankh, the scarab, the eye of Horus, the sedge and bee of the king’s title), 21 determinatives (the seated man, woman and god, the walking legs, the pyramid) and the numerals, each with its number in Gardiner’s sign list, how it is typed and what it stands for. docs/hieroglyph_signs.png is a chart of them all.

Hieroglyphs.write(text) (scripts/hieroglyphs.gd) sets a text out in signs, and Hieroglyphs.read(text) gives back what it says in plain words, for a translation:

The signs are packed as the Egyptians packed them, into squares: flat ones over each other, small ones together, tall ones alone; in rows or in columns; read from the left, or from the right with every sign turned to face the other way; with ruled lines between. The same text always comes out the same.

An Inscription (scripts/inscription.gd) is a panel carrying a text, carved: sunk into the stone (or raised out of it), painted in the old colours or bare, as wear-worn as you like. It is one flat rectangle and one small picture, made once: the picture holds how far each point is from the edge of a sign, and the shader reads the slope of the cut from it, so the sun and a passing torch light the side of each cut that faces them. See the top of the file.

Inscription.on_box(wall, Vector3(4, 3, 0.6), Vector3.BACK, "@offering")          # on the front of a box-shaped wall
add_child(Inscription.slab("<Khufu> lives forever", Vector2(2.4, 1.2)))           # a slab of its own
for carved: Inscription in get_tree().get_nodes_in_group(&"inscriptions"):       # what each one says
	print(carved.translation())

In the level editor it is “Inscription” on the Stone page: type what it should say, or pick one of the texts; its width and height, rows or columns, carved, painted, weathered, and whether it is a slab of its own or bare writing to stand against a wall that is already there. The test yard has a station of them (east of the grappling hook), and the “Carved wall” prop now carries the offering formula on its front and the curse on its back.

godot --path . --resolution 1280x720 --script tools/glyph_sheets.gd -- <folder> [chart] [stone] [torch] [yard] [prop] [banded]

draws the chart and the pictures: slabs near and far, in raking light, in shade, painted, worn, and by torchlight.

How true it is: the signs, their sounds, the dictionary words, the formulas and the way they are packed are real, and someone who reads hieroglyphs can read the named texts. English spelled out letter by letter is the modern game of writing a name in hieroglyphs, not Egyptian. The hints are composed for the game, in Egyptian words and word order.

The gate guardians

The dead of Egypt went down through a row of gates, each kept by a seated demon with the head of a beast and a knife in its fist. Whoever could say the keeper’s name passed; whoever could not was cut down. The names were written in the Book of the Dead so that the dead man would have them by him. That is the puzzle: a keeper at a gate, and somewhere a leaf of the book with its name on it.

var keeper := GateGuardian.new()
keeper.who = 6                     # its place in BookOfTheDead.NAMES: "He who lives on snakes"
keeper.bars_way = true
keeper.position = beside_the_gateway
add_child(keeper)
keeper.changed.connect(func(on: bool) -> void: door.is_open = on)
keeper.caught.connect(...)

var leaf := BookPage.new()         # (who = -1: the nearest keeper's name)
leaf.position = somewhere_else
add_child(leaf)

The level editor has “Gate guardian” (its name, head, what it wears, one knife or two, whether it bars the gateway, where the gateway is and how big, whether it asks whoever steps onto the threshold, its size) and “Leaf of the Book of the Dead” (whose name, rolled or not) on its Puzzle page; a guardian is one of the things a door can be opened by. The test yard has a station, south of the railway and beyond everything else: a wall with three gateways and a keeper at each, the first keeping a stone door and the others barring their own, the first leaf in the open and each of the others in the cell behind the gate before.

The figure is a Blender model, models/guardian.glb, built by tools/build_guardian.py, which also keeps tools/guardian.blend to look at and change by hand:

blender --background --python tools/build_guardian.py
godot --headless --path . --import

(Changed by hand in tools/guardian.blend instead, it is exported from there as glTF to models/guardian.glb, +Y up; running the script again would put back what the script says.) It has no bones. It is separate things, each named for what it is, and where a thing’s origin is put is where the game hinges it:

The colours are the model’s materials, named plainly (linen, linen_band, skin, seat, hilt, crocodile_belly, ram_horn, …), and the game takes each colour from there. A few names mean something to it (scripts/guardian_mesh.gd): gold and blade shine; eye is lit from within; stone is drawn as the game’s sandstone in that colour; hide (the neck, and the fists of the wrapped figure) is painted in the colour of the head’s own hide, which is the head’s material whose name ends _hide; a head with anything painted wig gets the wig down its back; papyrus is written on and tie is the cord of the rolled leaf. The colours of the drawings on the leaf and the buttons are not the model’s: they are BookOfTheDead.HIDES.

A head is 660 to 1,200 triangles and the whole of a guardian 1,800 to 2,300 (it was about 1,500). It is ten things to draw (it was thirty): one for each part that moves, in one material that every guardian shares, each point carrying its colour, and one more for the stone of the plinth; the two great knives are one each. Where its hips, knees and ankles are as it sits and rises stays in scripts/gate_guardian.gd (SEATED, RISEN, ANKLE, THIGH, SHIN), since they are worked out as it moves. The pictures on the leaf and the buttons are drawn in code (BookOfTheDead.draw_vignette, draw_signs), as is the writing on the leaf lying on the ground and the shadow under it, and its voice is made in code, not recorded. scripts/guardian_panel.gd is the leaf to read and the names to choose from: nothing on it is under 88 high to touch, or written under 26. With a gamepad the mark is on the first name as it comes up, the d-pad moves it, A says the name and B steps back.

How true it is. The gates, their seated keepers with beasts’ heads and knives, and the passing of each by saying a name are spells 144 to 147 of the Book of the Dead; in 144 and 147 there are seven gates, each with a keeper, a watcher and a herald, and in 145 and 146 the portals of the house of Osiris, each with a doorkeeper. The thirteen names here are theirs (twelve from the seven gates, “Long-horned bull” from the fourth portal of 146), in English that follows the usual translations. They were set down from memory, not from a book open on the desk: the wording differs from one translator to the next, the papyri do not agree on which of the three at a gate bears which name, and a name or two may be given here to the wrong gate. The Egyptian under each (says) is the word as best remembered, and the signs spell it sound by sound with the signs this game has (with the face, the heart, the voice and “life” as the words they are): it is Egyptian that can be read, but not the spelling of any papyrus. Which beast’s head goes with which name is this game’s choice; the papyri draw the keepers with these heads but do not match them so. The three names to choose from, and the striking down by the keeper himself, are the game’s.

godot --headless --path . --fixed-fps 60 --script tools/guardian_test.gd
godot --path . --fixed-fps 60 --resolution 960x960 --script tools/guardian_sheets.gd -- <folder> [heads round far idle strike named dark leaf yard]
godot --path . --fixed-fps 60 --resolution 1560x720 --script tools/guardian_sheets.gd -- <folder> panel book yard

The first runs it with nothing drawn and ends PASSED or FAILED: without the leaf no English is shown and with it there is; the same seed deals the same names; “Step back” does nothing; a wrong name, touched on the screen, catches him and deals new ones; the right one turns the guardian on, unbars its gateway and opens a door linked to it in the desert; a leaf takes the nearest keeper’s name; and a level started again has forgotten its leaves. The second draws it; the third takes the names and the leaf as a phone shows them, every keeper as the papyrus draws him, and the notebook’s page.

Temples

Temple (scripts/temple.gd) is an Egyptian temple made from numbers, as Pyramid is a pyramid: a row of parts along an axis, front to back. He comes to it from +Z and walks in towards -Z; the middle of its length is its origin.

plan sets it out as one of three real places, or as your own:

  True to the place Simplified
EDFU (Horus; Ptolemaic, 237 to 57 BC) 137 m long; the pylon 79 m wide and 36 m high with four flagstaff slots; the court with its colonnade on three sides; the outer hall of 18 columns behind screen walls and the inner of 12; two rooms; the sanctuary free-standing in its corridor with Nectanebo’s grey granite naos (4.2 m); the enclosure wall and the walk inside it; composite capitals; the granite falcons in the double crown, two at the gate and one before the hall; all of it roofed, and dark The chapels round the sanctuary are shut doors, not rooms. The stairs to the roof, the roof kiosk, the library and robing room are not there. Every capital is the same composite (the real ones are in pairs, each pair different). The nilometer is a round well, not the stair down from the east walk. The mammisi is not built
LUXOR (Amun; Amenhotep III and Ramesses II, about 1390 to 1213 BC) Long and open: Ramesses’ pylon 65 m wide and 24 m high, his two seated colossi, one of the standing ones, and the one obelisk (25 m) on the left as he comes to it, with the pedestal of the other, which went to Paris in 1833; his court with a double row of papyrus bud columns; the mosque of Abu el-Haggag standing in it on the undug bank, with steps up; the colonnade of fourteen open-papyrus columns 19 m high between walls; the sun court with double rows of bud columns on three sides; the hall of 32 columns; and the roofed rooms behind. No roofs over the courts and colonnade The first court is skewed off the axis in the real temple (to keep an older shrine): here it is straight. Thutmose III’s three-roomed shrine in the first court is left out. The mosque is a plain white hall, dome and minaret. The rooms behind (the Roman chapel, the birth room, Alexander’s boat shrine) are two rooms and a sanctuary. It is 235 m long, not 260
KOM_OMBO (Sobek and Horus the Elder; Ptolemaic and Roman) Two of everything along two axes side by side: two gateways, two aisles through both halls and three rooms, two sanctuaries. Its front has gone into the river: pylon, court walls and the sixteen court columns are stumps, with the altar in the court. The 15 and 10 columns of the halls stand to their full height under half a roof; the sanctuaries are down to low walls with the black stone each boat stood on. It stands on a rise with steps up, with the well and a small pool beside it Which side is whose is not marked. The two walks round it are one. The reliefs (the surgical instruments, the calendar) are not those. The small pool for the crocodiles is a square tank above the ground
CUSTOM Whatever the numbers say: width, the height of the pylon, the court and its rows of columns, how many halls and how many columns across and deep, their height and capitals, a higher middle aisle, two of everything, roofed or not, wall, lake, well, obelisks, kings, falcons, a rise  

size is how big: 1 is the size of the real thing, and that is how the three are placed. Whatever the size, doorways stay at least 2.2 m wide and 2.9 m high, steps stay steps, and columns never stand so close that he could be caught between them. ruin (with seed) takes it apart: roof slabs go, columns snap and their drums lie about, walls come down to different heights along their length, lintels fall, blocks lie where they fell (never in the way in), sand drifts into the corners, and past a half the pylon is down to its lower courses. buried fills it with sand inside and out and banks it up round it: 1 is to the tops of the columns of its great hall, as Edfu was until 1860. painted is how much of the old colour is left on capitals, cornices and carving.

The parts are models, made in Blender. tools/build_temple.py builds every part as an object of its own in tools/temple.blend, and exports them all to models/temple/temple.glb:

blender --background --python tools/build_temple.py
godot --headless --path . --import

Open tools/temple.blend to look at a part or change it by hand, export it over models/temple/temple.glb (glTF binary, +Y up, everything), and the game uses what you made. (Running the script again makes them all afresh and overwrites what was changed by hand; to keep a change, make it in the script.) The objects:

Object What it is How the game uses it
column_bud, column_open, column_composite, column_hathor A column with its base and capital: a bundle of papyrus stems with closed buds (Luxor’s courts), the open papyrus bell (Luxor’s colonnade), the composite of leaves and flowers (Edfu, Kom Ombo), Hathor’s head. 10 m high, the shaft 1 m in radius Stretched to the height and thickness wanted
column_..._far The same with fewer faces Drawn in its place from far off, and casts the near one’s shadow
column_broken, column_drum What is left of a snapped column; one drum of a fallen one Stretched; the drum laid on its side
architrave, roof_slab A beam and a slab, 1 m each way Stretched to the length, height and width wanted
cornice, cornice_corner, torus A metre of cornice, the corner where two lengths meet, a metre of the roll The cornice stretched along a wall top (the roll is not placed yet: the towers and gateway carry their own)
screen_wall One of the half-height walls between the columns of a Ptolemaic front, with its cornice Stretched from column to column
pylon_tower, pylon_tower_left The right and left towers of a pylon, as Edfu’s: slots, windows, rolls, cornice Stretched to the width, height and depth wanted
pylon_tower_ruin, pylon_tower_ruin_left A tower down to its lower courses The same, as high as what is left
pylon_gateway The gate between them: jambs, lintel with the winged disc, cornice Stretched until its opening is the size wanted
naos, barque_stand, altar, well The granite shrine; the stand for the god’s boat; an altar; the nilometer Made the size wanted
obelisk, obelisk_pedestal An obelisk on its pedestal; the pedestal alone The same
colossus_seated, colossus_standing, falcon The seated and the standing king; Horus as a falcon in the double crown The same
mosque, minaret The whitewashed hall with its dome; its minaret The same

The game measures each model and stretches it to fit, so a part may be remodelled at any size; what is solid of it is worked out from its size too (TempleParts.SOLID; for the pylon and gateway from TempleParts.TOWER, TOWER_LEAN and GATE, which must be changed to match if their shape is). What a face is made of is read from the name of its material (stone, stone_coursed, stone_relief, stone_shaded, redgranite, greygranite, whitewash, dark, and ..._painted_red and the other paints: the top of tools/build_temple.py lists them), so a face given one of those materials in Blender is drawn as that. All the materials of a model are made one mesh and one draw when it is first used; there are no textures and no UVs to keep.

What is built in code is only what is plain: walls (with doorways, and coming down in ruin), floors, the bank the mosque stands on, steps, the kerb of the lake, fallen blocks, sand, light, and everything that is solid. All of that is one mesh. The stone of both is one shader (scripts/temple_stone.gd): blocks in courses, and on the acres of wall nobody comes close to, rows of relief (a line of figures with columns of signs between them, none of it real writing), all of which fades out before it is small enough to shimmer. Where he does come close the writing is real: Inscriptions of the king’s titles with his name in its ring (Ptolemy at Edfu and Kom Ombo, Ramesses at Luxor) beside every doorway on the axis and down the obelisk, the offering formula and the praise of Ra in the sanctuary, and Horus’s name on the naos. The king and the god on the pylon are drawn here in code as the hieroglyphs are (their outlines are in TempleStone.SMITING and GOD) and cut into the tower’s face as an inscription is.

For whoever makes a level, a temple marks its places with Marker3Ds in groups: temple_sanctuary (before each naos), temple_doorways (the foot of every doorway on the way in, each with wide, tall and thick as metadata: give a “Door” from the Puzzle page those numbers and it fits, though a door there goes no higher than 12 m and the gate of Edfu is 15.5), temple_pylon_top, temple_court, temple_well, temple_lake, temple_mosque, and temple_avenue (pairs of places along the way up to the gate, for sphinxes). ways is the way in for each axis and open whether he can get all the way (and shut_by why not: sand to within 1.45 m of a lintel shuts a doorway). The tops of its walls, beams, roofs and cornices are solid and can be run along, and their edges caught, like any other stone; GrapplePoints are put on the gate’s cornice and on beams that have open air under them. There are no stairs up the pylon: its top is marked, and a ladder or a rope must be put there by whoever wants him up it.

Light: under a roof the stone is drawn darker (it is baked into the mesh; the less roof is left, the less dark), and the sun’s shadows do the rest. The boy himself and whatever else is put inside are lit as they are outside, because the game’s light from the sky is the same everywhere: a level that wants him dark in the sanctuary must do that itself. The lake’s water is a Pool, which lies square to the world: if a temple with a lake is turned other than by quarter turns, the water does not turn with its tank.

What it costs, at full size (tools/temple_sheets.gd prints it): Edfu is about 24,000 triangles when he is among its columns and 11,000 from far off, 235 solid shapes, and builds in about 130 ms the first time (when the models are read) and 25 ms after; Luxor, with 174 columns, is 57,000 and 22,000, 490 shapes, 60 ms; Kom Ombo 11,000 and 5,000, 210 shapes, 20 ms. A whole temple is about twenty-five draws. With the sun’s shadows, a frame in Luxor’s first court draws about 60,000 triangles more than the same frame without the temple, and one in Edfu’s about 35,000.

In the level editor they are “Temple: Edfu”, “Temple: Luxor”, “Temple: Kom Ombo” and “Temple (your own)” on the Stone page, with sliders for the above; move one and it is built again.

godot --path . --resolution 1280x960 --script tools/temple_sheets.gd -- <folder> [banded] [edfu] [luxor] [kom_ombo] [custom] [capitals] [ruin] [buried] [painted] [small]
godot --headless --path . --fixed-fps 60 --script tools/temple_test.gd [-- quick]

The first draws each plan from the air, as a plan, from his eye on the way up, the pylon, the court, each hall and the sanctuary, every model (capitals), and ruin and sand at several values. The second builds each with nothing drawn, walks him from before the gate to each sanctuary, runs and jumps him at its stone from every side to see that he is never inside it or stuck, and does the same ruined and buried.

Abu Simbel

The two temples Ramesses II had cut into a sandstone cliff above the Nile in Nubia, at their true size, as they stood in the 1910s: by the river, where they were cut (the lake and the move uphill are fifty years off), with the sand of the desert above still coming down the gully between them. AbuSimbel.new() is the whole place; in the level editor it is “Abu Simbel” on the Stone page. The node is on the ground before the door of the Great Temple, which looks along +Z (the river and the sunrise); north is +X, and the Small Temple is 100 m that way.

The models. The stone is made in Blender, by a script, so that it can be looked at and changed:

blender --background --python tools/build_abu_simbel.py              # everything again from the numbers in the script
blender --background tools/abu_simbel.blend --python tools/build_abu_simbel.py -- export
                                                                     # only write the .glb again, after changing the .blend by hand
godot --headless --path . --import

tools/abu_simbel.blend has every part as an object with a plain name, the parts of each temple lying where they belong and the figures in rows out in front; models/abu_simbel/abu_simbel.glb is the same, for the game, which finds each part by its name: colossus, colossus_broken, colossus_fallen, crown_fallen, small_figure, baboon, falcon, ra_horakhty, king_standing, queen_standing, osiride_pillar, square_pillar, hathor_pillar, god_ptah, god_amun, god_king, god_ra, cow, great_hill, small_hill, ridge, great_facade, small_facade, great_inside, small_inside (the top of the script says what each is, and how it must lie). A shape may be changed freely in the .blend. What is solid is not in the models: the script scripts/abu_simbel.gd makes it from boxes, from the same measurements as the Blender script (so a part that is moved or made a different size wants the number changed in both), and takes the skin of the hills from the model as it is. The colour of each point is read by the stone shader (red: how light; green: laid in courses, or inside, a wall that carries the battle; blue: rough rock, or inside, a half for a ceiling), and must not go above 1.

What it costs, at true size with both temples: about 40,000 triangles outside and 18,000 inside (which are only drawn while the camera is within about 100 m of a temple’s door), before Godot’s own coarser versions of each mesh for distance; 52 draw calls at most, 30 of them writing that is only drawn from near; 265 shapes to collide with. It is made in about 70 ms (the first one, which loads the models, in about 280). The colossi are one mesh drawn three times and a broken one; pillars, baboons and the small temple’s figures are each one mesh drawn many times.

godot --headless --path . --fixed-fps 60 --script tools/abu_simbel_test.gd
godot --path . --resolution 1280x800 --script tools/abu_simbel_sheets.gd -- <folder> [figures] [whole] [inside] [drift]

The first walks him in at both doors to both sanctuaries, in over the sand at a half and at 1, up the ramp to the top, throws him at the colossi, what fell and the pillars from many sides to see that he is never inside stone, and checks the sun and the switch. The second takes the pictures (add --rendering-method gl_compatibility for the web’s renderer), and prints what it costs. There is no station for it in the test yard: it is too big.

How true it is. True: where everything is and how big (the facades, the colossi, the halls and their order, the number of pillars, who sits in the sanctuary and which of them the sun misses, the six colossi of the small temple and their order, the broken colossus and where it lies, the baboons, the niche, the sand from the north). Made simpler: the hill is a rounded mass in level beds, not the real cliff; the figures are plain, with no carving on thrones or kilts and none of the bound captives along the bases; the great temple has four store-rooms where there are eight, and the two chapels off the vestibule and the two outside are left out; the floors do not rise nor the ceilings come down towards the sanctuary as the real ones do; the battle on the walls is rows of soldiers, chariots and the king, not the scenes as they are; the small temple’s pillars and sanctuary are plainer than the real ones; and the writing is the king’s names and the stock formulas, not what is carved there. The two temples are set as they lie to each other only roughly. Below about three quarters of its size the doors get too low and narrow for him. The sides of the ramp of sand are steeper the nearer the top (up to 62 degrees), where it is banked against the rock: lying at 33 degrees all the way up, its top stood out 75 m either side of the hill and its upper end was open there, so that he could walk in under the sand.

The temples in the shipped desert

The shipped desert (levels/desert.json) is 640 m across. What was in it when it was 400 m across is all there, as far from the north and west edges as it was: 120 m west and 120 m north of where its numbers used to put it (he starts at -120, 45). East of it a river runs from north to south, 24 m wide, with palms and reeds on its west bank. Three temples stand by it at their true size, each on level ground that the dunes keep off:

There is a checkpoint before each and one inside each. Luxor is not there (it wants 300 m by 100, and the open ground left is the level’s dunes): it is still on the editor’s Stone page. tools/desert_temples_test.gd checks where they stand and does the three things; tools/desert_walk.gd walks to each, in and out; tools/desert_temples_sheets.gd -- <folder> takes the pictures and says what each costs.

Tombs

tomb.tscn is a tomb made from a number: the daily tomb, the same for everyone on the same day, or one for practice from any number and at any of three difficulties. He goes in, takes the gold falcon from the burial chamber, and comes out again; the clock runs from his first step until he is out of the door with it. It is not in the menu’s list of levels yet: add ["Tombs", "res://tomb.tscn"], to LEVELS in scripts/menu.gd. Opened like that, or by itself (godot res://tomb.tscn), it is today’s tomb, and a card comes up first that offers a random one instead. To open it on a tomb of your choosing, set TombLevel.play first ({"mode": "daily"} or {"mode": "random", "seed": 12, "difficulty": 1}), or pass -- tomb=daily or -- tomb=12:1 on the command line. Either takes how it is to be seen as well: "view": "side" (what it is with nothing said) or "view": "3d", and view=3d on the command line (-- tomb=12:1 view=3d). The card has the two as “Side-on” and “Walk through”, and remembers the last one chosen until the game is closed. A third button, “Branching”, is another kind of tomb altogether ("shape": "branching", shape=branching): rooms on a floor plan with ways off to either side, side chambers, dead ends and loops, and a map. “Tombs that branch”, below, has it.

Tombs walked through

TombLayout3D (scripts/tomb_layout_3d.gd) sets a plan out a second way, and TombBuilder and TombLevel take either: it is a TombLayout, and answers the same questions (which room he is in, whether he is out, whether a thing is lost, whether a mummy can still get at him).

Tombs that branch

A third kind of tomb: rooms on a floor plan, with ways off to either side, side chambers that hold what a door further on wants, dead ends, and loops. TombBranching (scripts/tomb_branching.gd) makes the plan, with a cell for every room, and from there it is a tomb like any other: proved by TombSolver, set out by TombLayout3D (it is only ever walked through), built by TombBuilder, run by TombLevel. It is opened with "shape": "branching" in TombLevel.play ({"mode": "random", "seed": 12, "difficulty": 1, "shape": "branching"}; the view is “3d” whatever is asked), with -- tomb=12:1 shape=branching on the command line, or with “Branching” on the card, beside “Side-on” and “Walk through”. The other two kinds are what they were: the same plans and the same stone from the same numbers (TombBranching.VERSION is its own: 2, since the creatures went in).

File What it does
scripts/tomb_plan.gd What a tomb is before it is built: rooms, ways, things, switches
scripts/tomb_generator.gd Makes a plan from a seed; never hands back one that fails
scripts/tomb_branching.gd Makes the plan of a tomb that branches from a seed, its rooms on squares of a floor plan; never hands back one that fails
scripts/tomb_solver.gd Proves a plan can be finished, by trying everything
scripts/tomb_layout.gd Sets a plan out as a tomb seen from the side: every stone and part, as numbers; and what a level asks of either layout
scripts/tomb_layout_3d.gd Sets the same plan out as a tomb to be walked through: the cells and their rules, rooms and ways, the stone cut round them, and the lines he is expected to walk
scripts/tomb_reach.gd What he can do, from the Player’s own numbers, and a layout held against it
scripts/tomb_builder.gd Makes either layout into nodes
scripts/tomb_level.gd Runs a tomb, seen either way: doors, mummies, checkpoints, the clock, the cards
scripts/tomb_daily.gd Which tomb a day has, and what is kept of how it went, for each way of seeing it
scripts/tomb_spec.gd A tomb made by hand, as data: what is wrong with it, the plan of it, keeping it
scripts/tomb_floor_spec.gd The part of that which is about a tomb that branches: where each room stands, what it is entered from, second ways; what is wrong with it in plain words; and what the editor does to a floor plan
scripts/tomb_map.gd The map on the HUD of a tomb that branches
scripts/tomb_editor.gd The tomb editor (tomb_editor.tscn): a tomb down one axis from the side, one that branches as a floor plan from overhead
scripts/tomb_phone_ui.gd What is touched in the tomb editor on a phone: the two bars, the strip, and the pages
tools/tomb_editor_test.gd Not part of the game: checks hand-made tombs and the editor, and works the editor as it is on a phone by touches alone, in both views (tools/tomb_editor_shots.gd takes pictures of it with a mouse, tools/tomb_phone_shots.gd on a phone)
tools/tomb_phone_shots.gd Not part of the game: a picture of every state of the editor on a phone, each measured
tools/tomb_phone_probe.gd Not part of the game: measures what can be touched on the phone layout against what a thumb needs (88 by 88, 12 apart, clear of the edges, words of 26)
scripts/tomb_hooks.gd Where scarabs, cobwebs, jackals, inscriptions and the notebook plug in
scripts/tomb_guide.gd What the Ba knows: the level as a position of the solver’s, the next step from it, and the places to fly by to show it
scripts/tomb_random.gd The dice
tools/tomb_test.gd Not part of the game: checks plans, geometry and sameness over hundreds of seeds, and plays tombs through with the real Player, seen from the side or (3d) walked through
tools/tomb_sheets.gd Not part of the game: draws a tomb’s plan, the whole of it as built, and each room as he sees it (from the side)
tools/tomb3d_sheets.gd Not part of the game: the same for a tomb walked through (its floor plan, the whole of it from overhead, each room through his camera), and with kinds a sheet of every kind of way, switch and barrier as he comes to it
tools/tomb3d_probe.gd Not part of the game: prints how a tomb is set out to be walked through (cells, rooms, air, parts, his lines; with at=x,z the stone and the parts round a place)
tools/tomb_branch_shots.gd Not part of the game: pictures of what a tomb that branches adds to the screen (the card, the map small and large, forks and a loop as he sees them, the editor’s floor plan)
tools/tomb_shapes.gd Not part of the game: tombs made by hand that the tools share (every lock; the two plates, the rope and the sand; every way; a plan with cells, a side chamber and a loop)
tools/tomb_keeper_shots.gd Not part of the game: pictures of the creatures in a tomb as he meets them (each leaf, guardian, sphinx and Medjed, far and near; Apophis coming out; the Ba showing the way), in either view
godot --headless --path . --fixed-fps 60 --script tools/tomb_test.gd -- [plans] [geometry] [same] [kept] [drive] [templates] [custom] [seeds=500] [drive_seeds=1,2,3] [per=3]
godot --headless --path . --fixed-fps 60 --script tools/tomb_test.gd -- 3d [geometry] [same] [drive] [templates] [custom] [grid] [seeds=500] [drive_seeds=1,2,3] [per=3] [pick=<part of a name>]
godot --path . --fixed-fps 60 --resolution 1280x720 --script tools/tomb_test.gd -- templates custom shots=<folder>
godot --path . --fixed-fps 60 --resolution 1280x720 --script tools/tomb_sheets.gd -- <folder> [seed:difficulty ...] [daily]
godot --path . --fixed-fps 60 --resolution 1280x720 --script tools/tomb3d_sheets.gd -- <folder> [seed:difficulty ...] [b<seed>:<difficulty> ...] [daily] [bdaily] [custom] [grid] [kinds]
godot --headless --path . --script tools/tomb3d_probe.gd -- <seed>:<difficulty> [branching] [areas] [parts] [lines] [at=<x>,<z>]
godot --headless --path . --fixed-fps 60 --script tools/tomb_test.gd -- branching [plans] [geometry] [same] [drive] [templates] [map] [doors] [seeds=500] [drive_seeds=1,2,3] [drive_difficulties=0,1,2]
godot --headless --path . --fixed-fps 60 --script tools/tomb_test.gd -- 3d doors
godot --headless --path . --fixed-fps 60 --script tools/tomb_test.gd -- sweep [seeds=300]
godot --headless --path . --fixed-fps 60 --script tools/tomb_test.gd -- [3d | branching] ba [drive_seeds=1,2,3] [drive_difficulties=0,1,2]
godot --headless --path . --fixed-fps 60 --script tools/tomb_test.gd -- [3d] custom pick=guardian|sphinx|medjed|snake
godot --path . --fixed-fps 60 --resolution 1280x720 --script tools/tomb_keeper_shots.gd -- <folder> [side | 3d | branching] [<seed>:<difficulty> | keepers | sphinx | medjed | snake]
godot --path . --fixed-fps 60 --resolution 1280x720 --script tools/tomb_branch_shots.gd -- <folder> [seed:difficulty] [name=<prefix>]
godot --path . --fixed-fps 60 --resolution 1280x720 res://tomb.tscn -- tomb=12:1 shape=branching
godot --headless --path . --fixed-fps 60 --script tools/tomb_editor_test.gd -- [seeds=60] [fuzz=1200] [size=1280x720]
godot --path . --fixed-fps 60 --resolution 1560x720 --script tools/tomb_phone_shots.gd -- <folder> [only=<part of a name>]
godot --path . --resolution 1560x720 res://tomb_editor.tscn -- phone [tombs_file=<file>]

Everything after 3d is asked of the tomb that is walked through. There the automated player is steered about the floor by the camera, as a thumb’s push is: it goes by the lines the layout says he is expected to walk (TombLayout3D.walk_lines), onto the nearest, from corner to corner along them, and off the last to where it is going; and where the way is not a walk (a rope, the sand, the hook, up into a loft, down into a crypt) it does what he would.