Compare commits

...

7 Commits

Author SHA1 Message Date
Rijad Zuzo 4ff8858b19 feat(characters): add modular woodland traveler kit 2026-09-05 22:54:54 +02:00
Rijad Zuzo 70ee6feddf chore(godot): finalize sky import and project settings 2026-09-05 20:40:33 +02:00
Rijad Zuzo ec16e2f2ca feat(environment): add painterly skies and living meadows 2026-09-05 20:27:09 +02:00
Rijad Zuzo 7899d36f03 feat(foliage): add recovering grass trails and bush contact 2026-09-05 19:30:46 +02:00
Rijad Zuzo da3bed67a0 feat(art): add storybook meadow and woodland villagers 2026-09-05 18:56:44 +02:00
Rijad Zuzo 6aae41d36f docs: add graph-backed agent guide 2026-08-31 00:09:36 +02:00
Rijad Zuzo 52307f1306 feat: integrate regional caravan persistence 2026-08-23 17:20:41 +02:00
196 changed files with 8261 additions and 695 deletions
+178 -18
View File
@@ -3,11 +3,58 @@
This repository is a Godot 4.7 simulation prototype. Treat it as a living This repository is a Godot 4.7 simulation prototype. Treat it as a living
systems project, not a content dump. The expected working style is: systems project, not a content dump. The expected working style is:
## Start here and sources of truth
Begin with `docs/README.md`, then follow it to `docs/DEVELOPER_INDEX.md`. The
developer index is the shortest current map from a feature request to its code,
focused contract, tests, and known incomplete work.
When sources disagree, use this order:
1. Current code and focused tests establish implemented behavior; they do not
silently supersede an architectural decision.
2. Applicable Accepted records in `docs/decisions/` define durable architecture
within their stated scope until another Accepted record explicitly
supersedes them. Treat conflicting code as drift to fix or document.
`docs/ARCHITECTURE_OVERVIEW.md` is the current ownership/dependency map.
3. `docs/DEVELOPER_INDEX.md` and the focused architecture/schema guides define
the current documented contract.
4. `docs/LEARNING_ROADMAP.md` defines sequencing, not runtime truth.
Fix the smallest authoritative document after changing behavior. Do not create
a second implementation in prose or copy the same status table into many docs.
## Codebase discovery with codebase-memory
This repository is indexed as
`Users-rijadzuzo-dev-private-gamedev-the-steward` in codebase-memory. Treat the
name as a lookup hint, not proof that the graph is current.
- At session start, after a pull, and after changing branches, call
`list_projects` and `index_status`. Confirm the indexed root, branch, and HEAD
match the checkout; re-index after a large or external update.
- Use Verify/Tier 2 evidence by default: `search_graph` to find exact symbols,
`trace_path` in the material direction, and `get_code_snippet` for source.
Use `get_architecture` only for orientation, not as a substitute for exact
code.
- Check `has_more`/cursors and paginate relevant results. After candidate files
are known, call `check_index_coverage` once with every evidence path.
- A clean coverage result is best-effort, not proof of completeness. Read exact
source for partial, skipped, excluded, stale, pending, or unknown ranges.
- Use `rg` for string literals, resource paths, scene/config files, generated
data, and graph coverage gaps. Do not make negative or exhaustive claims from
a provisional graph search.
- Before a refactor, use `detect_changes` or inbound traces to inspect the blast
radius. Re-run focused searches after edits rather than relying on this file
as a frozen graph dump.
## Default development loop ## Default development loop
1. Read the relevant roadmap/docs before changing code. 1. Read the relevant roadmap/docs before changing code.
- Start with `docs/README.md`. - Start with `docs/README.md` and `docs/DEVELOPER_INDEX.md`.
- Use `docs/LEARNING_ROADMAP.md` for the current next item. - Use `docs/DEVELOPER_INDEX.md` for current status and open gaps.
- Use `docs/LEARNING_ROADMAP.md` for milestone sequencing and exit tests;
reconcile any candidate slice with current code and focused tests.
- Use focused plans such as `docs/RESOURCE_NODE_MIGRATION.md` only within - Use focused plans such as `docs/RESOURCE_NODE_MIGRATION.md` only within
their stated scope. their stated scope.
2. Inspect the current code and tests before assuming roadmap status is still 2. Inspect the current code and tests before assuming roadmap status is still
@@ -37,7 +84,7 @@ Use the project quality gate for the current platform:
powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1 powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1
``` ```
For changed-file quick checks: To narrow formatter/linter scope to changed GDScript files:
```bash ```bash
./tools/quality.sh --changed ./tools/quality.sh --changed
@@ -47,18 +94,69 @@ For changed-file quick checks:
powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1 -Changed powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1 -Changed
``` ```
`--changed` narrows only the `gdformat` and `gdlint` file set. It still runs the
Godot import/parser check, every headless scenario, the compatibility-renderer
scenario, and GUT. Use it only while iterating; run the full current-platform
gate before every commit.
The quality scripts isolate Godot's user profile under `logs/quality/godot_profile` The quality scripts isolate Godot's user profile under `logs/quality/godot_profile`
so headless Godot 4.7 can run without crashing when platform user-data paths so headless Godot 4.7 can run without crashing when platform user-data paths
are unavailable. Do not remove that behavior. are unavailable. Do not remove that behavior.
Useful extra checks: Useful extra checks:
```powershell ```bash
git diff --check git diff --check
``` ```
If touching `main.tscn` or runtime wiring, also do a direct Godot 4.7 headless If changing `project.godot`, `main.tscn`, autoload/plugin configuration, scene
boot when practical. UIDs or node paths, or startup/runtime scene wiring, also run the configured
main scene with the same Godot 4.7 binary used by the gate:
```bash
"$GODOT_BIN" --headless --path "$PWD" --quit-after 3
```
```powershell
& $env:GODOT_BIN --headless --path (Get-Location).Path --quit-after 3
```
Set `GODOT_BIN` when the gate auto-detected a binary instead. Inspect output as
well as exit status, and accept only diagnostics matching the quality gate's
exact allowlist. Read failures from `logs/quality/latest/`; do not weaken an
allowlist or skip a failing scenario merely to make the gate green.
## Current codebase map
Keep this map concise; `docs/DEVELOPER_INDEX.md` owns the detailed feature
matrix and extension workflow.
- `project.godot` starts `main.tscn`. The main scene wires the authored Jajce
world, player, `ActiveWorldAdapter`, `SimulationManager`,
`SaveSlotController`, `WorldViewManager`, and UI surfaces.
- `simulation/SimulationManager.gd` is the scene-tree facade and deterministic
tick boundary. It advances regional authority before committing the local
tick, then coordinates focused systems rather than duplicating their rules.
- `simulation/actions/`, `simulation/commands/`, and `simulation/economy/` own
selection/execution, target resolution, shared player/NPC command contracts,
and exact inventory or storage transactions.
- `simulation/events/`, `simulation/knowledge/`, `simulation/relationships/`,
`simulation/opportunities/`, `simulation/situations/`,
`simulation/dialogue/`, and `simulation/quests/` turn completed mutations
into causal facts and derived player-facing projections.
- `simulation/state/` owns versioned primitive records;
`simulation/definitions/` owns immutable definitions and stable IDs;
`simulation/regional/` owns scheduled distant work and caravan authority;
`simulation/persistence/` owns combined manifests and safe slot replacement.
- `world/active_world_adapter.gd` and `world/targets/` own contextual loaded-
world discovery, descriptors, capabilities, and transient handles. Resource,
storage, activity, and animal nodes are providers bound to simulation state.
- `world/world_view_manager.gd`, `world/presentation/`, `world/jajce/`,
`world/ui/`, and `player/` own input, geometry, navigation, and visible
feedback. They submit commands or report facts; they do not author saves.
- `tests/` contains deterministic headless scenarios and GUT tests;
`simulation/benchmark/` and `docs/benchmarks/` contain reproducible workload
evidence; `tools/` contains the cross-platform quality gate.
## Architecture direction ## Architecture direction
@@ -73,12 +171,29 @@ Preserve the simulation/presentation boundary.
`ActionTargetResolver`. `ActionTargetResolver`.
- Visual movement belongs to `WorldViewManager`/`NpcVisual`; decision and task - Visual movement belongs to `WorldViewManager`/`NpcVisual`; decision and task
execution belong to the simulation systems. execution belong to the simulation systems.
- Player and NPC callers use the same commands and capabilities. Revalidate the
actor, target, context, range, revision, cost, and permission at execution;
mutate atomically before recording facts or deriving projections.
- Target handles, generation tokens, spatial/population indexes, presentation
cues, and reason traces are disposable derived state. Persist stable
contextual IDs and primitives, then prove rebuilding derived state preserves
decisions and checksums.
- A local tick may advance only after the regional facade succeeds. Regional
work must remain deterministic, bounded, fail-closed, and rollback-safe.
- When regional authority exists, save and restore must use the combined
manifest and remain all-or-nothing. Preserve schema validation, size bounds,
restore rollback, temporary-file validation, and the atomic
temporary/backup/final replacement path; a failed restore emits no success
notification.
When adding a system, first prove the contract with one real gameplay use case. When adding a system, first prove the contract with one real gameplay use case.
Do not extract generic frameworks before multiple real consumers justify them. Do not extract generic frameworks before multiple real consumers justify them.
## Emergent-world doctrine ## Emergent-world doctrine
These agent-facing guardrails summarize ADR 0001 and ADR 0002. The Accepted
records remain authoritative within their stated scopes.
The game world is the source of narrative truth. Dialogue, tasks, quests, and The game world is the source of narrative truth. Dialogue, tasks, quests, and
visible happenings must arise from ordinary simulation state and events rather visible happenings must arise from ordinary simulation state and events rather
than maintaining parallel scripted copies. than maintaining parallel scripted copies.
@@ -134,14 +249,16 @@ behaviour, not as a one-off object. Add a new type through data/definitions
plus a small behaviour or visual hook, reusing the root for movement, plus a small behaviour or visual hook, reusing the root for movement,
presentation, serialization, and lifecycle: presentation, serialization, and lifecycle:
- resources: `ResourceNode` + `ResourceStateRecord` (amount, yield, regrowth); - resources: immutable resource definitions plus authoritative
- creatures: `CreatureVisual` shared movement root (navigate to the `ResourceStateRecord`; `ResourceNode` is the loaded provider and visual
authoritative simulation position, report position, death), with per-type binding;
visuals built from definitions; - creatures: `CreatureVisual` is the shared navigation/death presentation root;
- enemies: `EnemyDefinition` + `SimulationEnemies` registry driving combatant - enemies: `EnemyDefinition`/`SimulationEnemies` provide typed content,
spawns and hostile visuals; `CombatantFactory` and `ConflictSystem` own authoritative instances and
- animals: `AnimalNode` + `AnimalStateRecord` following the same lifecycle, and `HostileCombatant` presents them;
follow-the-authoritative-position contract. - animals: `AnimalDefinition`, `AnimalStateRecord`, `AnimalFactory`, and
`AnimalCareSystem` own content and lifecycle; `AnimalNode` is the loaded
interaction/navigation binding.
A new berry, tree, bear, bandit, boar, or goat should be mostly a definition A new berry, tree, bear, bandit, boar, or goat should be mostly a definition
plus a bounded hook — never a new movement/combat/save system. plus a bounded hook — never a new movement/combat/save system.
@@ -159,6 +276,19 @@ Resource additions should preserve:
Storage uses `StorageNode`. Rest, study, and patrol use `ActivitySite`. Do not Storage uses `StorageNode`. Rest, study, and patrol use `ActivitySite`. Do not
turn these back into generic marker zones. turn these back into generic marker zones.
## Vendored dependencies and Godot asset hygiene
- Treat `addons/dialogue_manager`, `addons/gut`, and `addons/terrain_3d` as
pinned vendor code. Do not reformat, refactor, or update them unless the task
explicitly owns that dependency.
- Preserve the complete cross-platform Terrain3D payload checked by the quality
gate. Do not replace a missing platform binary with a local-only artifact.
- Preserve `.uid` files, `class_name` locations, resource UIDs, and scene paths
when moving Godot scripts or scenes. Use `git mv` for intentional moves and
prove them with import plus headless startup.
- Never commit `.godot/`, `.venv/`, `logs/quality/`, or generated local editor
state. Do not hand-edit imported cache files to hide an error.
## Testing expectations ## Testing expectations
Add or update headless scenarios when a change affects: Add or update headless scenarios when a change affects:
@@ -169,19 +299,34 @@ Add or update headless scenarios when a change affects:
- navigation/reachability assumptions; - navigation/reachability assumptions;
- player/NPC parity; - player/NPC parity;
- deterministic continuation; - deterministic continuation;
- regional job ordering, rollback, or local/regional tick coordination;
- combined-manifest save/restore, migration, or atomic slot behavior;
- event causality, knowledge, situation, dialogue, or quest projections;
- UI/debug state that represents real simulation facts. - UI/debug state that represents real simulation facts.
Keep tests deterministic. Prefer fixed seeds and stable IDs. Keep tests deterministic. Prefer fixed seeds and stable IDs.
Any persistent-state change requires an explicit schema/migration decision,
exact validation, checksum and tamper coverage, rollback coverage, and a
deterministic continuation test.
## Documentation expectations ## Documentation expectations
Update docs when the implemented behavior changes the roadmap, architecture, or Update docs when the implemented behavior changes the roadmap, architecture, or
current contract. Keep updates local: current contract. Keep updates local:
- `docs/DEVELOPER_INDEX.md` for the current feature map, extension routes, and
deliberately incomplete work;
- `docs/ARCHITECTURE_OVERVIEW.md` for ownership and dependency boundaries;
- `docs/PROJECT_CONTEXT.md` only when product intent or active constraints
change; dated implementation passages are historical context;
- `docs/LEARNING_ROADMAP.md` for next-item sequencing and milestone status; - `docs/LEARNING_ROADMAP.md` for next-item sequencing and milestone status;
- `docs/RESOURCE_NODE_MIGRATION.md` for resource-target migration status; - `docs/RESOURCE_NODE_MIGRATION.md` for resource-target migration status;
- `docs/SIMULATION_STATE_SCHEMA.md` for serialized state changes; - `docs/SIMULATION_STATE_SCHEMA.md` for serialized state changes;
- `docs/SIMULATION_DEFINITIONS.md` for action/profession/ID contracts; - `docs/SIMULATION_DEFINITIONS.md` for action/profession/ID contracts;
- `docs/ACTION_SYSTEM_ARCHITECTURE.md`, `docs/ECONOMIC_EVENTS.md`, and
`docs/REGIONAL_SIMULATION.md` for their focused runtime contracts;
- `docs/FEATURE_*.md` only when the corresponding implementation/extension
guide changes;
- `docs/BUILD_IN_PUBLIC_PLAN.md` for Jajce/demo/readability slices; - `docs/BUILD_IN_PUBLIC_PLAN.md` for Jajce/demo/readability slices;
- `docs/decisions/` only for durable architectural decisions. - `docs/decisions/` only for durable architectural decisions.
@@ -190,16 +335,31 @@ trust code/tests first, then update docs.
## Git expectations ## Git expectations
Preserve user changes. Check `git status --short` before editing and before Preserve user changes. Check `git status --short --branch` before editing and
committing. Do not use destructive cleanup commands unless explicitly asked. before committing, inspect the branch/upstream and recent history, and stage
only the files owned by the current slice. Do not use destructive cleanup
commands unless explicitly asked.
Use conventional commits, for example: Every repository commit must use Conventional Commits with an imperative,
specific summary: `<type>(optional-scope): summary`. Common types are `feat`,
`fix`, `docs`, `test`, `refactor`, `perf`, `build`, `ci`, `chore`, and `merge`.
Examples:
- `feat: validate expanded resource discovery` - `feat: validate expanded resource discovery`
- `fix: stabilize godot 4.7 headless validation` - `fix: stabilize godot 4.7 headless validation`
- `docs: capture agent workflow` - `docs: capture agent workflow`
Commit only after validation relevant to the change has passed. Before committing, review `git diff`, run `git diff --check` and the full
current-platform quality gate, then review `git diff --cached`. Validation is
explicit; do not assume a local hook ran it. One coherent vertical slice may
include its implementation, deterministic tests, required `.uid`/resource
files, and smallest necessary documentation update. Split unrelated fixes,
never stage pre-existing user work, and never use `--no-verify` to bypass a
repository gate.
Do not amend, rebase, drop, or fold user-owned commits unless explicitly asked.
Do not push implicitly. After committing, report the commit hash and the real
ahead/behind or remote-sync state.
## Product taste ## Product taste
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+116
View File
@@ -0,0 +1,116 @@
# Woodland character kit
Original Blender geometry inspired by the supplied cat-traveler reference:
broad cheeks, relaxed almond eyes, whiskers, layered clothing, rolled cuffs,
boots, a scarf, and a travel pack with a bedroll. The reference image is not
embedded in the assets. Player and NPC visuals use this kit in the main game.
## Try and configure
Run `tools/CharacterWorkshop.tscn` in Godot 4.7 (F6), or:
```bash
/Applications/Godot.app/Contents/MacOS/Godot --path "$PWD" tools/CharacterWorkshop.tscn
```
Choose one of six presets, change the parts and proportions, drag the model to
turn it, scroll over the model to zoom, and enable the walking preview. **Save
profile** writes a `CharacterAppearanceProfile` `.tres` to the chosen file.
Assign that resource to `PlayerVisual.appearance` or `NpcVisual.appearance` in
the Inspector. The player defaults to `profiles/reference_cat.tres`. NPCs with
no authored override derive their appearance from their stable simulation ID.
The workshop is a separate authoring scene; it does not modify a game save.
| Control | Available values |
| --- | --- |
| Head and tail | Cat, fox, rabbit, badger, otter |
| Ears | Species default, or any of six ear pairs including lop ears |
| Height | 0.80–1.15 times the base height |
| Body fullness | 0–1 Blender `Stout` shape key |
| Head size / leg length / ear length | 0.88–1.12 / 0.85–1.15 / 0.65–1.20 |
| Clothing | Open vest, long coat, work tunic with apron |
| Arms | Relaxed or holding pack straps |
| Accessories | Scarf, spectacles, beret, brimmed hat, satchel, travel pack |
| Palette | Fur, muzzle, outfit, shirt, scarf, trousers, leather |
The workshop exposes the four main palette colors. All seven channels are
editable on the profile resource. Profession tint remains a separate, subtle
overlay driven by the actual NPC profession.
## Blender source and rebuilding
Open `art/characters/woodland_character_kit.blend`. The visible collection is an
assembled traveler; the hidden library meshes contain the editable building
blocks. Unhide a library object to edit it. Torso and wearable meshes have
`Basis` and `Stout` shape keys. Preview objects are copies of library objects.
```bash
/Applications/Blender.app/Contents/MacOS/Blender --background \
--python tools/art/build_character_kit.py
/Applications/Godot.app/Contents/MacOS/Godot --headless --editor --path "$PWD" --import
```
The reproducible Blender 5.1 generator rebuilds the source, GLB, and triangle
manifest. Regenerating replaces manual edits to those generated files. Commit
changes to the generator when they should survive a rebuild. Godot only imports
the portable GLB; `.gdignore` excludes the Blender authoring directory.
## Mesh and animation contract
`woodland_parts.glb` contains 31 named parts, each with one opaque surface. The
reference cat uses 10 surfaces and 20,070 base triangles before Godot's generated
LODs. The GLB is approximately 4.6 MiB. `mesh_budget.json` records each part's
triangle count, origin and shape-key support. Accessories are optional; only
selected meshes enter the live scene.
- `AnimalAppearance` caches the imported mesh library once. Characters share
`ArrayMesh` resources and have their own shader material and blend-shape
weights. No per-frame mesh rebuilding or CPU deformation is performed.
- Godot Y is up, +Z is forward. The existing body, head, arm, leg and tail node
paths remain animation sockets. Ear/glasses attachments follow the head;
scarf and bag attachments follow the torso. `Hair` is the optional hat socket.
- Fullness deforms the torso and worn gear together. Shoulder/hip spacing follows
it. Leg length raises the upper body while keeping the soles on the ground.
Gait uses configured rest heights; the hiker pose has restrained arm swing.
- Animation remains the existing rigid-part procedural gait. The kit does not
add a skeletal animation library, facial rig, cloth simulation, or physics
bodies. Changes to these appearance controls are cosmetic: collision,
navigation, interaction reach, authoritative positions, and saves retain
their existing contracts. Very unusual mixed parts may need art adjustment.
- `UV2.x` stores a discrete palette role (0 fixed vertex paint, 1 fur, 2 muzzle,
3 outfit, 4 linen, 5 scarf, 6 trousers, 7 leather, 8 fur markings, 9 outfit seams).
Preserve both UV layers and vertex colors on export. Do not regenerate UV2
lightmap coordinates. `character_cel.gdshader` supplies soft cel bands with
no texture sampling, transparency, outline pass, or screen-space effect.
## Verification and review
`tests/character_appearance_test.gd` checks the real imported geometry, palette
roles, independent morph weights, shared meshes, attachment reuse, all six
presets, extreme proportions, stable IDs/RNG isolation, gait rest heights, and
NPC cue clearance. Existing lifecycle scenarios cover profession, inventory,
death, and save/load behavior. Run the full repository quality gate before a
commit.
```bash
./tools/quality.sh
/Applications/Godot.app/Contents/MacOS/Godot --path "$PWD" \
--rendering-driver metal --script tools/capture_character_kit.gd
/Applications/Godot.app/Contents/MacOS/Godot --path "$PWD" \
--rendering-method gl_compatibility --script tools/capture_character_kit.gd
/Applications/Godot.app/Contents/MacOS/Godot --path "$PWD" \
--rendering-driver metal --script tools/capture_character_kit.gd -- --world
```
The `docs/baselines/character_*.png` images are actual Godot renders. They show
the traveler, rear pack detail, preset lineup, workshop and main-world placement.
These local Apple M1 Max checks do not establish a frame-time budget for a
large crowd or a weaker GPU.
The workshop uses filmic tonemapping with a separately calibrated Compatibility
exposure to preserve cream-fabric highlights. This only stages the authoring
preview; the main world retains its existing day/night environment. Metal and
Compatibility images are separate visual reviews, not a pixel-parity claim.
The full Godot 4.7 gate passes 61 scenario runs (including 111 character checks)
and 182 GUT tests / 2,279 assertions. The native workshop controls and profile
export were also exercised directly.
+312
View File
@@ -0,0 +1,312 @@
{
"TorsoVest": {
"triangles": 2914,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"TorsoCoat": {
"triangles": 3074,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"TorsoTunic": {
"triangles": 1894,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"ArmRelaxedLeft": {
"triangles": 996,
"surfaces": 1,
"pivot": [
-0.44,
1.3,
0
],
"stout_shape": false
},
"ArmHikerLeft": {
"triangles": 2144,
"surfaces": 1,
"pivot": [
-0.44,
1.3,
0
],
"stout_shape": false
},
"LegLeft": {
"triangles": 1074,
"surfaces": 1,
"pivot": [
-0.2,
0.61,
0
],
"stout_shape": false
},
"ArmRelaxedRight": {
"triangles": 996,
"surfaces": 1,
"pivot": [
0.44,
1.3,
0
],
"stout_shape": false
},
"ArmHikerRight": {
"triangles": 2144,
"surfaces": 1,
"pivot": [
0.44,
1.3,
0
],
"stout_shape": false
},
"LegRight": {
"triangles": 1074,
"surfaces": 1,
"pivot": [
0.2,
0.61,
0
],
"stout_shape": false
},
"HeadCat": {
"triangles": 6798,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsCat": {
"triangles": 202,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailCat": {
"triangles": 584,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"HeadFox": {
"triangles": 6798,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsFox": {
"triangles": 202,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailFox": {
"triangles": 636,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"HeadRabbit": {
"triangles": 6684,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsRabbit": {
"triangles": 328,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailRabbit": {
"triangles": 440,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"HeadBadger": {
"triangles": 6684,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsBadger": {
"triangles": 1456,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailBadger": {
"triangles": 248,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"HeadOtter": {
"triangles": 6684,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsOtter": {
"triangles": 1456,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailOtter": {
"triangles": 360,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"EarsLop": {
"triangles": 328,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"Scarf": {
"triangles": 396,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"BagTravel": {
"triangles": 2740,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"BagSatchel": {
"triangles": 640,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"HatBeret": {
"triangles": 880,
"surfaces": 1,
"pivot": [
0,
1.9,
0
],
"stout_shape": false
},
"HatBrim": {
"triangles": 976,
"surfaces": 1,
"pivot": [
0,
1.9,
0
],
"stout_shape": false
},
"Spectacles": {
"triangles": 644,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
}
}
@@ -0,0 +1,17 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 3
height = 0.98
body_fullness = 1.0
leg_length = 0.9
outfit = 2
arm_pose = 0
bag = 0
hat = 1
scarf = false
fur_color = Color(0.79, 0.77, 0.71, 1)
outfit_color = Color(0.47, 0.5, 0.35, 1)
@@ -0,0 +1,16 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 1
height = 1.12
body_fullness = 0.15
leg_length = 1.12
outfit = 1
arm_pose = 0
hat = 2
bag = 1
fur_color = Color(0.72, 0.43, 0.24, 1)
outfit_color = Color(0.42, 0.49, 0.33, 1)
@@ -0,0 +1,17 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 2
ears = 4
height = 0.92
body_fullness = 0.8
ear_length = 1.12
outfit = 2
arm_pose = 0
bag = 0
scarf = false
fur_color = Color(0.80, 0.78, 0.73, 1)
outfit_color = Color(0.6, 0.4, 0.28, 1)
@@ -0,0 +1,15 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 4
height = 1.06
body_fullness = 0.42
head_size = 0.94
outfit = 0
arm_pose = 0
bag = 1
fur_color = Color(0.51, 0.42, 0.33, 1)
outfit_color = Color(0.41, 0.43, 0.54, 1)
@@ -0,0 +1,17 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 2
height = 0.84
body_fullness = 0.18
head_size = 1.08
leg_length = 0.92
outfit = 1
arm_pose = 0
bag = 1
spectacles = true
fur_color = Color(0.76, 0.72, 0.64, 1)
outfit_color = Color(0.57, 0.37, 0.31, 1)
@@ -0,0 +1,6 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
Binary file not shown.
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://b4p2tfex61t8o"
path="res://.godot/imported/woodland_parts.glb-1394516718c8f9a3ee6122be9d99f45f.scn"
[deps]
source_file="res://assets/characters/woodland_parts.glb"
dest_files=["res://.godot/imported/woodland_parts.glb-1394516718c8f9a3ee6122be9d99f45f.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
+21
View File
@@ -0,0 +1,21 @@
# Jajce meadow flowers
Original mesh assets built for The Steward. Four small clumps give authored
meadow drifts a cream, rose, butter-yellow and lavender palette. Each contains
three stems, folded leaves and opaque petal geometry with painted vertex colors.
There are no external textures, dependencies, collision shapes or gameplay IDs.
Editable sources live in `art/meadow/`, outside Godot's import tree. Rebuild with
Blender 5.1 or later:
```sh
Blender --background --python tools/art/build_meadow_flowers.py
```
`mesh_budget.json` records exported triangle/surface/file-size counts. Runtime
`PainterlyMeadow` uses one MultiMesh per species and its own soft-lit wind shader;
the GLB material preserves the original palette when opened in other tools.
Godot 4.7 Compatibility requires explicit white MultiMesh instance colors when
the custom instance buffer is enabled. `PainterlyMeadow` supplies that neutral
color so both Compatibility and Forward+ preserve the painted GLB palette.
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cnoywbu65c0hg"
path="res://.godot/imported/cream_daisy.glb-7a51f417838affb7d915e8a81cdabcc9.scn"
[deps]
source_file="res://assets/meadow/cream_daisy.glb"
dest_files=["res://.godot/imported/cream_daisy.glb-7a51f417838affb7d915e8a81cdabcc9.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dur42glc0ybm3"
path="res://.godot/imported/lavender_spire.glb-ca4666e5e14c84270d4621d49918d5e7.scn"
[deps]
source_file="res://assets/meadow/lavender_spire.glb"
dest_files=["res://.godot/imported/lavender_spire.glb-ca4666e5e14c84270d4621d49918d5e7.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
+26
View File
@@ -0,0 +1,26 @@
{
"cream_daisy": {
"triangles": 180,
"surfaces": 1,
"flower_stems": 3,
"bytes": 19896
},
"pink_cosmos": {
"triangles": 171,
"surfaces": 1,
"flower_stems": 3,
"bytes": 18888
},
"yellow_buttercup": {
"triangles": 153,
"surfaces": 1,
"flower_stems": 3,
"bytes": 16840
},
"lavender_spire": {
"triangles": 360,
"surfaces": 1,
"flower_stems": 3,
"bytes": 40444
}
}
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dqksrghyqlfec"
path="res://.godot/imported/pink_cosmos.glb-f2a2c19fa8aeebec56761be5c75e2426.scn"
[deps]
source_file="res://assets/meadow/pink_cosmos.glb"
dest_files=["res://.godot/imported/pink_cosmos.glb-f2a2c19fa8aeebec56761be5c75e2426.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://urkk7ybwrnv7"
path="res://.godot/imported/yellow_buttercup.glb-88c3511b4d2c884beab5a1c921cdc3cb.scn"
[deps]
source_file="res://assets/meadow/yellow_buttercup.glb"
dest_files=["res://.godot/imported/yellow_buttercup.glb-88c3511b4d2c884beab5a1c921cdc3cb.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
+59
View File
@@ -0,0 +1,59 @@
# Painterly valley assets
Original broadleaf and evergreen meshes created in Blender for The Steward.
Scalloped crowns, painted vertex variation, crooked forks and root flares replace
the primitive tree silhouettes. Editable sources are in `art/painterly/`;
`.gdignore` keeps Blender outside Godot's runtime import dependencies. The two
portable GLBs use opaque geometry without textures or alpha cards.
## Rebuild and mesh budget
From the project root, using Blender 5.1 and Godot 4.7:
```sh
"$BLENDER_BIN" --background --python tools/art/build_painterly_trees.py
"$GODOT_BIN" --headless --editor --path "$PWD" --import
```
Use the isolated Godot profile configured by `tools/quality.sh` for headless
validation. Runtime consumes the GLBs directly; no mesh-extraction step is
needed. Preserve their `.import` files when updating exports.
| Geometry | Triangles | Surfaces |
| --- | ---: | ---: |
| Shared broadleaf crown | 1,236 | 1 |
| Joined broadleaf trunk, forks and roots | 406 | 1 |
| Complete broadleaf: four crowns plus trunk | 5,350 | 5 |
| Complete conifer: boughs plus trunk | 1,580 | 2 |
`tree_mesh_budget.json` records these export counts. The broadleaf GLB is
45,676 bytes; the conifer GLB is 40,836 bytes and stands 3.10 m before placement
scaling. A separate temporary rebuild with Blender 5.1.2 produced byte-identical
copies of both GLBs and the budget JSON. This establishes generator
reproducibility with that Blender version, not identical `.blend` serialization
across versions.
`StylizedTree` shares imported meshes and isolates crown tint/wind materials.
Its four direct `Canopy*` children retain the existing thinning and stump
presentation contract. `PainterlyConifer` shares imported bough/trunk meshes.
Both use the same world-direction breeze and explicit painted bark colors;
neither adds simulation state, collision or per-frame GDScript work.
## Painted sky provenance
`painted_sky.png` is an original 1774 × 887 image generated with the built-in
ImageGen tool, rather than a CLI generator. The prompt asks for a painted
cumulus panorama with warm ivory cloud tops, cool turquoise cloud shadows and
a clear blue summer atmosphere. Film references informed the color and mood;
no external film image was copied into the texture. The exact generation
prompt is recorded in [sky_prompt.md](sky_prompt.md).
`storybook_sky.gdshader` maps the panorama's horizon at 69% of its height onto
the upper hemisphere, repeats the painted band twice around the azimuth to
retain cloud proportions, blends its seam/edge into haze, and follows the existing
day/night palette. The image supplies the sky backdrop only; it does not paint
terrain or create volumetric clouds.
Final renderer QA and the complete project quality gate belong to the
integrated environment review. See the [presentation guide](../../docs/FEATURE_PRESENTATION_PERFORMANCE.md)
for current verification evidence and remaining hardware limits.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

+41
View File
@@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dmkubk6dhhyis"
path.s3tc="res://.godot/imported/painted_sky.png-9888802278aff2f83effdab2649f1f05.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/painterly/painted_sky.png"
dest_files=["res://.godot/imported/painted_sky.png-9888802278aff2f83effdab2649f1f05.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://clcvatvbyixma"
path="res://.godot/imported/painterly_conifer.glb-596d48094a6a072cfc85374d478c7bbe.scn"
[deps]
source_file="res://assets/painterly/painterly_conifer.glb"
dest_files=["res://.godot/imported/painterly_conifer.glb-596d48094a6a072cfc85374d478c7bbe.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
Binary file not shown.
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://blu5r5vupqvpd"
path="res://.godot/imported/painterly_tree.glb-12cb20acdabe08bb0fe06cba12958067.scn"
[deps]
source_file="res://assets/painterly/painterly_tree.glb"
dest_files=["res://.godot/imported/painterly_tree.glb-12cb20acdabe08bb0fe06cba12958067.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
+35
View File
@@ -0,0 +1,35 @@
# Painted sky provenance
Created for this project with the built-in imagegen tool on 2026-09-05.
Original output: 1774 × 887 PNG, copied without pixel editing to
`assets/painterly/painted_sky.png`. The sky shader samples the upper portion
and blends its horizon into the simulation-driven day/night palette.
No film frame or downloaded artwork is used as a runtime texture.
## Generation prompt
Use case: stylized-concept. Asset type: original 360-degree panoramic sky
texture for a Godot game, 2:1 equirectangular landscape, ideally 3072x1536.
Create ONLY SKY, absolutely no ground, hills, buildings, ocean, objects, text
or sun disk. Art direction: exquisitely hand-painted Japanese animation
background, Studio Ghibli inspired summer pastoral fantasy, gouache brush
texture, saturated clear turquoise-blue atmosphere, towering luminous cumulus
clouds with creamy warm sunlit edges, intricate scalloped billowing forms,
pale cyan and slate blue softly painted shadow volumes. Composition is a full
spherical sky environment texture: upper half of image shows the upward
hemisphere, zenith at top is mostly deep clean blue, large beautiful cumulus
towers occupy the upper middle band between y=15% and y=44%, and the horizon
at exactly image vertical halfway is uniform pale atmospheric aqua. Lower
half is entirely an unobtrusive smooth pale aqua color gradient, with NO
landscape. Cloud coverage about 40% of upper hemisphere, generous varied blue
gaps, asymmetrical naturally spaced cloud masses. Left and right edges must
wrap seamlessly in color and shape, keep thin strips at far left/right clear
sky to avoid seam. No photographic cloud noise, no 3D render or vector
outlines, no flat cartoon circles. Painterly refined layered brushwork like a
cinematic animated film background, not a flat gradient. Use only original
cloud shapes; no copied film imagery.
The returned image does not exactly meet the requested resolution or horizon
position. The runtime shader uses its actual dimensions and remaps the painted
horizon at 69% image height. Blender mesh assets are deterministic rebuilds;
this generated image is a pinned art asset, not a deterministic generator output.
+24
View File
@@ -0,0 +1,24 @@
{
"Canopy": {
"triangles": 1236,
"surfaces": 1,
"vertices": 712
},
"Trunk": {
"triangles": 406,
"surfaces": 1,
"vertices": 221
},
"runtime_tree": {
"triangles": 5350,
"surfaces": 5,
"shared_meshes": 2
},
"glb_bytes": 45676,
"conifer": {
"triangles": 1580,
"surfaces": 2,
"height_m": 3.1,
"glb_bytes": 40836
}
}
+83
View File
@@ -0,0 +1,83 @@
# Woodland storybook art
Original Blender models for The Steward's warm, painted animation direction.
The runtime cast is a fox traveler, rabbit gardener, bespectacled badger and
otter in oversized coats, scarves, boots and satchels. These are original
characters, not models of existing film characters.
`art/storybook/` contains the editable Blender 5.1 sources. Its `.gdignore`
keeps Blender out of Godot's runtime import dependency chain. This directory
contains portable GLBs and the extracted grass mesh consumed by Terrain3D.
## Rebuild
From the project root, with Blender and Godot 4.7 installed:
```sh
"$BLENDER_BIN" --background --python tools/art/build_storybook_assets.py
"$GODOT_BIN" --headless --editor --path "$PWD" --import
"$GODOT_BIN" --headless --path "$PWD" --script tools/import_storybook_grass.gd
```
On macOS the binaries used for this pass were
`/Applications/Blender.app/Contents/MacOS/Blender` and
`/Applications/Godot.app/Contents/MacOS/Godot`. Headless CI should use the
isolated profile configured by `tools/quality.sh`.
## Mesh and material contract
- The seven-blade meadow tuft has **35 triangles**, one surface, rooted Y=0,
and UV.y=0 at each root / 1 at each tip. Wind and actor bending run on the
GPU. The material is opaque and double-sided, with no alpha cards, texture
lookups, per-blade nodes, shadow casting, or outline pass.
- Terrain3D owns the existing nine camera-local emitter cells: 10,404 tuft
slots on High, 5,184 on Balanced, hidden and stopped on Low. Terrain masks
and distance fading further reduce visible grass. Four scene-derived path
clearings collapse blades around the walking strips. Changing density does
not alter the terrain, navigation, resource state or collision.
- Each animal has eight mesh surfaces, **7,884–10,050 triangles** total, and
vertex colors. `mesh_budget.json` is emitted by the Blender generator.
Vertex alpha is a clothing tint mask; the cel shader never writes ALPHA.
- `AnimalAppearance` derives NPC species from stable ID modulo four; the
player is a fox. It shares imported meshes, creates an isolated material
per actor and tints clothing from the existing profession definition.
`Body`, `Head`, `Hair`, four limbs and `Tail` preserve the visual controllers'
animation slots. Shoulder/hip pivots support the existing velocity-driven
gait. Ears, face details, boots and scarves are joined into those surfaces.
- Inventory sacks, carried logs, profession tools, task glyphs, trust reactions,
death and player sword cues remain controlled by their existing state paths.
Animal species here is appearance only: no animal simulation classification,
RNG calls, saved fields, migration or gameplay mechanics are introduced.
## Review captures
```sh
"$GODOT_BIN" --path "$PWD" --script tools/capture_storybook.gd -- --cast
"$GODOT_BIN" --path "$PWD" --script tools/capture_storybook.gd
```
These require a real renderer. The first writes `storybook_cast.png`; the
second captures the running main scene at a controlled daytime presentation,
then writes gameplay/valley PNGs and a small profile sample to `docs/baselines/`.
It pauses simulation only in the capture process and does not save anything.
The gameplay camera defaults remain unchanged. The matched environment camera
can also be captured with `tools/capture_jajce_lookdev.gd -- --hide-debug-labels
--output=res://docs/baselines/storybook_after.png`.
Profile samples are local wall-frame latency at a static camera with six NPC
visuals, including vsync. They are not isolated GPU timings or weak-PC proof.
The full macOS quality gate passed on Godot 4.7, including all scenarios,
Compatibility profile checks and 180 GUT tests / 2,258 assertions. Native
Metal captures were visually reviewed. The character shader also rendered on
Compatibility, whose color/tonemapping differs substantially from Metal; the
approved color reference is `storybook_cast.png` (Metal).
Native Metal shutdown reports one `ParticlesShaderRD` / material Shader RID
retained at exit. The identical diagnostic was reproduced by importing and
running untouched commit `6aae41d` in a separate temporary directory. The
existing headless gate allowlist was not changed. Import/startup diagnostics
and the baseline comparison are in ignored `logs/quality/storybook-*.log`.
Shader interfaces follow the official [Godot 4.7 spatial shader contract](https://docs.godotengine.org/en/4.7/tutorials/shaders/shader_reference/spatial_shader.html).
The exporter uses Blender's [glTF export API](https://docs.blender.org/api/main/bpy.ops.export_scene.html).
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://buehm0sth6etc"
path="res://.godot/imported/badger.glb-4a20c61fae6d6e8d68c151cba8d056d5.scn"
[deps]
source_file="res://assets/storybook/badger.glb"
dest_files=["res://.godot/imported/badger.glb-4a20c61fae6d6e8d68c151cba8d056d5.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cbne2ol0h0uku"
path="res://.godot/imported/fox.glb-972d78ae35ada569a710c17f00c10a3e.scn"
[deps]
source_file="res://assets/storybook/fox.glb"
dest_files=["res://.godot/imported/fox.glb-972d78ae35ada569a710c17f00c10a3e.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bd6mrof50g4wn"
path="res://.godot/imported/meadow_grass.glb-0321cd60906212db637f52f1f1493ea1.scn"
[deps]
source_file="res://assets/storybook/meadow_grass.glb"
dest_files=["res://.godot/imported/meadow_grass.glb-0321cd60906212db637f52f1f1493ea1.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
{
"fox": {
"triangles": 7884,
"surfaces": 8,
"bytes": 271636
},
"rabbit": {
"triangles": 9060,
"surfaces": 8,
"bytes": 314636
},
"badger": {
"triangles": 10050,
"surfaces": 8,
"bytes": 345484
},
"otter": {
"triangles": 9088,
"surfaces": 8,
"bytes": 314108
},
"meadow_grass": {
"triangles": 35,
"surfaces": 1,
"bytes": 3648
}
}
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://6f01q2ru2sq4"
path="res://.godot/imported/otter.glb-92d54495ed52927d77a0a5213ac92330.scn"
[deps]
source_file="res://assets/storybook/otter.glb"
dest_files=["res://.godot/imported/otter.glb-92d54495ed52927d77a0a5213ac92330.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bywpllx00b6aq"
path="res://.godot/imported/rabbit.glb-ed7bf640394b68863b9dc6437e0f1808.scn"
[deps]
source_file="res://assets/storybook/rabbit.glb"
dest_files=["res://.godot/imported/rabbit.glb-ed7bf640394b68863b9dc6437e0f1808.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
+14 -12
View File
@@ -21,10 +21,11 @@ own persistent quantities, travel completion, or quest progress.
The long-term direction is one regional simulation containing settlements, The long-term direction is one regional simulation containing settlements,
routes, caravans, named people, aggregate cohorts, and polities. The current routes, caravans, named people, aggregate cohorts, and polities. The current
repository has the regional records, deterministic scheduler, one exact repository has the regional records, deterministic scheduler, one exact
caravan transfer service, chunked persistence, and a 20-caravan structural caravan transfer service, a production facade for one inbound Jajce route, a
benchmark. It does not yet run that regional service as the production combined local/regional save manifest, chunked persistence, and a 20-caravan
`SimulationManager` facade or provide a complete market, settlement-growth, or structural benchmark. It does not yet provide a complete market,
kingdom gameplay loop. That distinction is important when extending the game. settlement-growth, or kingdom gameplay loop. That distinction is important
when extending the game.
## Start here ## Start here
@@ -37,6 +38,7 @@ kingdom gameplay loop. That distinction is important when extending the game.
| Add caravans, routes, scheduled work, or regional saves | [Regional simulation and persistence](FEATURE_REGIONAL_SIMULATION_AND_PERSISTENCE.md) | `simulation/regional/`, `simulation/state/*StateRecord.gd` | | Add caravans, routes, scheduled work, or regional saves | [Regional simulation and persistence](FEATURE_REGIONAL_SIMULATION_AND_PERSISTENCE.md) | `simulation/regional/`, `simulation/state/*StateRecord.gd` |
| Add an enemy or animal using an existing behavior | [Entity families](FEATURE_ENTITIES_COMBAT_ANIMALS.md) | `CombatantFactory.gd`, `AnimalFactory.gd`, shared visual roots | | Add an enemy or animal using an existing behavior | [Entity families](FEATURE_ENTITIES_COMBAT_ANIMALS.md) | `CombatantFactory.gd`, `AnimalFactory.gd`, shared visual roots |
| Change rendering or support weaker PCs | [Presentation and performance](FEATURE_PRESENTATION_PERFORMANCE.md) | `world/jajce/jajce_world.gd`, `world/presentation/` | | Change rendering or support weaker PCs | [Presentation and performance](FEATURE_PRESENTATION_PERFORMANCE.md) | `world/jajce/jajce_world.gd`, `world/presentation/` |
| Author humanoid animal characters or combine clothing and body shapes | [Modular character kit](../assets/characters/README.md) | `tools/CharacterWorkshop.tscn`, `CharacterAppearanceProfile.gd`, `tools/art/build_character_kit.py` |
| Change a saved field or migration | [Save schema and migrations](FEATURE_SAVE_SCHEMA_AND_MIGRATIONS.md) | `SimulationStateRecord.gd`, nested state records, `SaveSlotStore.gd` | | Change a saved field or migration | [Save schema and migrations](FEATURE_SAVE_SCHEMA_AND_MIGRATIONS.md) | `SimulationStateRecord.gd`, nested state records, `SaveSlotStore.gd` |
| Add a regression, benchmark, or run the gate | [Testing and benchmarks](FEATURE_TESTING_AND_BENCHMARKS.md) | `tests/`, `tools/`, `docs/benchmarks/` | | Add a regression, benchmark, or run the gate | [Testing and benchmarks](FEATURE_TESTING_AND_BENCHMARKS.md) | `tests/`, `tools/`, `docs/benchmarks/` |
@@ -53,9 +55,9 @@ kingdom gameplay loop. That distinction is important when extending the game.
| Situations/quests | Definition-driven bounded situations, derived progress, journal metadata, commitments and idempotent outcomes | The initial Jajce slice is bounded; it is not a universal objective scripting language | | Situations/quests | Definition-driven bounded situations, derived progress, journal metadata, commitments and idempotent outcomes | The initial Jajce slice is bounded; it is not a universal objective scripting language |
| Dialogue | Deterministic semantic `ConversationService`, authored intent/template resources, Dialogue Manager v3 presenter | Dialogue Manager renders planned turns; it is not an authority or save format | | Dialogue | Deterministic semantic `ConversationService`, authored intent/template resources, Dialogue Manager v3 presenter | Dialogue Manager renders planned turns; it is not an authority or save format |
| Entities | Shared definition → instance/state → presentation contracts for resources, animals, enemies, items, storage | New behavior profiles and genuinely new mechanics still need typed code and tests | | Entities | Shared definition → instance/state → presentation contracts for resources, animals, enemies, items, storage | New behavior profiles and genuinely new mechanics still need typed code and tests |
| Regional kernel | Primitive records, due-job heap, keyed random, analytical updates, caravan proof, chunk/file store | Regional service and 20-caravan trade are currently integration fixtures, not the live Jajce loop | | Regional kernel | Primitive records, due-job heap, keyed random, analytical updates, caravan service, production facade, chunk/file store | One authored inbound route is live; the 20-caravan economy remains a structural fixture, not the Jajce market |
| Presentation | Active-world adapters, context registries, relevance cap of 40, navigation budget of 2/frame, High/Balanced/Low profiles | Hardware frame-time/VRAM evidence is still pending; structural benchmarks are not rendered claims | | Presentation | Active-world adapters, context registries, relevance cap of 40, navigation budget of 2/frame, High/Balanced/Low profiles | Hardware frame-time/VRAM evidence is still pending; structural benchmarks are not rendered claims |
| Persistence | Local save schema v16 with migrations and scope preflight; regional chunk generations with recovery | No save menu/profile system; regional chunk store is not yet the local `SaveSlotStore` backend | | Persistence | Combined manifest v1 containing unchanged local schema v16 plus the canonical regional facade; regional chunk generations with recovery | No save menu/profile system; regional chunk/history generations are not yet the `SaveSlotStore` backend |
## The authority pipeline ## The authority pipeline
@@ -106,9 +108,10 @@ relationship, or reward that the event claims happened.
- `simulation/events/`, `knowledge/`, `relationships/`, `situations/`, and - `simulation/events/`, `knowledge/`, `relationships/`, `situations/`, and
`dialogue/` — causal history and emergent narrative layers. `dialogue/` — causal history and emergent narrative layers.
- `simulation/state/` — versioned primitive records and validation. - `simulation/state/` — versioned primitive records and validation.
- `simulation/persistence/` — local quicksave safety. - `simulation/persistence/` — combined quicksave manifest and atomic slot
- `simulation/regional/` — regional records, scheduler, caravan proof, chunk replacement, plus regional chunk codecs and generations.
codec, and file generations. - `simulation/regional/` — production facade, regional records, scheduler,
caravan transaction, retention planning, and scale contracts.
### Loaded world and presentation ### Loaded world and presentation
@@ -161,12 +164,11 @@ are used when an existing save is loaded.
The following are deliberately **not** claimed as complete: The following are deliberately **not** claimed as complete:
- wire `RegionalCaravanService` into the production regional facade and local - integrate event retention/rollups/chunked history into one atomic,
save manifest; lazily loaded long-horizon save;
- run 20 real caravan trades across five settlement economies with atomic - run 20 real caravan trades across five settlement economies with atomic
cargo/stock ledgers; cargo/stock ledgers;
- replace local unloaded travel freezes with authoritative route-time work; - replace local unloaded travel freezes with authoritative route-time work;
- integrate event retention/rollups/chunked history into one long-horizon save;
- add settlement projects, cohort promotion/demotion, offices, diplomacy, - add settlement projects, cohort promotion/demotion, offices, diplomacy,
armies, campaigns, and aggregate conflict; armies, campaigns, and aggregate conflict;
- add visual relevance streaming to the live world view rather than only the - add visual relevance streaming to the live world view rather than only the
+159 -4
View File
@@ -38,19 +38,92 @@ spawn is a next integration.
`JajceWorld` owns reversible High, Balanced, and Low presentation profiles with `JajceWorld` owns reversible High, Balanced, and Low presentation profiles with
instance-local mutable environment/grass resources and viewport-scale ownership. instance-local mutable environment/grass resources and viewport-scale ownership.
| Profile | 3D scale | Shadow distance | Volumetric/glow | Grass | | Profile | 3D scale / MSAA | Shadow distance | Volumetric/glow | Grass |
| --- | ---: | ---: | --- | --- | | --- | ---: | ---: | --- | --- |
| High | authored | authored (currently 180 m) | authored when renderer supports it | authored density/interactors | | High | authored / authored 4× | authored (currently 180 m) | authored when renderer supports it | authored density/interactors |
| Balanced (default) | 0.85 cap | 120 m cap | authored ordinary lookdev; unsupported volumetric disabled | reduced spacing/interactors/update rate | | Balanced (default) | 0.85 cap / 2× cap | 120 m cap | authored ordinary lookdev; unsupported volumetric disabled | reduced spacing/interactors/update rate |
| Low | 0.70 cap | 80 m, orthogonal | volumetric/glow/adjustment disabled; ordinary fog retained | hidden/stopped, emitters off, shader interactors zero | | Low | 0.70 cap / off | 80 m, orthogonal | volumetric/glow/adjustment disabled; ordinary fog retained | hidden/stopped, emitters off, shader interactors zero |
UI remains at native resolution. High/Low switches restore captured authored UI remains at native resolution. High/Low switches restore captured authored
values; viewport scale restoration is guarded by the current quality owner so a values; viewport scale restoration is guarded by the current quality owner so a
second world cannot clobber the active owner. Compatibility/mobile feature second world cannot clobber the active owner. Compatibility/mobile feature
selection never forces unsupported volumetric fog on. selection never forces unsupported volumetric fog on.
## Grass trails and bush contact
`GrassInteractionController` samples the existing `grass_interactors` group:
player, NPCs, hostile creatures and animals. The player keeps one slot; other
visible actors are ranked by camera distance within the loaded actor container.
High updates at 0.08 seconds with eight actors; Balanced uses 0.12 seconds and
four. Low stops updates and immediately releases both grass and bushes.
`FoliageTrailMap` maintains a disposable 128×128 RGBA float texture covering a
64 m square around the camera. Swept footprints part grass to either side and
lower its tips; the field recovers over eight seconds after contact. Sparse
history is clipped to the moving window. The 256 KiB texture is reused with
[`ImageTexture.update`](https://docs.godotengine.org/en/stable/classes/class_imagetexture.html#class-imagetexture-method-update),
at most 3.125 MiB/s of texture data at the High update rate. The vertex shader
adds one field lookup per grass vertex; there are no blade collision bodies,
per-blade scripts, render targets or added grass draw calls.
`StylizedBerryPatch` shares the field and contact positions between its leaf
and berry MultiMeshes. Both bend in world space around the planted bush root,
including under rotated/scaled parents, and spring back faster than grass.
The five village berry placeholders now use this same amount-aware presentation
as the existing riverbank bush. Contact does not change berries, resource
amounts, navigation, targets or collision geometry.
The controller clears history after `SimulationManager.state_restored`, on
quality changes and on world disposal. Missing/recreated actors, teleports over
3 m between samples and update gaps over 0.5 seconds start a new footprint rather
than a connecting trail. Actor foot height gates deformation so bridge traffic
does not flatten grass below. Trails are visual only and never enter saves.
The exported actor, primary actor, foliage and simulation paths can be rebound
when placing the controller in another loaded scene container.
`tests/foliage_interaction_test.gd` checks sweep direction, recovery, teleport
rejection, camera movement, world isolation, restore/quality reset, the primary
actor slot and shared leaf/fruit bindings. Run `tools/capture_foliage.gd` with
the native renderer for before/contact/trail/recovery images and controller
timing in `docs/baselines/foliage_*`. That capture freezes simulation and wind, and moves
the player presentation through the actual meadow, checking the state checksum
is unchanged. CPU update timing is not a GPU or weak-PC frame-rate guarantee.
Native Metal and Compatibility both render the effect; existing particle-shader
shutdown warnings (and four Compatibility texture leaks) also reproduce in the
untouched pre-art baseline. The headless quality gate retains its exact allowlist.
## Runtime hot paths already bounded ## Runtime hot paths already bounded
The woodland storybook art pass adds portable Blender animal meshes and an
opaque 35-triangle, seven-blade grass tuft. The existing camera-local grass
field and quality controls own its instance budget; four loaded path strips
provide disposable grass-clearance data after material isolation. Animal
appearance is derived from stable NPC IDs, shares mesh resources, and retains
profession tint, velocity-driven motion, inventory cues and death through the
existing presentation controllers. No saved state or navigation changes.
Characters and buildings use soft cel bands; tree crowns retain the shared
breeze with painted color patches. A painted cloud panorama, cooler
ambient fill and warm sunlight follow the simulation day/night clock. The
cloud shader adds no volumetric pass, outline pass or screen-space postprocess.
See [the asset contract and rebuild commands](../assets/storybook/README.md).
The current humanoid cast uses the [modular woodland character kit](../assets/characters/README.md):
31 Blender parts, five animal heads, six authored examples and independently
configurable proportions, outfits and accessories. `CharacterAppearanceProfile`
resources are cosmetic. Meshes are shared; `Stout` morph weights and palette
materials are per actor. The reference traveler has 20,070 base triangles across
10 opaque surfaces; Godot imports LODs. The standalone
`tools/CharacterWorkshop.tscn` previews and saves profiles. Existing motion,
profession/inventory/death cues, and shader grass interaction remain owned by
their existing controllers. The appearance scenario verifies imported parts,
independent shape weights, stable identity, extreme proportions and cue clearance.
`docs/baselines/storybook_*.png` records the native Metal review. The accompanying
`storybook_render_metrics.json` records a bounded local 1600×900 static-camera
sample on Apple M1 Max with six NPCs and High/Balanced/Low settings. Wall-frame
latency includes vsync and does not close the weak-PC evidence gap below.
- Player interaction and villager inspection probes are throttled/event-driven; - Player interaction and villager inspection probes are throttled/event-driven;
they no longer rebuild all candidate data every rendered frame. they no longer rebuild all candidate data every rendered frame.
- `LoadedResourceSpatialIndex` bounds finite-resource discovery. - `LoadedResourceSpatialIndex` bounds finite-resource discovery.
@@ -99,3 +172,85 @@ ledger, not brittle cross-machine CI thresholds.
See [the benchmark ledger](benchmarks/README.md), [regional contract](REGIONAL_SIMULATION.md), See [the benchmark ledger](benchmarks/README.md), [regional contract](REGIONAL_SIMULATION.md),
and [the build-in-public visual plan](BUILD_IN_PUBLIC_PLAN.md). and [the build-in-public visual plan](BUILD_IN_PUBLIC_PLAN.md).
## Painterly environment pass — September 2026
The reference study highlighted flat cloud bands, primitive tree silhouettes,
sparse ground cover, opaque green water, and a sun arc offset from daytime
brightness. The current pass addresses those through shared presentation assets:
- The WorldEnvironment uses cool blue ambient fill, warm sunlight, restrained
glow and ordinary aerial fog. The solar arc now peaks at noon and the warm
transitions overlap dawn/dusk. An original painted cloud panorama supplies
layered cumulus shapes; a static sky shader remaps the artwork, blends its
seam/horizon, and tints it from the simulation clock. It adds no TIME-driven
cubemap updates or volumetric cloud pass. Generation provenance and the exact
prompt are in [the asset guide](../assets/painterly/README.md).
- Original Blender broadleaf crowns/trunks and layered conifers replace the
primitive foliage. Seventeen decorative edge trees form a woodland backdrop.
The four village tree placeholders now use `HarvestableTreePresentation`,
retaining their existing IDs, amounts, interaction points and resource count.
Full/low/depleted state and restored crowns still derive from real amounts.
- `PainterlyMeadow` creates at most 1,680 decorative flower clumps in four
MultiMesh batches, with cream daisies, pink cosmos, buttercups and lavender.
Local seeded drifts follow terrain and exclude houses, paths, work sites and
resource approach points. High/Balanced/Low expose at most 1,680/1,092/536
clumps, with shorter distance fading in cheaper profiles. Flowers share the
prevailing breeze; the existing grass and berry contact field remains intact.
Explicit white instance colors preserve the painted GLB palette when custom
instance data is enabled in Compatibility; native review caught and verified
that renderer-specific regression.
Flowers have no collision, harvesting, actor-contact deformation or saved
state. See [mesh sources and budgets](../assets/meadow/README.md).
- The blue/turquoise river follows actual centerline terrain heights, keeps
level cross-sections, and clips dry edges at the shoreline. The pool and its
outlet meet without overlapping coplanar transparent sheets. Broken foam,
subtle flowing strokes and painted reflection patches replace the hard ring
and featureless surface. These reflections are stylized procedural patches
plus ordinary sky specular response, not exact reflected scene geometry,
screen-space reflections, refraction or depth sampling. Water obeys scene
lighting at night and adds no per-frame scripts.
- MSAA follows the existing viewport ownership rules: High preserves authored
4×, Balanced caps at 2×, and Low disables it. Closing an inactive world cannot
overwrite the active world's antialiasing or an external viewport override.
The editable Blender sources live under `art/painterly/` and `art/meadow/`,
protected by `.gdignore`; runtime consumes portable GLBs. The broadleaf is
5,350 triangles / five surfaces and the conifer is 1,580 / two. All flower
clumps together are capped at 362,880 triangles / four surfaces before
quality and distance reduction. No scene collision, navigation bake, terrain
heightmap, simulation rule or save schema was changed.
`tools/capture_painterly_environment.gd -- --label=after` captures the running
main scene with paused simulation, fixed daytime presentation and hidden HUD,
including the normal gameplay camera, valley, lower village view and night.
The valley/near-village `painterly_before_*` and `painterly_after_*`
images are matched cameras; `after_gameplay` fixes the normal rig at its default
yaw/zoom with mouse input disabled, and `after_vista` is an additional review angle.
The tool also records static-camera profile samples in
`painterly_after_metrics.json`; wall-frame latency includes vsync and is not
isolated GPU time or weak-PC proof. Runtime camera defaults are unchanged.
Remaining visual work is primarily authored terrain/shoreline detail and the
blockout-like building/fortress forms. This pass improves atmosphere and
foliage without treating those assets as finished film-quality environments.
Validation for this pass: the full macOS Godot 4.7 gate passed, including all
headless scenarios, the Compatibility quality scenario, and 182 GUT tests /
2,279 assertions. Main-scene headless startup passed the gate's existing exact
diagnostic allowlist. Native Metal daytime/gameplay/valley/night images were
reviewed at 1600×900, with a separate native Compatibility capture confirming
colored petals after the instance-buffer fix. The presentation-quality scenario
also passed with native Compatibility, including white instance-color readback
(the headless dummy renderer cannot provide that readback).
The static M1 Max sample reports approximately 16.7 ms
median wall-frame time in all three profiles; see its JSON for p95, draw calls
and primitives. This includes vsync and does not establish GPU headroom.
Native renderer shutdown still reports the particle shader/material RID
retention previously documented in the storybook/foliage pass; the headless
allowlist was not expanded. Compatibility has different color/tonemapping and
its existing particle/texture shutdown diagnostics. Metal is the color
reference for the saved art-review images.
The native multi-world quality scenario additionally logged a transient macOS
OpenGL cubemap-unloadable diagnostic; the final main-scene captures did not.
@@ -2,9 +2,9 @@
The regional package is the scale foundation for one authority spanning The regional package is the scale foundation for one authority spanning
settlements, routes, caravans, named people, aggregate cohorts, and polities. settlements, routes, caravans, named people, aggregate cohorts, and polities.
It is intentionally additive: the current Jajce `SimulationManager` remains The production `SimulationManager` now owns a bounded regional facade for one
the playable local facade while the regional contracts prove deterministic inbound Jajce route, while the broader regional contracts continue to prove
unloaded work and conservation in isolation. deterministic unloaded work and conservation before wider integration.
## Regional record model ## Regional record model
@@ -63,6 +63,12 @@ no partial mutation. `to_dictionary()`, `from_dictionary()`, and `checksum()`
provide deterministic save/restore. The test runs always-loaded, never-loaded, provide deterministic save/restore. The test runs always-loaded, never-loaded,
and load/unload presentation modes through the same authoritative service. and load/unload presentation modes through the same authoritative service.
`RegionalSimulationFacade` is the first production consumer. It owns one
authored Travnik-to-Jajce route, advances before each committed local tick, and
exposes one bounded delivery command. The cargo is a separate catalogued
`regional_trade_goods` item with no local storage tag, so this proof does not
shadow or duplicate the pantry and woodpile economy.
## Chunked persistence ## Chunked persistence
`RegionalChunkedPersistence` captures the service into a manifest plus fixed `RegionalChunkedPersistence` captures the service into a manifest plus fixed
@@ -89,11 +95,20 @@ primitive-tree bounds.
## Current integration boundary ## Current integration boundary
The regional package is not yet the production authority for `main.tscn`: `main.tscn` now advances the regional facade and `SaveSlotStore` writes a
combined `SimulationSaveManifest` containing the unchanged local v16 record
and canonical regional envelope. Both checksums plus world, seed, location,
and tick alignment are validated before either authority is restored. Raw
local v16 slots remain compatible and create an idle regional facade;
explicit non-Jajce adapter scopes keep writing local v16 until they have a
matching regional facade. Those scopes cannot dispatch a Jajce delivery, and
local-only saving fails closed if regional state is no longer reconstructibly
idle.
- Jajce local saves still use `SaveSlotStore` and `SimulationStateRecord` v16; The remaining boundary is intentionally narrow:
- `RegionalCaravanService` is a complete isolated transfer proof, not the live
economy's 20-trade scheduler; - the facade has one callable inbound route, not the live economy's 20-trade
market scheduler;
- event retention/rollups and regional chunk files are not yet one integrated - event retention/rollups and regional chunk files are not yet one integrated
long-horizon save transaction; long-horizon save transaction;
- loaded/unloaded local NPC travel still has the old active-visual boundary; - loaded/unloaded local NPC travel still has the old active-visual boundary;
@@ -101,8 +116,8 @@ The regional package is not yet the production authority for `main.tscn`:
aggregate conflict remain planned. aggregate conflict remain planned.
Do not create one `SimulationManager` per settlement or caravan. The next Do not create one `SimulationManager` per settlement or caravan. The next
production step is an explicit regional facade that delegates to these records production step is to publish retained raw events, daily rollups, and chunked
and services, while the active adapter presents only a bounded relevant context. regional history as one atomic, lazily loaded transaction behind the facade.
## Scale evidence ## Scale evidence
+20 -8
View File
@@ -64,11 +64,22 @@ catch corruption.
## Local file safety ## Local file safety
`SaveSlotStore` validates the complete JSON before replacement, restricts slot `SimulationSaveManifest` v1 combines the unchanged local v16 record and the
names and size, preserves a previous file, and can recover a backup after an canonical `RegionalSimulationFacade` envelope. It stores an independent
interrupted replacement. `F5`/`F9` use the current quicksave path in `main.tscn`. checksum for each authority and cross-validates world, seed, active location,
The player transform and presentation-only scene state are intentionally not and processed tick. A rejected manifest cannot partially replace local or
saved. regional state.
`SaveSlotStore` validates the complete manifest before replacement, restricts
slot names and size, preserves a previous file, and can recover a backup after
an interrupted replacement. It also accepts existing raw local v16 files and
bootstraps an idle regional facade at the restored seed and tick. Explicit
non-Jajce adapter scopes continue writing raw local v16 records until a
matching regional facade is implemented. This fallback is permitted only while
the regional facade equals its deterministic idle bootstrap; a local-only save
fails closed rather than discarding live regional state. `F5`/`F9` use the
current quicksave path in `main.tscn`. The player transform and presentation-only
scene state are intentionally not saved.
## Regional persistence boundary ## Regional persistence boundary
@@ -79,9 +90,10 @@ per-file checksums. The file store uses immutable generations, a current
pointer, recovery scanning, and conservative pruning. Partial active-location pointer, recovery scanning, and conservative pruning. Partial active-location
loads are metadata-only and cannot silently resurrect a removed location. loads are metadata-only and cannot silently resurrect a removed location.
This regional codec is not yet the backend for local `SaveSlotStore`. Integrate This regional chunk codec is not yet the backend for `SaveSlotStore`. The
it only after the production regional facade, event retention, and lazy context production facade and combined quicksave manifest are complete; retained raw
loading have one atomic manifest contract. events, rollups, chunk generations, and lazy context loading still need one
atomic long-horizon publication contract.
## How to change state safely ## How to change state safely
+1 -1
View File
@@ -77,7 +77,7 @@ For a new system, add tests for:
## Current evidence and gaps ## Current evidence and gaps
The current full suite passes 154/154 tests with 2,047 assertions. Baseline 03 The current full suite passes 180/180 tests with 2,258 assertions. Baseline 03
measures 600 full-fidelity data-only NPC/combatant records at about 90.6 ticks/s measures 600 full-fidelity data-only NPC/combatant records at about 90.6 ticks/s
on the reviewed development host. The regional baseline proves the target on the reviewed development host. The regional baseline proves the target
record counts and scheduler parity. Neither measures a complete regional record counts and scheduler parity. Neither measures a complete regional
+11 -4
View File
@@ -1,10 +1,11 @@
# The Steward — Learning and Reusable-Systems Roadmap # The Steward — Learning and Reusable-Systems Roadmap
> **Current implementation checkpoint (2026-08-13):** The roadmap below is a > **Current implementation checkpoint (2026-08-23):** The roadmap below is a
> chronological learning record. The current code has completed the authored > chronological learning record. The current code has completed the authored
> content/catalog, shared target/command, event/social, situation/dialogue, > content/catalog, shared target/command, event/social, situation/dialogue,
> entity-family, context-scoping, and first regional record/caravan/chunk > entity-family, context-scoping, and first regional record/caravan/chunk
> slices. For a source-based status matrix and the remaining integration work, > slices, including one production facade and combined save manifest. For a
> source-based status matrix and the remaining integration work,
> start with [`DEVELOPER_INDEX.md`](DEVELOPER_INDEX.md). Do not infer current > start with [`DEVELOPER_INDEX.md`](DEVELOPER_INDEX.md). Do not infer current
> behavior from an older milestone paragraph when the index or code disagrees. > behavior from an older milestone paragraph when the index or code disagrees.
@@ -43,8 +44,8 @@ becomes knowledge, generated semantic dialogue, a journal projection, and an
event-backed social commitment whose ordinary deposit outcome survives event-backed social commitment whose ordinary deposit outcome survives
save/load. Definition packs now cover reusable items, resources, storage, save/load. Definition packs now cover reusable items, resources, storage,
enemies, and animals. Regional records, deterministic scheduled work, bounded enemies, and animals. Regional records, deterministic scheduled work, bounded
presentation relevance, and the first authoritative caravan transfer form the presentation relevance, and the first authoritative caravan transfer now feed
next world-slice foundation. See one production facade and combined local/regional save boundary. See
[ADR 0001](decisions/0001-simulation-authority-boundary.md), [ADR 0001](decisions/0001-simulation-authority-boundary.md),
[ADR 0002](decisions/0002-emergent-world-content-and-narrative.md), and the [ADR 0002](decisions/0002-emergent-world-content-and-narrative.md), and the
[regional simulation contract](REGIONAL_SIMULATION.md). [regional simulation contract](REGIONAL_SIMULATION.md).
@@ -1107,6 +1108,12 @@ resolution, and personal request accept/decline.
Recently completed: Recently completed:
- `Regional Jajce Facade 13`: `SimulationManager` now advances one authored
Travnik-to-Jajce caravan route through the regional service, and an atomic
combined manifest preserves both local v16 and regional authority. Separate
regional trade goods prevent duplicate pantry/woodpile ownership; tamper,
legacy-slot, rollback, conservation, and deterministic continuation
regressions define the boundary before broader markets or chunked history.
- `Jajce Villager Field Note 12`: a separate player-facing note selects the - `Jajce Villager Field Note 12`: a separate player-facing note selects the
nearest loaded living villager and shows live name, action, task state, exact nearest loaded living villager and shows live name, action, task state, exact
target, carried inventory, and a currently matching decision reason. It target, carried inventory, and a currently matching decision reason. It
+37 -16
View File
@@ -1,10 +1,11 @@
# Regional simulation contract # Regional simulation contract
> **Implementation status (2026-08-13):** The record, scheduler, caravan, > **Implementation status (2026-08-23):** The record, scheduler, caravan,
> chunk, file-generation, relevance, and navigation-budget contracts are > chunk, file-generation, relevance, and navigation-budget contracts are
> implemented and tested. They are still an additive regional package; the > implemented and tested. `SimulationManager` now owns a production regional
> playable Jajce `SimulationManager` and `SaveSlotStore` remain the local > facade for one inbound Jajce route, and `SaveSlotStore` persists local and
> production authority until the integrations listed at the end land. > regional authority in one checked manifest. The scale economy and chunked
> long-horizon history remain separate integrations.
The regional layer extends the Jajce simulation without multiplying scene-tree The regional layer extends the Jajce simulation without multiplying scene-tree
managers. One authority owns every loaded and unloaded location, settlement, managers. One authority owns every loaded and unloaded location, settlement,
@@ -38,12 +39,10 @@ validated transactionally before a replacement state is published. Canonical
serialization sorts record families and IDs, making checksums independent of serialization sorts record families and IDs, making checksums independent of
construction order. construction order.
The current local `SimulationStateRecord` remains the v16 Jajce save boundary. The current local `SimulationStateRecord` remains the unchanged v16 Jajce
Regional state is deliberately carried in its own versioned service envelope record. `SimulationSaveManifest` v1 combines that record with the canonical
until one playable route consumes it; this avoids placing speculative regional `RegionalSimulationFacade` envelope and checksums both authorities. Regional
fields in every local save. The merge into the production save manifest must fields are not copied into local records or scene nodes.
retain this canonical regional payload rather than copying its fields into
scene nodes.
## Deterministic work ## Deterministic work
@@ -92,6 +91,30 @@ never-loaded, and load/unload executions must preserve cargo, headcount, facts,
arrival tick, queue state, and checksum. Nearby visuals may interpolate route arrival tick, queue state, and checksum. Nearby visuals may interpolate route
progress; they do not report elapsed travel completion. progress; they do not report elapsed travel completion.
## Production facade and combined save
`RegionalSimulationFacade` is now owned by the production `SimulationManager`.
It bootstraps an authored Travnik-to-Jajce route and advances the same
`RegionalCaravanService` before each committed local tick. Its bounded public
command can depart one exact delivery; arrival remains scheduled simulation
work and does not depend on a loaded caravan visual.
The route transports `regional_trade_goods`, a catalogued item with no local
storage tag. Jajce starts with no regional stock and Travnik starts with four
units, so the facade cannot duplicate the playable pantry's food or the
woodpile's wood. Local and regional event-ID spaces remain deliberately
separate until an explicit cross-domain history contract exists.
`SimulationSaveManifest` stores exactly the local v16 record, its checksum, the
regional facade envelope, its checksum, and the manifest schema/version. It
rejects checksum tampering and mismatched world, seed, active location, or
tick. Combined restore is transactional across both authorities. Existing raw
local v16 quicksaves remain loadable and bootstrap an idle regional route at
the restored seed and tick. Explicit non-Jajce adapter scopes continue to use
their local v16 boundary until a matching regional facade exists. That fallback
is allowed only while the regional facade equals its deterministic idle
bootstrap; Jajce delivery commands are rejected outside the combined scope.
## Event retention and daily rollups ## Event retention and daily rollups
`WorldEventRetentionPlanner` is a pure, deterministic query over a complete `WorldEventRetentionPlanner` is a pure, deterministic query over a complete
@@ -157,14 +180,12 @@ active-world and hardware captures.
Build in this order, keeping each step behind conservation and continuation Build in this order, keeping each step behind conservation and continuation
tests: tests:
1. make the Jajce route proof callable from the production regional facade and 1. integrate the retention/rollup and chunk codecs as one atomic,
save manifest;
2. integrate the retention/rollup and chunk codecs as one atomic,
lazily loaded history transaction; lazily loaded history transaction;
3. schedule 20 ordinary caravan trades across five settlement economies; 2. schedule 20 ordinary caravan trades across five settlement economies;
4. promote/demote presentation and population fidelity while pinning named and 3. promote/demote presentation and population fidelity while pinning named and
causally referenced people; causally referenced people;
5. add data-defined settlement projects, tier promotion, offices, diplomacy, 4. add data-defined settlement projects, tier promotion, offices, diplomacy,
armies, campaigns, and aggregate conflict. armies, campaigns, and aggregate conflict.
Context-owned world-target registries and the first shared player/NPC activity Context-owned world-target registries and the first shared player/NPC activity
+16 -8
View File
@@ -451,12 +451,20 @@ replayed after restore.
## Local quicksave boundary ## Local quicksave boundary
`SaveSlotStore` writes the existing versioned local JSON record to `SaveSlotStore` writes `SimulationSaveManifest` v1 to
`user://saves/quicksave.json`. It validates the serialized record before `user://saves/quicksave.json`. The manifest contains the unchanged local v16
replacement, limits file size, restricts slot names, preserves the previous record and canonical regional facade envelope, with an independent checksum
file during replacement, and can recover that backup if replacement is for each. Restore also requires world, seed, active location, and tick to agree
interrupted. Loading parses and validates the complete record before mutating across both authorities, and rolls both back if local restoration fails.
the simulation.
The store validates the complete serialized manifest before replacement,
limits file size, restricts slot names, preserves the previous file during
replacement, and can recover that backup if replacement is interrupted. Raw
local v16 quicksaves remain loadable; they bootstrap an idle regional facade
at the restored seed and tick. Explicit non-Jajce adapter scopes continue
writing local v16 records until a matching regional facade exists. Local-only
saving is allowed only while regional state equals its deterministic idle
bootstrap; otherwise it fails closed rather than discarding authority.
In `main.tscn`, F5 saves and F9 loads this slot. Restoration rebuilds active NPC In `main.tscn`, F5 saves and F9 loads this slot. Restoration rebuilds active NPC
visuals from authoritative state. visuals from authoritative state.
@@ -476,8 +484,8 @@ This phase does not yet provide:
- persistence of a currently open dialogue UI/turn. Semantic conversation - persistence of a currently open dialogue UI/turn. Semantic conversation
history and commitment authority persist, while a balloon is regenerated history and commitment authority persist, while a balloon is regenerated
after restore; after restore;
- production integration of the regional chunked save manifest, lazy location - production integration of regional retained/rolled-up chunk history, lazy
loading, or aggregate population promotion/persistence; location loading, or aggregate population promotion/persistence;
- persistence for the player transform or presentation-only scene state. - persistence for the player transform or presentation-only scene state.
Those features should build on this boundary rather than inventing parallel Those features should build on this boundary rather than inventing parallel
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 KiB

@@ -0,0 +1,13 @@
{
"active_trail_texels": 145,
"controller_update_p50_ms": 0.307,
"controller_update_p95_ms": 0.338,
"draw_calls": 582.0,
"note": "CPU timing includes scanning, field painting and material uploads; not isolated GPU frame time.",
"profile": "High",
"renderer": "metal",
"resolution": "1600x900",
"simulation_checksum_unchanged": true,
"trail_texture_bytes": 262144,
"workload": "Actual Jajce meadow; 8 nearest actors, player presentation follows an 11 m sweep"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@@ -0,0 +1,53 @@
{
"device": "Apple M1 Max (Apple7)",
"renderer": "metal",
"resolution": "1600x900",
"samples": [
{
"active_npcs": 6,
"draw_calls": 825.0,
"meadow": {
"flower_clump_count": 1680,
"multimesh_batch_count": 4,
"visible_flower_clump_count": 1680
},
"msaa_enum": 2,
"primitives": 1070038.0,
"profile": "high",
"render_scale": 1.0,
"wall_frame_p50_ms": 16.649,
"wall_frame_p95_ms": 17.327
},
{
"active_npcs": 6,
"draw_calls": 710.0,
"meadow": {
"flower_clump_count": 1680,
"multimesh_batch_count": 4,
"visible_flower_clump_count": 1092
},
"msaa_enum": 1,
"primitives": 810830.0,
"profile": "balanced",
"render_scale": 0.850000023841858,
"wall_frame_p50_ms": 16.672,
"wall_frame_p95_ms": 17.06
},
{
"active_npcs": 6,
"draw_calls": 657.0,
"meadow": {
"flower_clump_count": 1680,
"multimesh_batch_count": 4,
"visible_flower_clump_count": 536
},
"msaa_enum": 0,
"primitives": 645010.0,
"profile": "low",
"render_scale": 0.699999988079071,
"wall_frame_p50_ms": 16.676,
"wall_frame_p95_ms": 17.069
}
],
"workload": "Paused main scene, static valley camera; wall time includes vsync"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

@@ -0,0 +1,53 @@
{
"device": "Apple M1 Max",
"renderer": "opengl3",
"resolution": "1600x900",
"samples": [
{
"active_npcs": 6,
"draw_calls": 1304.0,
"meadow": {
"flower_clump_count": 1680,
"multimesh_batch_count": 4,
"visible_flower_clump_count": 1680
},
"msaa_enum": 2,
"primitives": 1070038.0,
"profile": "high",
"render_scale": 1.0,
"wall_frame_p50_ms": 22.166,
"wall_frame_p95_ms": 23.467
},
{
"active_npcs": 6,
"draw_calls": 1166.0,
"meadow": {
"flower_clump_count": 1680,
"multimesh_batch_count": 4,
"visible_flower_clump_count": 1092
},
"msaa_enum": 1,
"primitives": 810758.0,
"profile": "balanced",
"render_scale": 0.850000023841858,
"wall_frame_p50_ms": 17.903,
"wall_frame_p95_ms": 26.363
},
{
"active_npcs": 6,
"draw_calls": 1084.0,
"meadow": {
"flower_clump_count": 1680,
"multimesh_batch_count": 4,
"visible_flower_clump_count": 536
},
"msaa_enum": 0,
"primitives": 645010.0,
"profile": "low",
"render_scale": 0.699999988079071,
"wall_frame_p50_ms": 13.533,
"wall_frame_p95_ms": 16.623
}
],
"workload": "Paused main scene, static valley camera; wall time includes vsync"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 KiB

@@ -0,0 +1,34 @@
{
"renderer": "metal",
"resolution": "1600x900",
"samples": [
{
"active_npcs": 6,
"draw_calls": 702.0,
"frame_wall_p50_ms": 16.646,
"frame_wall_p95_ms": 16.903,
"objects": 1114.0,
"primitives": 684656.0,
"profile": "high"
},
{
"active_npcs": 6,
"draw_calls": 613.0,
"frame_wall_p50_ms": 16.665,
"frame_wall_p95_ms": 16.93,
"objects": 1031.0,
"primitives": 486192.0,
"profile": "balanced"
},
{
"active_npcs": 6,
"draw_calls": 592.0,
"frame_wall_p50_ms": 16.659,
"frame_wall_p95_ms": 16.989,
"objects": 999.0,
"primitives": 377596.0,
"profile": "low"
}
],
"workload": "Paused static valley; wall-frame latency includes vsync, not isolated GPU time"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+8 -108
View File
@@ -1,72 +1,7 @@
[gd_scene load_steps=17 format=3] [gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://player/player_visual.gd" id="1_visual"] [ext_resource type="Script" path="res://player/player_visual.gd" id="1_visual"]
[sub_resource type="StandardMaterial3D" id="Material_tunic"]
albedo_color = Color(0.18, 0.46, 0.48, 1)
roughness = 0.84
[sub_resource type="CylinderMesh" id="Mesh_body"]
material = SubResource("Material_tunic")
top_radius = 0.28
bottom_radius = 0.42
height = 0.9
[sub_resource type="StandardMaterial3D" id="Material_skin"]
albedo_color = Color(0.84, 0.61, 0.39, 1)
roughness = 0.88
[sub_resource type="SphereMesh" id="Mesh_head"]
material = SubResource("Material_skin")
radius = 0.29
height = 0.56
[sub_resource type="StandardMaterial3D" id="Material_hair"]
albedo_color = Color(0.2, 0.105, 0.05, 1)
roughness = 0.92
[sub_resource type="CylinderMesh" id="Mesh_hair"]
material = SubResource("Material_hair")
top_radius = 0.24
bottom_radius = 0.29
height = 0.18
[sub_resource type="CylinderMesh" id="Mesh_arm"]
material = SubResource("Material_tunic")
top_radius = 0.095
bottom_radius = 0.11
height = 0.58
[sub_resource type="SphereMesh" id="Mesh_hand"]
material = SubResource("Material_skin")
radius = 0.12
height = 0.22
[sub_resource type="StandardMaterial3D" id="Material_pants"]
albedo_color = Color(0.15, 0.21, 0.24, 1)
roughness = 0.9
[sub_resource type="CapsuleMesh" id="Mesh_leg"]
material = SubResource("Material_pants")
radius = 0.115
height = 0.62
[sub_resource type="StandardMaterial3D" id="Material_boot"]
albedo_color = Color(0.13, 0.075, 0.04, 1)
roughness = 0.95
[sub_resource type="BoxMesh" id="Mesh_foot"]
material = SubResource("Material_boot")
size = Vector3(0.23, 0.15, 0.36)
[sub_resource type="StandardMaterial3D" id="Material_accent"]
albedo_color = Color(0.86, 0.4, 0.18, 1)
roughness = 0.82
[sub_resource type="BoxMesh" id="Mesh_scarf"]
material = SubResource("Material_accent")
size = Vector3(0.48, 0.1, 0.38)
[sub_resource type="StandardMaterial3D" id="Material_sword"] [sub_resource type="StandardMaterial3D" id="Material_sword"]
albedo_color = Color(0.72, 0.74, 0.78, 1) albedo_color = Color(0.72, 0.74, 0.78, 1)
roughness = 0.32 roughness = 0.32
@@ -85,67 +20,29 @@ top_radius = 0.045
bottom_radius = 0.045 bottom_radius = 0.045
height = 0.16 height = 0.16
[sub_resource type="SphereMesh" id="Mesh_satchel"]
material = SubResource("Material_accent")
radius = 0.22
height = 0.34
[node name="PlayerVisual" type="Node3D"] [node name="PlayerVisual" type="Node3D"]
script = ExtResource("1_visual") script = ExtResource("1_visual")
[node name="Body" type="MeshInstance3D" parent="."] [node name="Body" type="MeshInstance3D" parent="."]
position = Vector3(0, 1.05, 0) position = Vector3(0, 1.05, 0)
mesh = SubResource("Mesh_body")
[node name="Scarf" type="MeshInstance3D" parent="."]
position = Vector3(0, 1.42, 0.02)
mesh = SubResource("Mesh_scarf")
[node name="Head" type="MeshInstance3D" parent="."] [node name="Head" type="MeshInstance3D" parent="."]
position = Vector3(0, 1.68, 0) position = Vector3(0, 1.68, 0)
mesh = SubResource("Mesh_head")
[node name="Hair" type="MeshInstance3D" parent="."] [node name="Hair" type="MeshInstance3D" parent="."]
position = Vector3(0, 1.9, -0.015) position = Vector3(0, 1.9, -0.015)
mesh = SubResource("Mesh_hair")
[node name="ArmLeft" type="MeshInstance3D" parent="."] [node name="ArmLeft" type="MeshInstance3D" parent="."]
position = Vector3(-0.39, 1.08, 0) position = Vector3(-0.39, 1.3, 0)
rotation = Vector3(0, 0, -0.139626)
mesh = SubResource("Mesh_arm")
[node name="Hand" type="MeshInstance3D" parent="ArmLeft"]
position = Vector3(0, -0.35, 0)
mesh = SubResource("Mesh_hand")
[node name="ArmRight" type="MeshInstance3D" parent="."] [node name="ArmRight" type="MeshInstance3D" parent="."]
position = Vector3(0.39, 1.08, 0) position = Vector3(0.39, 1.3, 0)
rotation = Vector3(0, 0, 0.139626)
mesh = SubResource("Mesh_arm")
[node name="Hand" type="MeshInstance3D" parent="ArmRight"]
position = Vector3(0, -0.35, 0)
mesh = SubResource("Mesh_hand")
[node name="LegLeft" type="MeshInstance3D" parent="."] [node name="LegLeft" type="MeshInstance3D" parent="."]
position = Vector3(-0.18, 0.43, 0) position = Vector3(-0.18, 0.61, 0)
mesh = SubResource("Mesh_leg")
[node name="Foot" type="MeshInstance3D" parent="LegLeft"]
position = Vector3(0, -0.28, 0.09)
mesh = SubResource("Mesh_foot")
[node name="LegRight" type="MeshInstance3D" parent="."] [node name="LegRight" type="MeshInstance3D" parent="."]
position = Vector3(0.18, 0.43, 0) position = Vector3(0.18, 0.61, 0)
mesh = SubResource("Mesh_leg")
[node name="Foot" type="MeshInstance3D" parent="LegRight"]
position = Vector3(0, -0.28, 0.09)
mesh = SubResource("Mesh_foot")
[node name="Satchel" type="MeshInstance3D" parent="."]
position = Vector3(-0.43, 0.87, -0.04)
mesh = SubResource("Mesh_satchel")
[node name="Sword" type="Node3D" parent="."] [node name="Sword" type="Node3D" parent="."]
position = Vector3(0.52, 1.22, 0.12) position = Vector3(0.52, 1.22, 0.12)
@@ -158,3 +55,6 @@ mesh = SubResource("Mesh_blade")
[node name="Grip" type="MeshInstance3D" parent="Sword"] [node name="Grip" type="MeshInstance3D" parent="Sword"]
position = Vector3(0, -0.05, 0) position = Vector3(0, -0.05, 0)
mesh = SubResource("Mesh_grip") mesh = SubResource("Mesh_grip")
[node name="Tail" type="MeshInstance3D" parent="."]
position = Vector3(0, 0.8, -0.22)

Some files were not shown because too many files have changed in this diff Show More