Birdoggydog's Builds

Breaking the first package cycles with a levels interface

Movement code stopped naming level classes. It asks a small interface instead, and 22 backward dependencies went away.

The first slice of breaking the package cycles is landed. The movement rules in the actors package no longer name classes from the levels package. The count of backward dependencies went from 162 to 140, and the ones from actors into levels went from 91 to 69. Nothing in the game looks or sounds different, so there is no film.

I said the cycles should wait until the split was finished, and then that I wanted them done before the audio work. A package that needs another package that needs it back can’t be tested, pinned or handed to an agent on its own. This is only the first slice, and the rest of the 140 are still there.

What was in the wrong package?

Some of it was just filed in the wrong place. SeeThrough names nothing, so it moved to core. StepRule and SlopeRule are about bodies, so they moved to actors. PassageFit and FloorFollow are about dungeons and floors, so they moved to levels. Nothing moved on disk. The package of a file is a rule in packages.json, so this was five edits to that list.

How do the actors ask the levels something?

The rest of the movement code asked the levels package things by class name. Where is the ground here, how deep is the water, is this the open sea, how far does a dungeon opening let a monster walk. I listed every question and gave each a name.

LevelQueries in the actors package holds those eleven questions. It has one static dictionary from a question name to a Callable, and each question is a static function. If an answer is set, the function uses it. If not, it returns the answer for no level at all, which is what each call returned in a test with no world scene and no dungeon before the change. So tests that never load a level behave as they did.

The levels package fills in the answers. LevelAnswers.install() registers a Callable for each question, such as Callable(WaterSurface, &"level") or Callable(PassageFit, &"limit"). It returns at once if it already ran. WorldScene.begin() calls it just before the scene becomes the running one, and every ImportedDungeon calls it from _init(). The answers are there whenever a level exists that could give a different one.

The dependency now points one way. Actors defines the questions because it is the one asking, and levels, which may already use actors, answers them.

What crashed Godot?

I tried two ways of installing the answers first, and both crashed Godot 4.7 with exit code 139. The first stored lambdas in a static variable of another script. That crashed the level check. The second installed the answers from a _static_init(). That crashed as the class loaded, in the import and in the game. A Callable of an ordinary static function works fine, so that’s what the code uses, with the reason written in the doc comment so nobody tries the lambdas again.

Also done

  • The monster husk and the themed prop used to be named by StepRule as things a body can’t step on. They now join the no_step group in their own _init(), and the rule asks the group.
  • tests/level_queries has 32 checks. They cover the no-level answer of each question, the answers after install(), and that installing twice changes nothing.
  • The actors package’s 104 tests came out as 96 passed, 8 from the cache and none failed.
  • Tests see less: the files reachable from all the actors tests, counted per test file, went from 5,011 to 4,876, and the alchemy package’s reach from 77 files to 68.
  • Two counts went the other way. Levels into shell went from 14 to 15 and actors into shell from 8 to 7, because one save-related edge moved with FloorFollow. A later slice takes it.