File size: 6,218 Bytes
18d65b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# Native inspection, validation and conversion — F31

The `nanobrush` executable is built from `crates/nanobrush-cli`, backed by the strict [TXT](RUST_TXT.md) and [DAT](RUST_DAT.md) libraries. It supports TXT and DAT versions 201501280/201512200. MAP/VMF/OBJ, geometry evaluation, user-interface replay and the editor application remain future work.

Run from the repository:

```sh
nix run .#nanobrush -- --help
nix run .#nanobrush -- inspect assets/analysis/converter/probes/2016-03-10-c/properties-to-dat/output.mb3dat
nix run .#nanobrush -- validate path/to/world.mb3txt
nix run .#nanobrush -- convert input.mb3txt output.mb3dat --to dat-201512200
nix run .#nanobrush -- convert input.mb3dat output.mb3txt --to txt
nix run .#nanobrush -- convert input.mb3txt old.mb3dat --to dat-201501280
```

Inside `nix develop`, `cargo run -p nanobrush-cli -- ...` uses the same commands. `cargo build -p nanobrush-cli --release --locked --offline` produces `target/release/nanobrush`; `nix build .#nanobrush --no-link` builds an asset-free native package. No original executable is invoked by these commands.

## Interface

```text
nanobrush inspect INPUT [--from auto|txt|dat] [LIMITS]
nanobrush validate INPUT [--from auto|txt|dat] [LIMITS]
nanobrush convert INPUT OUTPUT --to txt|dat-201501280|dat-201512200
    [--from auto|txt|dat] [--force] [LIMITS]
```

`inspect` strictly parses the complete document and prints a single JSON summary: source format, DAT version (null for versionless TXT), computed flag, brush/plane/patch counts, name-table sizes, contour vertices, anchors, points and normals. It does not dump string contents or every coordinate. `validate` uses the same parser and prints `valid: mb3txt` or `valid: mb3dat`; “valid” means structural codec validity, not a convex/usable editor world.

`--from auto` checks the exact `Mb3World` magic; otherwise it attempts TXT. Suffixes do not dispatch formats. `--from dat` forces DAT header/version validation and `--from txt` forces the TXT parser. DAT output always requires its exact version token; there is no ambiguous `--to dat`. Format tokens are case-sensitive. Options may appear before/after positional paths; `--` ends option processing for paths beginning with a dash. Paths use OS strings, including spaces and Unicode; options/format tokens must be UTF-8. Unknown or repeated options fail.

Exit codes are **0** for success/help, **1** for invalid documents or I/O/conversion failures, and **2** for usage errors. Diagnostics go to stderr; inspect/validate/help results go to stdout; successful conversion is quiet. This deliberately differs from the legacy converter's exit/log conventions. There is no log file to become stale. Broken stdout writes also return an I/O failure.

Optional limits are nonnegative decimal platform `usize` values:

```text
--max-bytes N          # default 67108864, complete input/output
--max-string-bytes N   # default 1048576, per decoded string
--max-items N          # default 1000000, aggregate elements
--max-owned-bytes N    # default 134217728, codec ownership/scratch accounting
```

The same limits apply to input parsing and output encoding; verbose TXT output can exceed the byte cap even if its DAT source fit. Auto detection uses a small buffered prefix; byte limits constrain codec buffers/file size, not blocking-I/O duration or process RSS. See codec documents for precise memory accounting. Lowering limits can intentionally reject otherwise structurally valid files. No automatic property optimization, dropped arrays, Unicode replacement, texture conversion or geometry synthesis takes place.

## Publication and source protection

Conversion parses and fully encodes before opening any destination temporary. Existing outputs are refused by default. `--force` authorizes replacement of a separate regular file; **same-file paths and hard links to the input are always rejected on macOS/Linux**, and output symlinks/directories are rejected. The input path is not written by conversion. Tests use disposable destinations and never choose reference-corpus paths as outputs.

After validation, the CLI creates an exclusively owned temporary in the destination's existing parent directory, writes all bytes, syncs the file and closes it. New Unix files are private (mode 0600); replacement also takes that mode rather than inheriting old permissions. Default publication uses a same-directory hard link, atomically failing if the destination appeared concurrently. `--force` uses same-directory rename for atomic replacement. Temporary cleanup is best effort through an ownership guard, including error paths. Staged names are bounded to 100 exclusive-create attempts; no preexisting temporary is deleted.

A filesystem without same-directory hard-link support returns an error for no-clobber publication rather than falling back to a partial visible output. File synchronization plus publication provides atomic visibility; directory metadata is not separately synced, so full power-loss durability is not claimed. The workflow assumes the caller's parent directory is trusted against hostile concurrent directory/symlink replacement. It is not an openat-based sandbox. Filesystem interruption/disk-failure matrices remain future work.

Incompatible exports fail before publication: for example, Unicode TXT→ASCII DAT, property-bearing data→old DAT, DAT duplicate names→TXT, non-finite floats and inconsistent computed arrays. There is no silent drop or replacement option. Existing output bytes remain untouched on parser/encoder failure even with `--force`.

## Tests

Eight asset-free unit tests cover options, limits, inspection, both DAT directions, Unicode paths, overwrite/same-file/hard-link/symlink guards, validation before any output, preservation of existing destinations after incompatible conversion, exclusive publication races and temporary cleanup. Two process integration tests check documented exit streams/codes and invoke the real binary for inspect, DAT→TXT, validate and TXT→DAT against **all 58 DAT outputs** (232 successful commands), verifying exact final DAT bytes. Fixture tests fail when assets are missing. See [E04](experiments/txt-cli-2026-10-21.md).