### Defensive programming There is no need to add null checks for function parameters in most situations. Ie. ``` func _apply_region_visuals(region: RegionResource): _region = region ``` Same goes for variables which come from fixed scene data, such as: ``` @onready var _post_process_rect: ColorRect = get_node_or_null("WorldRoot/PostProcessLayer/FullScreenRect") as ColorRect func _ready() -> void: PostProcessing.set_render_target(_post_process_rect) ``` here it can be assumed `_post_process_rect` exists because it comes from the scene tree. If it doesn't, it is acceptable to let the game deference null. A useless null check with a silent (or non-silent) return will just hide bugs that should be fixed by avoiding the null in the first place. The caller should not be sending a null parameter. If there is to be a null-check (and there does not always need to be), add it to the caller as the caller will know what to do in a null scenario while the receiving function does not. In cases were it is important that null checks are done, use [catastrophic errors](scripts/ui/catastrophic_error_dialog.gd) that force the developer to see the errors. ### Backwards compatibility When there is existing code that breaks when you refactor it, do not add compatibility layers that make legacy code work with the new code. Instead, refactor or remove old code so only the new architecture is used. This also goes for the save files. The game is currently not shipped, so it is safe to void any old save files when schema changes. ### Headless godot execution You have access to a godot binary in the work folder. You can use this like so: ``` ./godot --headless --path /work --script ./scripts/tests/run_all_simulation_tests.gd ``` You can also use any .gd scripts you make yourself under /tmp like this. ### Simulated tests The game features a simulation test suite. When there are bugs that might re-emerge later, propose adding a new test scenario in the test suite. This test suite supports recording player input and playbacking it later. The simulated tests can be used for minimal runtime tests while developing code. You can write a small gd script to spawn something in the test room, then get actual entities to follow NPC behaviors to fire triggers, and so on. When running tests, make sure to specify the log path to a temporary file using the `log_path="/path/to/log.ndjson"` parameter. ### Documentation When changing architecture or any of the major singleton classes, read [architecture documentation](/doc/architecture.md) and update it if applicable. ### Godot specific programming advice * Use signals to propagate state changes that may influence many receivers. For example, look how [tirgger_run_callback.gd](scripts/level/triggers/trigger_run_callback.gd) is used via its `triggered()` signal to connect a trigger's activation to something in the relevant scene reacting to it. * Use resource files to store "data" attributes that control how something should work. * For example, see how [liquid resources](data/liquid-resources) and their [relevant script](scripts/level/areas/liquid_resource.gd) are used by [movement_area](scripts/level/areas/movement_area.gd) to decide what liquid each area contains and what its properties are. * Also see how [room resources](data/room-resources) are used to specify to which region a room belongs and what the room's name is. ### Localization * There are two localization systems: Godot's own localization for things like Resource localization, and our own dialogue localization. ### Logging The `AppLogger` writes logs to file and normal godot print debug log. All logs are encoded as JSON containing a timestamp and message pair: ``` {"t": 1234567, "m": "here is some error message" } ``` This is designed for programmatic access. If you want to log more detailed logs for programmatic reading, use `AppLogger.log_dict()` which takes a dictionary and allows logging more complex JSON objects. Avoid using `push_warning`, `push_error`, and `print` when `AppLogger` can be used.