Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e9a5b98
Enable libbacktrace stack traces with minimal debug info
dsrw Jan 8, 2026
4d6aa62
Use dsrw/nim-libbacktrace fork for dynlib support
dsrw Jan 8, 2026
44cf111
Add packed chunk format with VoxelStore extraction
dsrw Jan 8, 2026
17b60c4
Track content bytes separately from network overhead
dsrw Jan 8, 2026
5f21f14
Add debug stats logging and refactor VoxelStore
dsrw Jan 9, 2026
3f412e2
wip
dsrw Jan 9, 2026
230afb1
Fix Chunk sync flags to use packed_chunks for network sync
dsrw Jan 9, 2026
39efd4c
Add zen_ignore to VoxelStore.block_count to prevent client sync
dsrw Jan 8, 2026
ea7009f
Add now() function and Timestamp/Duration types for VM timing
dsrw Jan 12, 2026
8fb9d9a
Minor fixes: move connect log, add nph, unignore bin/
dsrw Jan 12, 2026
50f5c36
Bumped lockfile
dsrw Jan 12, 2026
4c17ef5
Add docs server and build tasks
dsrw Jan 12, 2026
2f9c116
Rate-limit snapshot sync, track voxel changes directly
dsrw Jan 13, 2026
ea2fc6b
Add ASAP mode VM API
dsrw Jan 13, 2026
aadaf57
Bump model_citizen to 0.19.12
dsrw Jan 13, 2026
fe340ae
Add worker stats: ticks/s, max tick time, snapshots, deltas
dsrw Jan 13, 2026
2b1c4ee
Add Code.runner to track script executor context
dsrw Jan 13, 2026
305d9df
Fix -d:metrics build, remove zen_debug_messages
dsrw Jan 14, 2026
3300820
Use bulk VoxelBuffer paste for faster large structure loading
dsrw Jan 15, 2026
4d7c712
Add VoxelRenderer, consolidate voxel storage
dsrw Jan 16, 2026
4cc31ab
Use SCREAMING_SNAKE_CASE for enum values and constants
dsrw Jan 16, 2026
72fa20e
Add Ed API documentation generator
dsrw Jan 17, 2026
dfa01a4
Rename Zen -> Ed (model_citizen library rename)
dsrw Jan 22, 2026
27acf4f
Add periodic paste during ASAP mode
dsrw Jan 22, 2026
a064673
Replace echo with proper logging in game.nim
dsrw Jan 22, 2026
3810b75
Update config: rename zen_lax_free -> ed_lax_free, comment debug options
dsrw Jan 22, 2026
982850d
Improve API docs: add syntax highlighting, custom fonts, better overl…
dsrw Jan 22, 2026
1c89176
Fix ASAP mode: reset flush timer when entering ASAP mode
dsrw Jan 22, 2026
3e67351
Buffer voxels in ASAP mode, paste once for remote clients
dsrw Jan 22, 2026
60c04fd
Render existing voxel data when BuildNode is set up
dsrw Jan 23, 2026
5dbada5
Fixed rendering blocks for instances and sync for new connections
dsrw Jan 29, 2026
930c7b5
Hack nph to have 80 char lines.
dsrw Jan 29, 2026
7626f4b
Fix CI build: cleanup fonts and .gitignore. Fix config precedence. Fi…
dsrw Feb 1, 2026
904b7fa
Fix tasks.nim paths for fonts and remove unsupported OS check
dsrw Feb 1, 2026
b41506a
Fix case mismatch and identifier error in serializers_test.nim
dsrw Feb 2, 2026
655cbc5
Fixed hole deserialization
dsrw Feb 3, 2026
6abb264
Unify documentation styling, fix code block visibility, and clean up …
dsrw Feb 4, 2026
6cceca3
docs: refine syntax highlighting colors and styling
dsrw Feb 4, 2026
3ce682e
docs: improve sidebar ux and refine header colors
dsrw Feb 4, 2026
66c45c6
Fixed deps
dsrw Feb 5, 2026
596b6d0
More doc font tweaks
dsrw Feb 5, 2026
f82fc45
Don't build nim on when setting up deps
dsrw Feb 5, 2026
0e42790
Fix font paths
dsrw Feb 5, 2026
053bb3e
Cleanup nil checks and doc assets
dsrw Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PATH_add bin
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ vendor/pcre
*.dylib
*.dll
*.a
bin/
scratch/
tools/build_helpers
tools/build_helpers.exe
logs/
*.exe
*.out

*.log

app/.import
.DS_Store
Expand All @@ -38,7 +37,6 @@ user_config.nims
nimble.develop
nimble.paths
/t.nim
fonts/
.submodules.tmp

