DartGame
A 3D darts game where the core loop is dragging modifiers onto board sectors to create compounding scoring interactions. Balatro's joker system, applied to a dartboard.
Built in Godot 4. Modding is a first-class concern, not an afterthought — our own content ships through the same loader community mods will use.
⚠️ Install git-lfs BEFORE you clone
Clone without it and you'll get 130-byte text pointers where your textures and models should be. Godot will throw import errors that look completely baffling and you will lose an hour figuring out why.
NixOS — configuration.nix:
environment.systemPackages = with pkgs; [ git git-lfs ];
Or declaratively via Home Manager (also writes the LFS filter config, so a fresh machine is correct with no imperative step):
programs.git = {
enable = true;
lfs.enable = true;
};
Debian/Ubuntu — sudo apt install git-lfs
macOS — brew install git-lfs
Then, once per machine:
git lfs install
Setup
git clone <gitea-url>/DartGame.git
cd DartGame
git lfs pull
Verify LFS worked. Open game/core/board/dartboard.glb in a text editor. If it's
readable text starting with version https://git-lfs..., LFS did not run. Stop and fix
it before opening the project.
Tooling
# GDScript formatter + linter
nix-shell -p gdtoolkit_4 # or: pipx install "gdtoolkit==4.*"
# Pre-commit hooks
pre-commit install
Open the project
Point Godot 4 at game/project.godot.
Where things live
| Nextcloud | This repo | |
|---|---|---|
.blend, .kra, .psd |
✅ | ❌ never |
.glb, .png (exported) |
❌ | ✅ (LFS) |
Code, scenes, .tres |
❌ | ✅ |
| Moodboards, references | ✅ | ❌ |
Source files live in the company Nextcloud share, not here. They churn constantly, never merge, and 99% of their revisions have no corresponding code change — they'd bloat LFS history forever, on every clone.
Exported artifacts do live here, because they must version in lockstep with the code that references them. If a UV layout changes and a script needs updating, those belong in one commit.
SOURCES.md maps each repo artifact back to its Nextcloud source.
Layout
game/
├── core/ ← THE ENGINE. Contains zero game content. Ever.
│ ├── board/ dartboard scene, mesh, sector logic
│ ├── dart/ dart scene, physics
│ ├── scoring/ ScorePacket pipeline
│ ├── game/ GameContext ← the mod API
│ ├── modding/ loader, registry, base classes, primitives
│ ├── ui/ modifier tray, drag-and-drop
│ └── debug/ hit_simulator.tscn ← you'll use this daily
├── content/official/ ← OUR CONTENT, loaded as a mod like any other
│ ├── skins/
│ ├── modifiers/
│ └── modes/
└── shared/ fonts, shaders
The two rules
1. Nothing in core/ knows about any specific content.
About to write if modifier.id == "mjolnir" in core/? Stop — you need a new primitive.
2. content/official/ must be deletable.
Delete it, and the game boots to an empty state without crashing. That's the proof the mod
pipeline is real. If it can't, we don't have a mod system — we have a plugin folder.
Docs
Full documentation lives in the Obsidian vault at
Nextcloud/DartGame/documentation/. Start with Build Plan.
Contributing
Branches: feat/, fix/, chore/ off main. main is protected and always runnable.
Commits: Conventional Commits.
feat(modding): add ModRegistry namespaced lookup
fix(scoring): ScorePacket.total() ignored final_override
Before you open a PR:
gdformat --check game/passesgdlint game/passes- Nothing in
core/references a specific content ID - New content is one self-contained folder
- If an asset changed: source updated in Nextcloud,
SOURCES.mdcurrent
⚠️ Moving files
Commit first. Godot's refactoring is fragile. Move files from inside the Godot editor's FileSystem dock, never your file manager, so references get fixed up.
If things break after a move or a branch switch:
rm -rf game/.godot/
then Project → Reload Current Project. If it's worse than that, git restore . — which only
works if you committed first.
Status
Pre-alpha. Currently in Phase 0 (repo hygiene). See docs/build_plan.md.