<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Cradicle Explorer</title>
    <link href="/css/bootstrap/bootstrap.min.css" rel="stylesheet">
    <style>
      .form-control-dark::placeholder {
          color: #aaa;
          opacity: 1;
      }
    </style>
    <link rel="stylesheet" href="/assets/fontawesome/css/all.min.css">
    <link rel="icon" type="image/png" href="/favicon.png">


                <link href="/css/dashboard.css" rel="stylesheet">
                </head>
                <body>
                <header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
                  <a class="navbar-brand col-md-3 col-lg-2 me-0 px-3 fs-6" href="/">Cradicle Explorer</a>
                  <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                  </button>
                  <form method="get" action="/cgi-bin/main" style="width:100%;"><input class="form-control form-control-dark w-100 rounded-0 border-0" type="text" name="q" placeholder="Search repos" aria-label="Search"></form>
                  <div class="navbar-nav flex-row">
                    <div class="nav-item text-nowrap">
                      <a class="nav-link px-3 active" href="/cgi-bin/repo?id=zvW6FTUiKUNEZvw23N7eoGYfjvVG">dynostic</a>
                    </div>
                  </div>
                </header>
                <div class="container-fluid">
                  <div class="row">
                    <nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-dark sidebar collapse">
                      <div class="position-sticky pt-3 sidebar-sticky">
                        <ul class="nav flex-column">
                          <li class="nav-item">
                            <a class="nav-link active" href="/cgi-bin/repo?id=zvW6FTUiKUNEZvw23N7eoGYfjvVG">
                              <i class="align-text-bottom fa-solid fa-info"></i>
                              Info
                            </a>
                          </li>
                          <li class="nav-item">
                            <a class="nav-link" href="/cgi-bin/repo?id=zvW6FTUiKUNEZvw23N7eoGYfjvVG&issue=list">
                              <i class="align-text-bottom fa-solid fa-layer-group"></i>
                              Issues
                            </a>
                          </li>
                          <li class="nav-item">
                            <a class="nav-link" href="/cgi-bin/repo?id=zvW6FTUiKUNEZvw23N7eoGYfjvVG&patch=list">
                              <i class="align-text-bottom fa-solid fa-vest-patches"></i>
                              Patches
                            </a>
                          </li>
                          <li class="nav-item">
                            <a class="nav-link" href="/cgi-bin/repo?id=zvW6FTUiKUNEZvw23N7eoGYfjvVG&wallet=list">
                              <i class="align-text-bottom fa-solid fa-wallet"></i>
                              Wallets
                            </a>
                          </li>
                          <li class="nav-item">
                            <a class="nav-link" href="/cgi-bin/repo?id=zvW6FTUiKUNEZvw23N7eoGYfjvVG&source=.">
                              <i class="align-text-bottom fa-solid fa-code"></i>
                              Source
                            </a>
                          </li>
                        <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
                          <span></span>
                        </h6>
                        <ul class="nav flex-column mb-2">
                        
                        </ul>
                      </div>
                    </nav>
                <main class="col-md-9 ms-sm-auto col-lg-10">
                  <div class="container px-1 py-3">
        

    <div class="list-group">
    <div class="list-group-item">
    <div style="font-size:1.3rem;">dynostic</div>
    <div class="repo-item">A reusable **deterministic tactical-sim core** in **Rust** with a **LÖVE (LuaJIT) front-end**.</div>
    <div>rad:zvW6FTUiKUNEZvw23N7eoGYfjvVG</div>
    </div>
    <div class="list-group-item">
    <div>Visibility</div>
    <div class="repo-item">public</div>
    </div>
    <div class="list-group-item">
    <div>Delegates</div><div class="repo-item">did:key:z6MkvqpvdaA8YpMzn5sQ67PChE3jRdzQQ69Qmco73HBtyUpm</div>
    </div>
    <div class="list-group-item">
    <div>Default branch</div>
    <div><span class="repo-item">main &#8594 44d58065dcb9b56d512541b38dc3a036281d119e</span> (Sun Mar  1 08:03:56 2026)</div>
    </div>
    <div class="list-group-item">
    <div>Threshold</div>
    <div class="repo-item">1</div>
    </div>
    </div>
    
        <div class="list-group mt-3">
        <div class="list-group-item">
        <div class="mb-2" style="font-weight:bold;"><i class="fa-solid fa-book"></i> README.md</div>
        <pre style="margin:0; font-size:0.85rem; overflow-x:auto; color:#fafafa;">![dynostic](dynostic.png)

