Every addon installs the same way, and the answer is deliberately boring: copy a folder.
Install an addon
# dot-core is required by everything. Copy it first.
cp -r dot-core/addons/dot_core my-game/addons/
cp -r dot-timer/addons/dot_timer my-game/addons/
Then enable each one in Project → Project Settings → Plugins.
The plugin toggle only registers inspector types
Every class is available through its class_name whether the plugin is enabled
or not. Enabling it is what puts the resources in the Create Resource dialog
and the nodes in Add Node.
An addon’s distributable is its addons/<name>/ folder and nothing else.
Everything outside it in the repository — project.godot, examples/,
fixtures/, icon.svg — exists so the addon can be opened and validated
standalone, and is not copied into a consuming project.
Requires Godot 4.7 or newer
The collection was built on 4.4 and now targets 4.7.2. Every
project.godot declares config/features=PackedStringArray("4.7"),
dotserve and the server tool’s setup.sh both refuse
anything older, and the container pins 4.7.2-stable with its checksum.
The move cost nothing in GDScript: every documented breaking change across 4.5,
4.6 and 4.7 — JSONRPC.set_scope, Node.get_rpc_config, the removed
RenderingServer interpolation methods, AudioEffectSpectrumAnalyzer.tap_back_pos,
the OpenXR and TextServerExtension signatures — occurs nowhere in these
repositories.
One repository per asset
Each asset is its own Git repository and is cloned individually. There is no parent repository, so there is no way to clone the whole collection at once.
git clone https://github.com/modcommunity/dot-core
git clone https://github.com/modcommunity/dot-timer
That is a consequence of how they are consumed: somebody who wants a speedrun
timer should be able to take dot-timer and dot-core and nothing else.
Working on an addon locally
Every project except dot-core needs dot-core present just to open, and the
games need every addon they use. Those links are gitignored, because a consumer
copies the folders instead — and a fresh clone therefore does not open until
something has made them.
Do not make them by hand. dot-bootstrap clones every repository and links
every addon each project needs:
git clone https://github.com/modcommunity/dot-bootstrap.git
cd dot-bootstrap
./bootstrap.sh # clone what is missing, pull what is not, link everything
./bootstrap.sh --links # only redo the links
./bootstrap.sh --status # branch, dirty, unpushed, per repository
./bootstrap.sh --check # does projects.tsv still agree with the disk?
./bootstrap.sh --content-keys # a content signing key, so this machine can publish packs
It contains no list of projects and no list of what each one needs: the projects
come from projects.tsv, and a project’s addons are read out of that
project’s own .gitignore, from its /addons/<name> lines — the one place
that cannot go stale, because the repository that gains a dependency is the
repository that has to ignore the link. The version before it carried four
manifests and they had drifted to 19 of the 33 repositories the collection had
at the time; one run now links getting on for three hundred folders across
fifty-eight of them.
--content-keys is the one mode that is not about repositories:
dot-cloud refuses an unsigned manifest, so
publishing a pack needs a private key, and a private key is the one thing a
clone can never carry. It mints one and points
dot-server-deploy/client/content.json at it — which leaves that committed file
locally modified, on purpose, and is why it is opt-in.
Getting started is the whole walk-through, including what Windows does instead (it copies the folders, because Godot does not follow a junction).
dot-serve needs none of this — it is a shell script
and finds a Godot binary at run time.
The two-step check
There is no CI. Every project has the same two-step check and both steps matter.
cd <project>
# 1. Every script parses.
godot --headless --path . --import
find . -name '*.gd' -not -path './.godot/*' | while read f; do
godot --headless --path . --check-only --script "res://${f#./}"
done
# 2. It actually works.
godot --headless --path . res://examples/<the example>.tscn
The --import pass is not optional
It is what registers the class_name globals. Without it every cross-file type
reference fails and it looks like dozens of unrelated errors — and re-run it
after adding any script with a new class_name, or the identifier does not
resolve, the scene fails to load, and the process hangs rather than failing,
because nothing reaches get_tree().quit().
Step 2 is the one people skip, and it is the one that finds things. Parse-clean
GDScript can still be wrong in ways only execution shows: an interest cache that
dropped what an observer owned, a render timeline that never moved between
packets, a Dictionary handed out by reference so every “copy” shared one
object. Each self-test example exits non-zero on failure, so they work as smoke
tests unchanged.
Cap every headless run with `timeout`
A scene whose script fails to parse hangs rather than failing, and so does a scene with no script at all. Four example scenes are deliberately not self-tests and never terminate on their own.
Three targets, one codebase
Desktop (Windows, macOS, Linux), mobile (Android, iOS) and the browser. The browser is where the constraints live, and they are encoded in the code rather than left to the reader — see Conventions.
The quickest way to find out what a build can actually do:
godot --headless --path . res://examples/capability_report.tscn
Run it on every target you ship to. It is how you discover that your web export
has no threads, or that a device reports no storage quota — both of which change
how dot-cloud behaves.
