Compare commits
7 Commits
49c5bda651
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ff8858b19 | |||
| 70ee6feddf | |||
| ec16e2f2ca | |||
| 7899d36f03 | |||
| da3bed67a0 | |||
| 6aae41d36f | |||
| 52307f1306 |
@@ -3,11 +3,58 @@
|
||||
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:
|
||||
|
||||
## 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
|
||||
|
||||
1. Read the relevant roadmap/docs before changing code.
|
||||
- Start with `docs/README.md`.
|
||||
- Use `docs/LEARNING_ROADMAP.md` for the current next item.
|
||||
- Start with `docs/README.md` and `docs/DEVELOPER_INDEX.md`.
|
||||
- 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
|
||||
their stated scope.
|
||||
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
|
||||
```
|
||||
|
||||
For changed-file quick checks:
|
||||
To narrow formatter/linter scope to changed GDScript files:
|
||||
|
||||
```bash
|
||||
./tools/quality.sh --changed
|
||||
@@ -47,18 +94,69 @@ For changed-file quick checks:
|
||||
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`
|
||||
so headless Godot 4.7 can run without crashing when platform user-data paths
|
||||
are unavailable. Do not remove that behavior.
|
||||
|
||||
Useful extra checks:
|
||||
|
||||
```powershell
|
||||
```bash
|
||||
git diff --check
|
||||
```
|
||||
|
||||
If touching `main.tscn` or runtime wiring, also do a direct Godot 4.7 headless
|
||||
boot when practical.
|
||||
If changing `project.godot`, `main.tscn`, autoload/plugin configuration, scene
|
||||
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
|
||||
|
||||
@@ -73,12 +171,29 @@ Preserve the simulation/presentation boundary.
|
||||
`ActionTargetResolver`.
|
||||
- Visual movement belongs to `WorldViewManager`/`NpcVisual`; decision and task
|
||||
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.
|
||||
Do not extract generic frameworks before multiple real consumers justify them.
|
||||
|
||||
## 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
|
||||
visible happenings must arise from ordinary simulation state and events rather
|
||||
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,
|
||||
presentation, serialization, and lifecycle:
|
||||
|
||||
- resources: `ResourceNode` + `ResourceStateRecord` (amount, yield, regrowth);
|
||||
- creatures: `CreatureVisual` shared movement root (navigate to the
|
||||
authoritative simulation position, report position, death), with per-type
|
||||
visuals built from definitions;
|
||||
- enemies: `EnemyDefinition` + `SimulationEnemies` registry driving combatant
|
||||
spawns and hostile visuals;
|
||||
- animals: `AnimalNode` + `AnimalStateRecord` following the same
|
||||
follow-the-authoritative-position contract.
|
||||
- resources: immutable resource definitions plus authoritative
|
||||
`ResourceStateRecord`; `ResourceNode` is the loaded provider and visual
|
||||
binding;
|
||||
- creatures: `CreatureVisual` is the shared navigation/death presentation root;
|
||||
- enemies: `EnemyDefinition`/`SimulationEnemies` provide typed content,
|
||||
`CombatantFactory` and `ConflictSystem` own authoritative instances and
|
||||
lifecycle, and `HostileCombatant` presents them;
|
||||
- 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
|
||||
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
|
||||
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
|
||||
|
||||
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;
|
||||
- player/NPC parity;
|
||||
- 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.
|
||||
|
||||
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
|
||||
|
||||
Update docs when the implemented behavior changes the roadmap, architecture, or
|
||||
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/RESOURCE_NODE_MIGRATION.md` for resource-target migration status;
|
||||
- `docs/SIMULATION_STATE_SCHEMA.md` for serialized state changes;
|
||||
- `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/decisions/` only for durable architectural decisions.
|
||||
|
||||
@@ -190,16 +335,31 @@ trust code/tests first, then update docs.
|
||||
|
||||
## Git expectations
|
||||
|
||||
Preserve user changes. Check `git status --short` before editing and before
|
||||
committing. Do not use destructive cleanup commands unless explicitly asked.
|
||||
Preserve user changes. Check `git status --short --branch` before editing and
|
||||
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`
|
||||
- `fix: stabilize godot 4.7 headless validation`
|
||||
- `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
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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.
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -21,10 +21,11 @@ own persistent quantities, travel completion, or quest progress.
|
||||
The long-term direction is one regional simulation containing settlements,
|
||||
routes, caravans, named people, aggregate cohorts, and polities. The current
|
||||
repository has the regional records, deterministic scheduler, one exact
|
||||
caravan transfer service, chunked persistence, and a 20-caravan structural
|
||||
benchmark. It does not yet run that regional service as the production
|
||||
`SimulationManager` facade or provide a complete market, settlement-growth, or
|
||||
kingdom gameplay loop. That distinction is important when extending the game.
|
||||
caravan transfer service, a production facade for one inbound Jajce route, a
|
||||
combined local/regional save manifest, chunked persistence, and a 20-caravan
|
||||
structural benchmark. It does not yet provide a complete market,
|
||||
settlement-growth, or kingdom gameplay loop. That distinction is important
|
||||
when extending the game.
|
||||
|
||||
## 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 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/` |
|
||||
| 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` |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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
|
||||
|
||||
@@ -106,9 +108,10 @@ relationship, or reward that the event claims happened.
|
||||
- `simulation/events/`, `knowledge/`, `relationships/`, `situations/`, and
|
||||
`dialogue/` — causal history and emergent narrative layers.
|
||||
- `simulation/state/` — versioned primitive records and validation.
|
||||
- `simulation/persistence/` — local quicksave safety.
|
||||
- `simulation/regional/` — regional records, scheduler, caravan proof, chunk
|
||||
codec, and file generations.
|
||||
- `simulation/persistence/` — combined quicksave manifest and atomic slot
|
||||
replacement, plus regional chunk codecs and generations.
|
||||
- `simulation/regional/` — production facade, regional records, scheduler,
|
||||
caravan transaction, retention planning, and scale contracts.
|
||||
|
||||
### Loaded world and presentation
|
||||
|
||||
@@ -161,12 +164,11 @@ are used when an existing save is loaded.
|
||||
|
||||
The following are deliberately **not** claimed as complete:
|
||||
|
||||
- wire `RegionalCaravanService` into the production regional facade and local
|
||||
save manifest;
|
||||
- integrate event retention/rollups/chunked history into one atomic,
|
||||
lazily loaded long-horizon save;
|
||||
- run 20 real caravan trades across five settlement economies with atomic
|
||||
cargo/stock ledgers;
|
||||
- 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,
|
||||
armies, campaigns, and aggregate conflict;
|
||||
- add visual relevance streaming to the live world view rather than only the
|
||||
|
||||
@@ -38,19 +38,92 @@ spawn is a next integration.
|
||||
`JajceWorld` owns reversible High, Balanced, and Low presentation profiles with
|
||||
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 |
|
||||
| Balanced (default) | 0.85 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 |
|
||||
| High | authored / authored 4× | authored (currently 180 m) | authored when renderer supports it | authored density/interactors |
|
||||
| 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 / 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
|
||||
values; viewport scale restoration is guarded by the current quality owner so a
|
||||
second world cannot clobber the active owner. Compatibility/mobile feature
|
||||
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
|
||||
|
||||
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;
|
||||
they no longer rebuild all candidate data every rendered frame.
|
||||
- `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),
|
||||
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
|
||||
settlements, routes, caravans, named people, aggregate cohorts, and polities.
|
||||
It is intentionally additive: the current Jajce `SimulationManager` remains
|
||||
the playable local facade while the regional contracts prove deterministic
|
||||
unloaded work and conservation in isolation.
|
||||
The production `SimulationManager` now owns a bounded regional facade for one
|
||||
inbound Jajce route, while the broader regional contracts continue to prove
|
||||
deterministic unloaded work and conservation before wider integration.
|
||||
|
||||
## 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,
|
||||
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
|
||||
|
||||
`RegionalChunkedPersistence` captures the service into a manifest plus fixed
|
||||
@@ -89,11 +95,20 @@ primitive-tree bounds.
|
||||
|
||||
## 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;
|
||||
- `RegionalCaravanService` is a complete isolated transfer proof, not the live
|
||||
economy's 20-trade scheduler;
|
||||
The remaining boundary is intentionally narrow:
|
||||
|
||||
- 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
|
||||
long-horizon save transaction;
|
||||
- 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.
|
||||
|
||||
Do not create one `SimulationManager` per settlement or caravan. The next
|
||||
production step is an explicit regional facade that delegates to these records
|
||||
and services, while the active adapter presents only a bounded relevant context.
|
||||
production step is to publish retained raw events, daily rollups, and chunked
|
||||
regional history as one atomic, lazily loaded transaction behind the facade.
|
||||
|
||||
## Scale evidence
|
||||
|
||||
|
||||
@@ -64,11 +64,22 @@ catch corruption.
|
||||
|
||||
## Local file safety
|
||||
|
||||
`SaveSlotStore` validates the complete JSON before replacement, restricts slot
|
||||
names and size, preserves a previous file, and can recover a backup after an
|
||||
interrupted replacement. `F5`/`F9` use the current quicksave path in `main.tscn`.
|
||||
The player transform and presentation-only scene state are intentionally not
|
||||
saved.
|
||||
`SimulationSaveManifest` v1 combines the unchanged local v16 record and the
|
||||
canonical `RegionalSimulationFacade` envelope. It stores an independent
|
||||
checksum for each authority and cross-validates world, seed, active location,
|
||||
and processed tick. A rejected manifest cannot partially replace local or
|
||||
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
|
||||
|
||||
@@ -79,9 +90,10 @@ per-file checksums. The file store uses immutable generations, a current
|
||||
pointer, recovery scanning, and conservative pruning. Partial active-location
|
||||
loads are metadata-only and cannot silently resurrect a removed location.
|
||||
|
||||
This regional codec is not yet the backend for local `SaveSlotStore`. Integrate
|
||||
it only after the production regional facade, event retention, and lazy context
|
||||
loading have one atomic manifest contract.
|
||||
This regional chunk codec is not yet the backend for `SaveSlotStore`. The
|
||||
production facade and combined quicksave manifest are complete; retained raw
|
||||
events, rollups, chunk generations, and lazy context loading still need one
|
||||
atomic long-horizon publication contract.
|
||||
|
||||
## How to change state safely
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ For a new system, add tests for:
|
||||
|
||||
## 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
|
||||
on the reviewed development host. The regional baseline proves the target
|
||||
record counts and scheduler parity. Neither measures a complete regional
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# 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
|
||||
> content/catalog, shared target/command, event/social, situation/dialogue,
|
||||
> 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
|
||||
> 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
|
||||
save/load. Definition packs now cover reusable items, resources, storage,
|
||||
enemies, and animals. Regional records, deterministic scheduled work, bounded
|
||||
presentation relevance, and the first authoritative caravan transfer form the
|
||||
next world-slice foundation. See
|
||||
presentation relevance, and the first authoritative caravan transfer now feed
|
||||
one production facade and combined local/regional save boundary. See
|
||||
[ADR 0001](decisions/0001-simulation-authority-boundary.md),
|
||||
[ADR 0002](decisions/0002-emergent-world-content-and-narrative.md), and the
|
||||
[regional simulation contract](REGIONAL_SIMULATION.md).
|
||||
@@ -1107,6 +1108,12 @@ resolution, and personal request accept/decline.
|
||||
|
||||
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
|
||||
nearest loaded living villager and shows live name, action, task state, exact
|
||||
target, carried inventory, and a currently matching decision reason. It
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# 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
|
||||
> implemented and tested. They are still an additive regional package; the
|
||||
> playable Jajce `SimulationManager` and `SaveSlotStore` remain the local
|
||||
> production authority until the integrations listed at the end land.
|
||||
> implemented and tested. `SimulationManager` now owns a production regional
|
||||
> facade for one inbound Jajce route, and `SaveSlotStore` persists local and
|
||||
> 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
|
||||
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
|
||||
construction order.
|
||||
|
||||
The current local `SimulationStateRecord` remains the v16 Jajce save boundary.
|
||||
Regional state is deliberately carried in its own versioned service envelope
|
||||
until one playable route consumes it; this avoids placing speculative regional
|
||||
fields in every local save. The merge into the production save manifest must
|
||||
retain this canonical regional payload rather than copying its fields into
|
||||
scene nodes.
|
||||
The current local `SimulationStateRecord` remains the unchanged v16 Jajce
|
||||
record. `SimulationSaveManifest` v1 combines that record with the canonical
|
||||
`RegionalSimulationFacade` envelope and checksums both authorities. Regional
|
||||
fields are not copied into local records or scene nodes.
|
||||
|
||||
## 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
|
||||
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
|
||||
|
||||
`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
|
||||
tests:
|
||||
|
||||
1. make the Jajce route proof callable from the production regional facade and
|
||||
save manifest;
|
||||
2. integrate the retention/rollup and chunk codecs as one atomic,
|
||||
1. integrate the retention/rollup and chunk codecs as one atomic,
|
||||
lazily loaded history transaction;
|
||||
3. schedule 20 ordinary caravan trades across five settlement economies;
|
||||
4. promote/demote presentation and population fidelity while pinning named and
|
||||
2. schedule 20 ordinary caravan trades across five settlement economies;
|
||||
3. promote/demote presentation and population fidelity while pinning named and
|
||||
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.
|
||||
|
||||
Context-owned world-target registries and the first shared player/NPC activity
|
||||
|
||||
@@ -451,12 +451,20 @@ replayed after restore.
|
||||
|
||||
## Local quicksave boundary
|
||||
|
||||
`SaveSlotStore` writes the existing versioned local JSON record to
|
||||
`user://saves/quicksave.json`. It validates the serialized record before
|
||||
replacement, limits file size, restricts slot names, preserves the previous
|
||||
file during replacement, and can recover that backup if replacement is
|
||||
interrupted. Loading parses and validates the complete record before mutating
|
||||
the simulation.
|
||||
`SaveSlotStore` writes `SimulationSaveManifest` v1 to
|
||||
`user://saves/quicksave.json`. The manifest contains the unchanged local v16
|
||||
record and canonical regional facade envelope, with an independent checksum
|
||||
for each. Restore also requires world, seed, active location, and tick to agree
|
||||
across both authorities, and rolls both back if local restoration fails.
|
||||
|
||||
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
|
||||
visuals from authoritative state.
|
||||
@@ -476,8 +484,8 @@ This phase does not yet provide:
|
||||
- persistence of a currently open dialogue UI/turn. Semantic conversation
|
||||
history and commitment authority persist, while a balloon is regenerated
|
||||
after restore;
|
||||
- production integration of the regional chunked save manifest, lazy location
|
||||
loading, or aggregate population promotion/persistence;
|
||||
- production integration of regional retained/rolled-up chunk history, lazy
|
||||
location loading, or aggregate population promotion/persistence;
|
||||
- persistence for the player transform or presentation-only scene state.
|
||||
|
||||
Those features should build on this boundary rather than inventing parallel
|
||||
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 371 KiB |
|
After Width: | Height: | Size: 461 KiB |
|
After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 249 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 1006 KiB |
|
After Width: | Height: | Size: 1008 KiB |
|
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"
|
||||
}
|
||||
|
After Width: | Height: | Size: 1000 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
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"
|
||||
}
|
||||
|
After Width: | Height: | Size: 709 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 815 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
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"
|
||||
}
|
||||
|
After Width: | Height: | Size: 469 KiB |
|
After Width: | Height: | Size: 809 KiB |
|
After Width: | Height: | Size: 798 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
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"
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.1 MiB |
@@ -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"]
|
||||
|
||||
[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"]
|
||||
albedo_color = Color(0.72, 0.74, 0.78, 1)
|
||||
roughness = 0.32
|
||||
@@ -85,67 +20,29 @@ top_radius = 0.045
|
||||
bottom_radius = 0.045
|
||||
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"]
|
||||
script = ExtResource("1_visual")
|
||||
|
||||
[node name="Body" type="MeshInstance3D" parent="."]
|
||||
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="."]
|
||||
position = Vector3(0, 1.68, 0)
|
||||
mesh = SubResource("Mesh_head")
|
||||
|
||||
[node name="Hair" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0, 1.9, -0.015)
|
||||
mesh = SubResource("Mesh_hair")
|
||||
|
||||
[node name="ArmLeft" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(-0.39, 1.08, 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")
|
||||
position = Vector3(-0.39, 1.3, 0)
|
||||
|
||||
[node name="ArmRight" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0.39, 1.08, 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")
|
||||
position = Vector3(0.39, 1.3, 0)
|
||||
|
||||
[node name="LegLeft" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(-0.18, 0.43, 0)
|
||||
mesh = SubResource("Mesh_leg")
|
||||
|
||||
[node name="Foot" type="MeshInstance3D" parent="LegLeft"]
|
||||
position = Vector3(0, -0.28, 0.09)
|
||||
mesh = SubResource("Mesh_foot")
|
||||
position = Vector3(-0.18, 0.61, 0)
|
||||
|
||||
[node name="LegRight" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0.18, 0.43, 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")
|
||||
position = Vector3(0.18, 0.61, 0)
|
||||
|
||||
[node name="Sword" type="Node3D" parent="."]
|
||||
position = Vector3(0.52, 1.22, 0.12)
|
||||
@@ -158,3 +55,6 @@ mesh = SubResource("Mesh_blade")
|
||||
[node name="Grip" type="MeshInstance3D" parent="Sword"]
|
||||
position = Vector3(0, -0.05, 0)
|
||||
mesh = SubResource("Mesh_grip")
|
||||
|
||||
[node name="Tail" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0, 0.8, -0.22)
|
||||
|
||||