A reusable **deterministic tactical-sim core** in **Rust** with a **LÖVE (LuaJIT) front-end**.

The goal: build simultaneous turn-based” combat loop games by reusing the *simulation + data pipeline*, while iterating quickly on *feel/UI* in LÖVE.

## Architecture
```
dynostic/
  rust/                 # Cargo workspace
    dynostic_core/      # pure deterministic simulation (no I/O, no rendering)
    dynostic_ffi/       # C ABI boundary (cdylib) for LuaJIT FFI
    dynostic_cli/       # tiny dev CLI (optional utilities / sanity checks)
  love/                 # LÖVE project (UI, rendering, input)
    main.lua
    dynostic.lua        # Lua wrapper around the Rust FFI
    native/             # place compiled native libs here (per platform)
```

### Design principles
- **Deterministic**: fixed-tick simulation, integer math, seeded RNG.
- **Event-sourced**: sim emits compact events; UI consumes events to animate.
- **FFI boundary stays dumb**: only POD types (ints, pointers, lengths), handle-based API.
- **Data-driven**: content can be loaded/compiled without changing engine code.

## Compatibility contract
See `docs/compatibility_contract.md` for ABI/schema/pack compatibility rules and migration policy.

## Documentation
Start with the guide at `docs/guide/README.md`.

## Quick start (dev)
### 1) Build the Rust native library
From repo root:
```bash
make build-native
```

This compiles `dynostic_ffi` and copies the resulting dynamic library into:
```
love/native/&lt;platform&gt;/
```

### 2) Run the LÖVE project
```bash
love love
```

You should see a small window with a moving dot driven by the Rust sim.

## Projection mode (prototype)
The LÖVE front-end defaults to orthographic projection. To try isometric projection:
```bash
export DYNOSTIC_PROJECTION=isometric
love love
```

Optional isometric tile height override (default is `tile_size / 2`):
```bash
export DYNOSTIC_ISO_TILE_H=20
love love
```

## New game scaffold
Generate a pack skeleton from the reference template:
```bash
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- new &quot;My Game&quot; --out games
```

The reference game lives at `examples/reference_game`.

## Profiling (headless)
Use the CLI to print hot-path counters (pathfinding, LOS, ability targeting, etc.):
```bash
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- --ticks 200 --profile
```

## Packaging
Build a portable `.love` archive and a platform folder with native libs:
```bash
make package-love
make package
```

## Shipping (reproducible + signed)
Deterministic `.love` packaging respects `SOURCE_DATE_EPOCH`:
```bash
SOURCE_DATE_EPOCH=1700000000 make package
```

Build a release manifest (engine + packs) and verify it:
```bash
make release
make release-verify
```

Sign packs and release manifests with an ed25519 key:
```bash
# generate keys
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- pack keygen --out-public keys/pack.pub --out-secret keys/pack.secret

# sign a pack.json
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- pack sign --pack dist/pack/pack.json --key keys/pack.secret

# sign a release manifest
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- release sign --manifest dist/release_manifest.json --key keys/pack.secret
```

`make release` will auto-sign `dist/pack/pack.json` when `RELEASE_SIGN_KEY` is set.
Set `RELEASE_ALLOW_UNSIGNED=1` to bypass pack signature checks (not recommended).

Auto-update check (local manifest only): set
`DYNOSTIC_UPDATE_MANIFEST=dist/release_manifest.json` before running the app to surface update
availability in the debug overlay.