*.previous
Expand Down
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"tasks": [
{
"label": "Build enu",
"command": "nimble",
"command": "nim",
"args": [
"build",
"--debugger:native"
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
28 changes: 25 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ Enu is a 3D sandbox environment for creating and exploring voxel worlds using a
- `atlas install && atlas rep` - Install dependencies (first-time setup)
- `nim prereqs` - Build Godot, download fonts, generate API bindings and stdlib

### Dependency Management (Atlas)

- `atlas.lock` pins exact dependency versions
- `atlas pin <package>` - Update lock file for a specific package after changing its version in deps/
- `atlas install` - Install dependencies from lock file
- `atlas rep` - Regenerate nim.cfg paths from lock file
- When changing dependencies in enu.nimble, run `atlas install && atlas pin` to update the lock file

### Generated Artifacts

**Never manually edit files that are meant to be produced by tools** (lock files, sentinel files, zip archives, etc.), even if you know how to make the required changes. We need to verify our tools work correctly and eliminate potential failure vectors. If a tool isn't producing the expected output, investigate why. Confirm with the user before making any exceptions.

### Core Development Commands
- `nim build` - Build the main application (required after code changes)
- `nim start` - Run Enu in development mode
Expand Down Expand Up @@ -84,16 +96,26 @@ Pass `amd64` or `arm64` to `nim prereqs` to set the target architecture. See `do
- Controllers manage game logic and coordinate between models and UI
- UI components handle presentation and user interaction

### Important Notes
### Casing Conventions

- **Always use `snake_case` for identifiers.** Nim's identifier normalization means `snake_case` and `camelCase` are equivalent, but this project strictly uses `snake_case`. Only use `lowerCamelCase` when `snake_case` doesn't work, which should only happen with third-party macros that don't properly normalize identifiers.
This project follows Ruby-like casing conventions. Nim is style-insensitive for identifiers (ignoring case and underscores), so these are style guidelines rather than compiler requirements:

- **Types**: `UpperCamelCase` (e.g., `VoxelRenderer`, `GameState`, `Build`)
- **Enum values**: `SCREAMING_SNAKE_CASE` (e.g., `ERASER`, `BLUE`, `CODE_MODE`, `PLAYING`)
- **Constants**: `SCREAMING_SNAKE_CASE` (e.g., `CHUNK_DIM`, `ACTION_COLORS`, `IR_BLACK`)
- **Everything else**: `snake_case` (procs, variables, fields, parameters, etc.)
- **Third-party identifiers starting with lowercase**: use `snake_case` (e.g., `writeVu64` → `write_vu64`)

Only use `lowerCamelCase` when `snake_case` doesn't work, which should only happen with third-party macros that don't properly normalize identifiers.

### Important Notes
- Use `nim build` to verify changes compile correctly
- The project uses ZenContext for metrics and threading
- Scripts are Logo-inspired but use Nim syntax
- World data is stored as JSON with accompanying Nim scripts
- If you're not on `main` or a `0.x` branch, try to keep a clean history. Prefer rebasing and ammending commits to keep work in
logical chunks and --force-with-lease push them to origin. Confirm with the user before pulling.
- Never include "Generated by Claude", "Co-Authored-By: Claude", or similar attribution in commits. Keep commit messages concise. Try not to include details that are obvious by quickly looking at the diff.
- **Never include "Generated by Claude", "Co-Authored-By: Claude"**, or similar attribution in commits. Keep commit messages concise. Try not to include details that are obvious by quickly looking at the diff.
- If rebasing or squashing commits, confirm with the user before merging 10 or more commits.
- Avoid nil checks, unless there is a known, non-bug reason why something could be nil. Asserting something isn't nil is fine.
- Things that are fairly obvious shouldn't have comments.
77 changes: 67 additions & 10 deletions atlas.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
"commit": "3aeb5a61f306a0ad4f5a7292cfdc69af2fa35e4d",
"version": ""
},
"model_citizen.getenu.github.com": {
"dir": "$deps/model_citizen.getenu.github.com",
"url": "https://github.com/getenu/model_citizen",
"commit": "cbc2c9d6a645b993ec76650b7735a9e927b223e0",
"ed.getenu.github.com": {
"dir": "$deps/ed.getenu.github.com",
"url": "https://github.com/getenu/ed",
"commit": "c12f6d673c46d4d765e1cfb3ccfdf86a8306d946",
"version": ""
},
"nanoid.nim.getenu.github.com": {
Expand Down Expand Up @@ -75,7 +75,7 @@
"zippy": {
"dir": "$deps/zippy",
"url": "https://github.com/guzba/zippy",
"commit": "a0057dcc689bae60adfc286b8e49dc10db4c98d3",
"commit": "9ab82da84edfd6483d273dfcf8a921309945861e",
"version": ""
},
"unittest2": {
Expand All @@ -84,6 +84,24 @@
"commit": "1efa65bb037185f0983ac4fa65932e8d3450825d",
"version": ""
},
"nph": {
"dir": "$deps/nph",
"url": "https://github.com/arnetheduck/nph",
"commit": "1c6d7813f9f6d6a4fbcd36dfd1489b18982d0d55",
"version": ""
},
"regex": {
"dir": "$deps/regex",
"url": "https://github.com/nitely/nim-regex",
"commit": "4593305ed1e49731fc75af1dc572dd2559aad19c",
"version": ""
},
"nim-libbacktrace.dsrw.github.com": {
"dir": "$deps/nim-libbacktrace.dsrw.github.com",
"url": "https://github.com/dsrw/nim-libbacktrace",
"commit": "d8bd4ce5c46bb6d2f984f6b3f3d7380897d95ecb",
"version": ""
},
"threading": {
"dir": "$deps/threading",
"url": "https://github.com/nim-lang/threading",
Expand All @@ -105,7 +123,7 @@
"supersnappy": {
"dir": "$deps/supersnappy",
"url": "https://github.com/guzba/supersnappy",
"commit": "e4df8cb5468dd96fc5a4764028e20c8a3942f16a",
"commit": "4fed6553d539cbbfb17ab5fea16a58b4f1916e7d",
"version": ""
},
"faststreams": {
Expand Down Expand Up @@ -150,6 +168,30 @@
"commit": "0646c444fce7c7ed08ef6f2c9a7abfd172ffe655",
"version": ""
},
"hldiff": {
"dir": "$deps/hldiff",
"url": "https://github.com/c-blake/hldiff",
"commit": "cc1fc22a18c57e16d89c580cf78478c38c394c66",
"version": ""
},
"glob": {
"dir": "$deps/glob",
"url": "https://github.com/haltcase/glob",
"commit": "dfb567fe1803d08b2e6272c56f30885c8a63f4d1",
"version": ""
},
"toml_serialization": {
"dir": "$deps/toml_serialization",
"url": "https://github.com/status-im/nim-toml-serialization",
"commit": "b5b387e6fb2a7cc75d54a269b07cc6218361bd46",
"version": ""
},
"unicodedb": {
"dir": "$deps/unicodedb",
"url": "https://github.com/nitely/nim-unicodedb",
"commit": "66f2458710dc641dd4640368f9483c8a0ec70561",
"version": ""
},
"stew": {
"dir": "$deps/stew",
"url": "https://github.com/status-im/nim-stew",
Expand Down Expand Up @@ -191,14 +233,20 @@
"url": "https://github.com/status-im/nim-http-utils",
"commit": "c53852d9e24205b6363bba517fa8ee7bde823691",
"version": ""
},
"adix": {
"dir": "$deps/adix",
"url": "https://github.com/c-blake/adix",
"commit": "38a95ee6bd675b31746fa79d318b546495374ffa",
"version": ""
}
},
"nimcfg": [
"############# begin Atlas config section ##########",
"--noNimblePath",
"--path:\"deps/Nim.getenu.github.com\"",
"--path:\"deps/godot-nim.getenu.github.com/godot\"",
"--path:\"deps/model_citizen.getenu.github.com/src\"",
"--path:\"deps/ed.getenu.github.com/src\"",
"--path:\"deps/nanoid.nim.getenu.github.com/src\"",
"--path:\"deps/pretty/src\"",
"--path:\"deps/cligen\"",
Expand All @@ -210,6 +258,9 @@
"--path:\"deps/metrics\"",
"--path:\"deps/zippy/src\"",
"--path:\"deps/unittest2\"",
"--path:\"deps/nph/src\"",
"--path:\"deps/regex/src\"",
"--path:\"deps/nim-libbacktrace.dsrw.github.com\"",
"--path:\"deps/threading\"",
"--path:\"deps/flatty/src\"",
"--path:\"deps/netty/src\"",
Expand All @@ -223,11 +274,16 @@
"--path:\"deps/chronos\"",
"--path:\"deps/results\"",
"--path:\"deps/stew\"",
"--path:\"deps/hldiff\"",
"--path:\"deps/glob/src\"",
"--path:\"deps/toml_serialization\"",
"--path:\"deps/unicodedb/src\"",
"--path:\"deps/fusion/src\"",
"--path:\"deps/mustache/src\"",
"--path:\"deps/parsetoml/src\"",
"--path:\"deps/bearssl\"",
"--path:\"deps/httputils\"",
"--path:\"deps/adix\"",
"############# end Atlas config section ##########",
""
],
Expand All @@ -244,16 +300,17 @@
"",
"requires \"https://github.com/getenu/Nim#77d820e1\",",
" \"https://github.com/getenu/godot-nim 0.8.6\",",
" \"https://github.com/getenu/model_citizen 0.19.10\",",
" \"https://github.com/getenu/ed 0.20.0\",",
" \"https://github.com/getenu/nanoid.nim >= 0.2.1\",",
" \"https://github.com/treeform/pretty >= 0.2.0\", \"cligen\", \"chroma\", \"markdown\",",
" \"chronicles\", \"dotenv\", \"nimibook\", \"metrics#51f1227\", \"zippy\", \"unittest2\"",
" \"chronicles\", \"dotenv\", \"nimibook\", \"metrics#51f1227\", \"zippy\", \"unittest2\",",
" \"nph\", \"regex\", \"https://github.com/dsrw/nim-libbacktrace\"",
""
]
},
"hostOS": "macosx",
"hostCPU": "arm64",
"nimVersion": "2.2.6 ab00c56904e3126ad826bb520d243513a139436a",
"nimVersion": "2.2.6",
"gccVersion": "",
"clangVersion": ""
}
1 change: 1 addition & 0 deletions bin/config.nims
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
patch_file "nph", "phrenderer", "patches/phrenderer"
76 changes: 76 additions & 0 deletions bin/docs.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import std/[asynchttpserver, asyncdispatch, os, strutils, mimetypes]

const
project_path = current_source_path().parent_dir().parent_dir()
vendor_path = project_path / "vendor"
voxel_docs_path = vendor_path / "modules/voxel/doc/site"
godot_docs_path = vendor_path / "godot/doc/_build/html"
enu_docs_path = project_path.parent_dir() / "enu-site"
port = 9999

proc handle_request(req: Request) {.async, gcsafe.} =
var path = req.url.path

# Determine which docs to serve based on path prefix
var docs_path: string
var relative_path: string

if path.starts_with("/voxel"):
docs_path = voxel_docs_path
relative_path = path[6 ..^ 1] # Strip "/voxel"
elif path.starts_with("/godot"):
docs_path = godot_docs_path
relative_path = path[6 ..^ 1] # Strip "/godot"
elif path.starts_with("/enu"):
docs_path = enu_docs_path
relative_path = path[4 ..^ 1] # Strip "/enu"
elif path == "/" or path == "":
# Serve index page with links to all
let index_html =
"""<!DOCTYPE html>
<html><head><title>Docs</title></head>
<body>
<h1>Local Documentation</h1>
<ul>
<li><a href="/enu/">Enu docs</a></li>
<li><a href="/voxel/">godot-voxel module docs</a></li>
<li><a href="/godot/">Godot 3.5 class reference</a></li>
</ul>
</body></html>"""
await req.respond(
HTTP200, index_html, new_http_headers([("Content-Type", "text/html")])
)
return
else:
await req.respond(HTTP404, "Not found. Try <a href=\"/\">/</a> for index.")
return

# Handle empty path or directory paths
if relative_path == "" or relative_path == "/":
relative_path = "/index.html"
elif not relative_path.contains('.'):
relative_path = relative_path / "index.html"

let file_path = docs_path / relative_path

if file_exists(file_path):
let content = read_file(file_path)
let ext = file_path.split_file().ext
let mimes = new_mimetypes()
let mime =
mimes.get_mimetype(ext.strip(chars = {'.'}), default = "text/html")
await req.respond(
Http200, content, new_http_headers([("Content-Type", mime)])
)
else:
await req.respond(Http404, "Not found: " & path)

proc main() =
echo "Serving docs at http://localhost:", port
echo " /enu/ -> Enu docs"
echo " /voxel/ -> godot-voxel docs"
echo " /godot/ -> Godot 3.5 class reference"
let server = new_async_http_server()
wait_for server.serve(port.Port, handle_request)

main()
1 change: 1 addition & 0 deletions bin/nph.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include pkg/nph
22 changes: 22 additions & 0 deletions bin/patches/phrenderer.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# stupider like a fox!

import std/[strutils, macros]
import pkg/regex

macro patch_line_length() =
# strip "." from imports, since they're not siblings to this patch file but
# are on the nim path
const import_regex = re2(r"import ""\.""\/\[(.*)\]")

let
path = "../../deps/nph/src/phrenderer.nim"
src = static_read path
og_line_length = "44 else: 88"
new_line_length = "40 else: 80"
patched = src.replace(import_regex, "import $1").replace(
og_line_length, new_line_length
)

parse_stmt(patched, path)

patch_line_length()
Loading