## Mods (experimental)
Mods live under `mods/` and are enabled via `mods/mods.json`. Enabled mods are merged **after** the base pack.

Common commands:
```bash
# generate integrity hashes for a pack
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- pack hash --pack love/content/pack.json

# verify integrity hashes
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- pack verify --pack love/content/pack.json

# add/enable/disable mods
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- mod add --id cool_mod --path mods/cool_mod/pack.json --enable
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- mod list
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- mod disable cool_mod
```

Script permissions are gated by `pack.permissions.scripts` (default false). Set it to true in a pack to allow campaign scripts.

## Campaign lint + scripted runs
Validate campaign structure and branching requirements:
```bash
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- campaign lint --pack games/my_game/pack.json
```

Run a deterministic scripted campaign path and emit a final-state snapshot:
```bash
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- \
  campaign run --pack games/my_game/pack.json \
  --script games/my_game/tools/campaign_route_left_alliance.json \
  --out /tmp/campaign_left_snapshot.json
```

You can also audit asset handoff status (missing keys, placeholders, duplicate paths):
```bash
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- asset audit --pack games/my_game/pack.json
```

## Pack cache (compiled content)
Compile a pack into a deterministic cache blob for faster loads:
```bash
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- pack cache --pack love/content/pack.json
```

The runtime will use `pack.cache.json` automatically when present. Set `DYNOSTIC_PACK_CACHE=0` to disable.

## Cutscenes (prototype)
Validate and run a deterministic cutscene timeline:
```bash
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- cine validate --timeline tools/cutscene_demo.json
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- cine run --timeline tools/cutscene_demo.json
```

Branching + world effects example:
```bash
# choices file can be a JSON array like [0, 1] or { &quot;choices&quot;: [0, 1] }
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- cine run --timeline tools/cutscene_branching.json --choices /tmp/cine_choices.json
```

Snapshot and resume:
```bash
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- cine save --timeline tools/cutscene_branching.json --ticks 2 --out /tmp/cine_snapshot.json
cargo run -p dynostic_cli --manifest-path rust/Cargo.toml -- cine resume --snapshot /tmp/cine_snapshot.json
```

`cine run` outputs `world_effects` so you can apply them at cutscene end (or ignore them for preview).

## Atmosphere (presentation-only)
Encounters can specify a biome or explicit atmosphere settings:
```json
{
  &quot;biome&quot;: &quot;wasteland&quot;,
  &quot;atmosphere&quot;: {
    &quot;preset&quot;: &quot;dusty&quot;,
    &quot;events&quot;: [
      { &quot;kind&quot;: &quot;time&quot;, &quot;from&quot;: 0, &quot;to&quot;: 200, &quot;start&quot;: 0.2, &quot;finish&quot;: 0.7 },
      { &quot;kind&quot;: &quot;fog&quot;, &quot;from&quot;: 120, &quot;to&quot;: 300, &quot;start&quot;: 0.1, &quot;finish&quot;: 0.35 }
    ]
  }
}
```

Presets include `clear`, `mist`, `dusk`, `night`, `storm`, `dusty`, and `smoky`.

## Cutscene Preview (LÖVE)
Launch the app and press `F7` to enter the cutscene preview tool. By default it loads `tools/cutscene_branching.json`.

Controls:
- `Space` play/pause, `Left/Right` step tick, `Shift+Left/Right` jump
- `1-9` choose options when a choice prompt is active
- `C` clear choices, `R` reload timeline, `L` cycle locale, `X` export preview bundle
- `[` / `]` adjust tick rate

Set `DYNOSTIC_CINE` to load a different timeline. Exported bundles go to `cine_preview_bundle.json` or `DYNOSTIC_CINE_BUNDLE`.

Localization + VO demo:
```bash
export DYNOSTIC_CINE=tools/cutscene_localized.json
export DYNOSTIC_CINE_LANG=es
```

`say` events accept `text_id` (localization key), optional `voice`, `portrait`, and `lip` frames; localization tables can live under `locales` in the timeline.

Camera macro demo:
```bash
export DYNOSTIC_CINE=tools/cutscene_macros.json
```

Macro events expand into ordinary `camera`/`fade` ops at load time, so replays stay simple. Each macro needs `x`, `y`, `zoom`, plus optional `duration` and a `preset` (`soft` or `bold`) to fill defaults.

Supported macros and parameters:
- `push_in`: `distance`, `offset_x`, `offset_y`
- `snap_zoom`: `delta`
- `smash_cut`: `duration`
- `handheld_sway`: `amplitude`, `steps`, `zoom_jitter`
- `rack_focus`: `delta`, `fade`

## Offline replay rendering (frames/video)
Render a replay into PNG frames (and optionally encode a video):
```bash
# render frames into render_frames/
DYNOSTIC_RENDER=1 DYNOSTIC_RENDER_REPLAY=../tools/golden/replay_combat.json make run

# render frames and run ffmpeg
DYNOSTIC_RENDER=1 DYNOSTIC_RENDER_REPLAY=../tools/golden/replay_combat.json DYNOSTIC_RENDER_FFMPEG=ffmpeg make run
```

Common options (env or CLI):
- `DYNOSTIC_RENDER_OUT` / `--render-out=`
- `DYNOSTIC_RENDER_PREFIX` / `--render-prefix=`
- `DYNOSTIC_RENDER_STEP=event|tick` / `--render-step=`
- `DYNOSTIC_RENDER_FROM` / `--render-from=` (event or tick index)
- `DYNOSTIC_RENDER_TO` / `--render-to=` (event or tick index)
- `DYNOSTIC_RENDER_SIZE=WxH` / `--render-size=` (or `--render-width=` / `--render-height=`)
- `DYNOSTIC_RENDER_HIDE_UI=0` / `--render-show-ui`
- `DYNOSTIC_RENDER_PREVIEW=1` / `--render-preview`
- `DYNOSTIC_RENDER_CAMERA` / `--render-camera=` (camera override JSON)
- `DYNOSTIC_RENDER_FFMPEG=ffmpeg` / `--render-ffmpeg`

Each run writes a manifest with per-frame hashes at:
```
render_frames/frame_manifest.json
```

Camera override example (fixed):
```json
{
  &quot;x&quot;: 7,
  &quot;y&quot;: 5,
  &quot;zoom&quot;: 1200
}
```

Camera override example (shot list by event index):
```json
{
  &quot;shots&quot;: [
    { &quot;from&quot;: 1, &quot;to&quot;: 120, &quot;x&quot;: 6, &quot;y&quot;: 5, &quot;zoom&quot;: 1200 },
    { &quot;from&quot;: 121, &quot;to&quot;: 200, &quot;entity_id&quot;: 3, &quot;zoom&quot;: 1100 }
  ],
  &quot;default&quot;: { &quot;x&quot;: 7, &quot;y&quot;: 5, &quot;zoom&quot;: 1000 }
}
```

## Visual regression (golden frames)
Render golden frames and compare in CI:
```bash
make visual-record
make visual-check
```

The harness reads `tools/golden_manifest.json` and renders replay bundles only. Output layout:
- Golden frames: `tools/golden_frames/&lt;label&gt;/`
- New runs: `tools/visual_runs/&lt;label&gt;/`
- Diffs: `tools/visual_diffs/&lt;label&gt;/` and `tools/visual_diffs/summary.json`

Limit to specific bundles:
```bash
python3 tools/visual_regression.py check --label combat_basic --label stealth_smoke
python3 tools/visual_regression.py check --tag stealth
```

## Notes on distribution
Dynamic libraries cannot be loaded from inside a zipped `.love` archive. For distribution, ship platform builds (fused or not) with the correct native library file next to the executable/app bundle and load it via an absolute path.
</pre>
        </div>
        </div>

</div>
</main>
</div>
</div>


</body>
</html>

