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
|
This repository is a Godot 4.7 simulation prototype. Treat it as a living
|
||||||
systems project, not a content dump. The expected working style is:
|
systems project, not a content dump. The expected working style is:
|
||||||
|
|
||||||
|
## Start here and sources of truth
|
||||||
|
|
||||||
|
Begin with `docs/README.md`, then follow it to `docs/DEVELOPER_INDEX.md`. The
|
||||||
|
developer index is the shortest current map from a feature request to its code,
|
||||||
|
focused contract, tests, and known incomplete work.
|
||||||
|
|
||||||
|
When sources disagree, use this order:
|
||||||
|
|
||||||
|
1. Current code and focused tests establish implemented behavior; they do not
|
||||||
|
silently supersede an architectural decision.
|
||||||
|
2. Applicable Accepted records in `docs/decisions/` define durable architecture
|
||||||
|
within their stated scope until another Accepted record explicitly
|
||||||
|
supersedes them. Treat conflicting code as drift to fix or document.
|
||||||
|
`docs/ARCHITECTURE_OVERVIEW.md` is the current ownership/dependency map.
|
||||||
|
3. `docs/DEVELOPER_INDEX.md` and the focused architecture/schema guides define
|
||||||
|
the current documented contract.
|
||||||
|
4. `docs/LEARNING_ROADMAP.md` defines sequencing, not runtime truth.
|
||||||
|
|
||||||
|
Fix the smallest authoritative document after changing behavior. Do not create
|
||||||
|
a second implementation in prose or copy the same status table into many docs.
|
||||||
|
|
||||||
|
## Codebase discovery with codebase-memory
|
||||||
|
|
||||||
|
This repository is indexed as
|
||||||
|
`Users-rijadzuzo-dev-private-gamedev-the-steward` in codebase-memory. Treat the
|
||||||
|
name as a lookup hint, not proof that the graph is current.
|
||||||
|
|
||||||
|
- At session start, after a pull, and after changing branches, call
|
||||||
|
`list_projects` and `index_status`. Confirm the indexed root, branch, and HEAD
|
||||||
|
match the checkout; re-index after a large or external update.
|
||||||
|
- Use Verify/Tier 2 evidence by default: `search_graph` to find exact symbols,
|
||||||
|
`trace_path` in the material direction, and `get_code_snippet` for source.
|
||||||
|
Use `get_architecture` only for orientation, not as a substitute for exact
|
||||||
|
code.
|
||||||
|
- Check `has_more`/cursors and paginate relevant results. After candidate files
|
||||||
|
are known, call `check_index_coverage` once with every evidence path.
|
||||||
|
- A clean coverage result is best-effort, not proof of completeness. Read exact
|
||||||
|
source for partial, skipped, excluded, stale, pending, or unknown ranges.
|
||||||
|
- Use `rg` for string literals, resource paths, scene/config files, generated
|
||||||
|
data, and graph coverage gaps. Do not make negative or exhaustive claims from
|
||||||
|
a provisional graph search.
|
||||||
|
- Before a refactor, use `detect_changes` or inbound traces to inspect the blast
|
||||||
|
radius. Re-run focused searches after edits rather than relying on this file
|
||||||
|
as a frozen graph dump.
|
||||||
|
|
||||||
## Default development loop
|
## Default development loop
|
||||||
|
|
||||||
1. Read the relevant roadmap/docs before changing code.
|
1. Read the relevant roadmap/docs before changing code.
|
||||||
- Start with `docs/README.md`.
|
- Start with `docs/README.md` and `docs/DEVELOPER_INDEX.md`.
|
||||||
- Use `docs/LEARNING_ROADMAP.md` for the current next item.
|
- Use `docs/DEVELOPER_INDEX.md` for current status and open gaps.
|
||||||
|
- Use `docs/LEARNING_ROADMAP.md` for milestone sequencing and exit tests;
|
||||||
|
reconcile any candidate slice with current code and focused tests.
|
||||||
- Use focused plans such as `docs/RESOURCE_NODE_MIGRATION.md` only within
|
- Use focused plans such as `docs/RESOURCE_NODE_MIGRATION.md` only within
|
||||||
their stated scope.
|
their stated scope.
|
||||||
2. Inspect the current code and tests before assuming roadmap status is still
|
2. Inspect the current code and tests before assuming roadmap status is still
|
||||||
@@ -37,7 +84,7 @@ Use the project quality gate for the current platform:
|
|||||||
powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1
|
powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
For changed-file quick checks:
|
To narrow formatter/linter scope to changed GDScript files:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./tools/quality.sh --changed
|
./tools/quality.sh --changed
|
||||||
@@ -47,18 +94,69 @@ For changed-file quick checks:
|
|||||||
powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1 -Changed
|
powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1 -Changed
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`--changed` narrows only the `gdformat` and `gdlint` file set. It still runs the
|
||||||
|
Godot import/parser check, every headless scenario, the compatibility-renderer
|
||||||
|
scenario, and GUT. Use it only while iterating; run the full current-platform
|
||||||
|
gate before every commit.
|
||||||
|
|
||||||
The quality scripts isolate Godot's user profile under `logs/quality/godot_profile`
|
The quality scripts isolate Godot's user profile under `logs/quality/godot_profile`
|
||||||
so headless Godot 4.7 can run without crashing when platform user-data paths
|
so headless Godot 4.7 can run without crashing when platform user-data paths
|
||||||
are unavailable. Do not remove that behavior.
|
are unavailable. Do not remove that behavior.
|
||||||
|
|
||||||
Useful extra checks:
|
Useful extra checks:
|
||||||
|
|
||||||
```powershell
|
```bash
|
||||||
git diff --check
|
git diff --check
|
||||||
```
|
```
|
||||||
|
|
||||||
If touching `main.tscn` or runtime wiring, also do a direct Godot 4.7 headless
|
If changing `project.godot`, `main.tscn`, autoload/plugin configuration, scene
|
||||||
boot when practical.
|
UIDs or node paths, or startup/runtime scene wiring, also run the configured
|
||||||
|
main scene with the same Godot 4.7 binary used by the gate:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"$GODOT_BIN" --headless --path "$PWD" --quit-after 3
|
||||||
|
```
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& $env:GODOT_BIN --headless --path (Get-Location).Path --quit-after 3
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `GODOT_BIN` when the gate auto-detected a binary instead. Inspect output as
|
||||||
|
well as exit status, and accept only diagnostics matching the quality gate's
|
||||||
|
exact allowlist. Read failures from `logs/quality/latest/`; do not weaken an
|
||||||
|
allowlist or skip a failing scenario merely to make the gate green.
|
||||||
|
|
||||||
|
## Current codebase map
|
||||||
|
|
||||||
|
Keep this map concise; `docs/DEVELOPER_INDEX.md` owns the detailed feature
|
||||||
|
matrix and extension workflow.
|
||||||
|
|
||||||
|
- `project.godot` starts `main.tscn`. The main scene wires the authored Jajce
|
||||||
|
world, player, `ActiveWorldAdapter`, `SimulationManager`,
|
||||||
|
`SaveSlotController`, `WorldViewManager`, and UI surfaces.
|
||||||
|
- `simulation/SimulationManager.gd` is the scene-tree facade and deterministic
|
||||||
|
tick boundary. It advances regional authority before committing the local
|
||||||
|
tick, then coordinates focused systems rather than duplicating their rules.
|
||||||
|
- `simulation/actions/`, `simulation/commands/`, and `simulation/economy/` own
|
||||||
|
selection/execution, target resolution, shared player/NPC command contracts,
|
||||||
|
and exact inventory or storage transactions.
|
||||||
|
- `simulation/events/`, `simulation/knowledge/`, `simulation/relationships/`,
|
||||||
|
`simulation/opportunities/`, `simulation/situations/`,
|
||||||
|
`simulation/dialogue/`, and `simulation/quests/` turn completed mutations
|
||||||
|
into causal facts and derived player-facing projections.
|
||||||
|
- `simulation/state/` owns versioned primitive records;
|
||||||
|
`simulation/definitions/` owns immutable definitions and stable IDs;
|
||||||
|
`simulation/regional/` owns scheduled distant work and caravan authority;
|
||||||
|
`simulation/persistence/` owns combined manifests and safe slot replacement.
|
||||||
|
- `world/active_world_adapter.gd` and `world/targets/` own contextual loaded-
|
||||||
|
world discovery, descriptors, capabilities, and transient handles. Resource,
|
||||||
|
storage, activity, and animal nodes are providers bound to simulation state.
|
||||||
|
- `world/world_view_manager.gd`, `world/presentation/`, `world/jajce/`,
|
||||||
|
`world/ui/`, and `player/` own input, geometry, navigation, and visible
|
||||||
|
feedback. They submit commands or report facts; they do not author saves.
|
||||||
|
- `tests/` contains deterministic headless scenarios and GUT tests;
|
||||||
|
`simulation/benchmark/` and `docs/benchmarks/` contain reproducible workload
|
||||||
|
evidence; `tools/` contains the cross-platform quality gate.
|
||||||
|
|
||||||
## Architecture direction
|
## Architecture direction
|
||||||
|
|
||||||
@@ -73,12 +171,29 @@ Preserve the simulation/presentation boundary.
|
|||||||
`ActionTargetResolver`.
|
`ActionTargetResolver`.
|
||||||
- Visual movement belongs to `WorldViewManager`/`NpcVisual`; decision and task
|
- Visual movement belongs to `WorldViewManager`/`NpcVisual`; decision and task
|
||||||
execution belong to the simulation systems.
|
execution belong to the simulation systems.
|
||||||
|
- Player and NPC callers use the same commands and capabilities. Revalidate the
|
||||||
|
actor, target, context, range, revision, cost, and permission at execution;
|
||||||
|
mutate atomically before recording facts or deriving projections.
|
||||||
|
- Target handles, generation tokens, spatial/population indexes, presentation
|
||||||
|
cues, and reason traces are disposable derived state. Persist stable
|
||||||
|
contextual IDs and primitives, then prove rebuilding derived state preserves
|
||||||
|
decisions and checksums.
|
||||||
|
- A local tick may advance only after the regional facade succeeds. Regional
|
||||||
|
work must remain deterministic, bounded, fail-closed, and rollback-safe.
|
||||||
|
- When regional authority exists, save and restore must use the combined
|
||||||
|
manifest and remain all-or-nothing. Preserve schema validation, size bounds,
|
||||||
|
restore rollback, temporary-file validation, and the atomic
|
||||||
|
temporary/backup/final replacement path; a failed restore emits no success
|
||||||
|
notification.
|
||||||
|
|
||||||
When adding a system, first prove the contract with one real gameplay use case.
|
When adding a system, first prove the contract with one real gameplay use case.
|
||||||
Do not extract generic frameworks before multiple real consumers justify them.
|
Do not extract generic frameworks before multiple real consumers justify them.
|
||||||
|
|
||||||
## Emergent-world doctrine
|
## Emergent-world doctrine
|
||||||
|
|
||||||
|
These agent-facing guardrails summarize ADR 0001 and ADR 0002. The Accepted
|
||||||
|
records remain authoritative within their stated scopes.
|
||||||
|
|
||||||
The game world is the source of narrative truth. Dialogue, tasks, quests, and
|
The game world is the source of narrative truth. Dialogue, tasks, quests, and
|
||||||
visible happenings must arise from ordinary simulation state and events rather
|
visible happenings must arise from ordinary simulation state and events rather
|
||||||
than maintaining parallel scripted copies.
|
than maintaining parallel scripted copies.
|
||||||
@@ -134,14 +249,16 @@ behaviour, not as a one-off object. Add a new type through data/definitions
|
|||||||
plus a small behaviour or visual hook, reusing the root for movement,
|
plus a small behaviour or visual hook, reusing the root for movement,
|
||||||
presentation, serialization, and lifecycle:
|
presentation, serialization, and lifecycle:
|
||||||
|
|
||||||
- resources: `ResourceNode` + `ResourceStateRecord` (amount, yield, regrowth);
|
- resources: immutable resource definitions plus authoritative
|
||||||
- creatures: `CreatureVisual` shared movement root (navigate to the
|
`ResourceStateRecord`; `ResourceNode` is the loaded provider and visual
|
||||||
authoritative simulation position, report position, death), with per-type
|
binding;
|
||||||
visuals built from definitions;
|
- creatures: `CreatureVisual` is the shared navigation/death presentation root;
|
||||||
- enemies: `EnemyDefinition` + `SimulationEnemies` registry driving combatant
|
- enemies: `EnemyDefinition`/`SimulationEnemies` provide typed content,
|
||||||
spawns and hostile visuals;
|
`CombatantFactory` and `ConflictSystem` own authoritative instances and
|
||||||
- animals: `AnimalNode` + `AnimalStateRecord` following the same
|
lifecycle, and `HostileCombatant` presents them;
|
||||||
follow-the-authoritative-position contract.
|
- animals: `AnimalDefinition`, `AnimalStateRecord`, `AnimalFactory`, and
|
||||||
|
`AnimalCareSystem` own content and lifecycle; `AnimalNode` is the loaded
|
||||||
|
interaction/navigation binding.
|
||||||
|
|
||||||
A new berry, tree, bear, bandit, boar, or goat should be mostly a definition
|
A new berry, tree, bear, bandit, boar, or goat should be mostly a definition
|
||||||
plus a bounded hook — never a new movement/combat/save system.
|
plus a bounded hook — never a new movement/combat/save system.
|
||||||
@@ -159,6 +276,19 @@ Resource additions should preserve:
|
|||||||
Storage uses `StorageNode`. Rest, study, and patrol use `ActivitySite`. Do not
|
Storage uses `StorageNode`. Rest, study, and patrol use `ActivitySite`. Do not
|
||||||
turn these back into generic marker zones.
|
turn these back into generic marker zones.
|
||||||
|
|
||||||
|
## Vendored dependencies and Godot asset hygiene
|
||||||
|
|
||||||
|
- Treat `addons/dialogue_manager`, `addons/gut`, and `addons/terrain_3d` as
|
||||||
|
pinned vendor code. Do not reformat, refactor, or update them unless the task
|
||||||
|
explicitly owns that dependency.
|
||||||
|
- Preserve the complete cross-platform Terrain3D payload checked by the quality
|
||||||
|
gate. Do not replace a missing platform binary with a local-only artifact.
|
||||||
|
- Preserve `.uid` files, `class_name` locations, resource UIDs, and scene paths
|
||||||
|
when moving Godot scripts or scenes. Use `git mv` for intentional moves and
|
||||||
|
prove them with import plus headless startup.
|
||||||
|
- Never commit `.godot/`, `.venv/`, `logs/quality/`, or generated local editor
|
||||||
|
state. Do not hand-edit imported cache files to hide an error.
|
||||||
|
|
||||||
## Testing expectations
|
## Testing expectations
|
||||||
|
|
||||||
Add or update headless scenarios when a change affects:
|
Add or update headless scenarios when a change affects:
|
||||||
@@ -169,19 +299,34 @@ Add or update headless scenarios when a change affects:
|
|||||||
- navigation/reachability assumptions;
|
- navigation/reachability assumptions;
|
||||||
- player/NPC parity;
|
- player/NPC parity;
|
||||||
- deterministic continuation;
|
- deterministic continuation;
|
||||||
|
- regional job ordering, rollback, or local/regional tick coordination;
|
||||||
|
- combined-manifest save/restore, migration, or atomic slot behavior;
|
||||||
|
- event causality, knowledge, situation, dialogue, or quest projections;
|
||||||
- UI/debug state that represents real simulation facts.
|
- UI/debug state that represents real simulation facts.
|
||||||
|
|
||||||
Keep tests deterministic. Prefer fixed seeds and stable IDs.
|
Keep tests deterministic. Prefer fixed seeds and stable IDs.
|
||||||
|
Any persistent-state change requires an explicit schema/migration decision,
|
||||||
|
exact validation, checksum and tamper coverage, rollback coverage, and a
|
||||||
|
deterministic continuation test.
|
||||||
|
|
||||||
## Documentation expectations
|
## Documentation expectations
|
||||||
|
|
||||||
Update docs when the implemented behavior changes the roadmap, architecture, or
|
Update docs when the implemented behavior changes the roadmap, architecture, or
|
||||||
current contract. Keep updates local:
|
current contract. Keep updates local:
|
||||||
|
|
||||||
|
- `docs/DEVELOPER_INDEX.md` for the current feature map, extension routes, and
|
||||||
|
deliberately incomplete work;
|
||||||
|
- `docs/ARCHITECTURE_OVERVIEW.md` for ownership and dependency boundaries;
|
||||||
|
- `docs/PROJECT_CONTEXT.md` only when product intent or active constraints
|
||||||
|
change; dated implementation passages are historical context;
|
||||||
- `docs/LEARNING_ROADMAP.md` for next-item sequencing and milestone status;
|
- `docs/LEARNING_ROADMAP.md` for next-item sequencing and milestone status;
|
||||||
- `docs/RESOURCE_NODE_MIGRATION.md` for resource-target migration status;
|
- `docs/RESOURCE_NODE_MIGRATION.md` for resource-target migration status;
|
||||||
- `docs/SIMULATION_STATE_SCHEMA.md` for serialized state changes;
|
- `docs/SIMULATION_STATE_SCHEMA.md` for serialized state changes;
|
||||||
- `docs/SIMULATION_DEFINITIONS.md` for action/profession/ID contracts;
|
- `docs/SIMULATION_DEFINITIONS.md` for action/profession/ID contracts;
|
||||||
|
- `docs/ACTION_SYSTEM_ARCHITECTURE.md`, `docs/ECONOMIC_EVENTS.md`, and
|
||||||
|
`docs/REGIONAL_SIMULATION.md` for their focused runtime contracts;
|
||||||
|
- `docs/FEATURE_*.md` only when the corresponding implementation/extension
|
||||||
|
guide changes;
|
||||||
- `docs/BUILD_IN_PUBLIC_PLAN.md` for Jajce/demo/readability slices;
|
- `docs/BUILD_IN_PUBLIC_PLAN.md` for Jajce/demo/readability slices;
|
||||||
- `docs/decisions/` only for durable architectural decisions.
|
- `docs/decisions/` only for durable architectural decisions.
|
||||||
|
|
||||||
@@ -190,16 +335,31 @@ trust code/tests first, then update docs.
|
|||||||
|
|
||||||
## Git expectations
|
## Git expectations
|
||||||
|
|
||||||
Preserve user changes. Check `git status --short` before editing and before
|
Preserve user changes. Check `git status --short --branch` before editing and
|
||||||
committing. Do not use destructive cleanup commands unless explicitly asked.
|
before committing, inspect the branch/upstream and recent history, and stage
|
||||||
|
only the files owned by the current slice. Do not use destructive cleanup
|
||||||
|
commands unless explicitly asked.
|
||||||
|
|
||||||
Use conventional commits, for example:
|
Every repository commit must use Conventional Commits with an imperative,
|
||||||
|
specific summary: `<type>(optional-scope): summary`. Common types are `feat`,
|
||||||
|
`fix`, `docs`, `test`, `refactor`, `perf`, `build`, `ci`, `chore`, and `merge`.
|
||||||
|
Examples:
|
||||||
|
|
||||||
- `feat: validate expanded resource discovery`
|
- `feat: validate expanded resource discovery`
|
||||||
- `fix: stabilize godot 4.7 headless validation`
|
- `fix: stabilize godot 4.7 headless validation`
|
||||||
- `docs: capture agent workflow`
|
- `docs: capture agent workflow`
|
||||||
|
|
||||||
Commit only after validation relevant to the change has passed.
|
Before committing, review `git diff`, run `git diff --check` and the full
|
||||||
|
current-platform quality gate, then review `git diff --cached`. Validation is
|
||||||
|
explicit; do not assume a local hook ran it. One coherent vertical slice may
|
||||||
|
include its implementation, deterministic tests, required `.uid`/resource
|
||||||
|
files, and smallest necessary documentation update. Split unrelated fixes,
|
||||||
|
never stage pre-existing user work, and never use `--no-verify` to bypass a
|
||||||
|
repository gate.
|
||||||
|
|
||||||
|
Do not amend, rebase, drop, or fold user-owned commits unless explicitly asked.
|
||||||
|
Do not push implicitly. After committing, report the commit hash and the real
|
||||||
|
ahead/behind or remote-sync state.
|
||||||
|
|
||||||
## Product taste
|
## Product taste
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
The long-term direction is one regional simulation containing settlements,
|
||||||
routes, caravans, named people, aggregate cohorts, and polities. The current
|
routes, caravans, named people, aggregate cohorts, and polities. The current
|
||||||
repository has the regional records, deterministic scheduler, one exact
|
repository has the regional records, deterministic scheduler, one exact
|
||||||
caravan transfer service, chunked persistence, and a 20-caravan structural
|
caravan transfer service, a production facade for one inbound Jajce route, a
|
||||||
benchmark. It does not yet run that regional service as the production
|
combined local/regional save manifest, chunked persistence, and a 20-caravan
|
||||||
`SimulationManager` facade or provide a complete market, settlement-growth, or
|
structural benchmark. It does not yet provide a complete market,
|
||||||
kingdom gameplay loop. That distinction is important when extending the game.
|
settlement-growth, or kingdom gameplay loop. That distinction is important
|
||||||
|
when extending the game.
|
||||||
|
|
||||||
## Start here
|
## Start here
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ kingdom gameplay loop. That distinction is important when extending the game.
|
|||||||
| Add caravans, routes, scheduled work, or regional saves | [Regional simulation and persistence](FEATURE_REGIONAL_SIMULATION_AND_PERSISTENCE.md) | `simulation/regional/`, `simulation/state/*StateRecord.gd` |
|
| Add caravans, routes, scheduled work, or regional saves | [Regional simulation and persistence](FEATURE_REGIONAL_SIMULATION_AND_PERSISTENCE.md) | `simulation/regional/`, `simulation/state/*StateRecord.gd` |
|
||||||
| Add an enemy or animal using an existing behavior | [Entity families](FEATURE_ENTITIES_COMBAT_ANIMALS.md) | `CombatantFactory.gd`, `AnimalFactory.gd`, shared visual roots |
|
| Add an enemy or animal using an existing behavior | [Entity families](FEATURE_ENTITIES_COMBAT_ANIMALS.md) | `CombatantFactory.gd`, `AnimalFactory.gd`, shared visual roots |
|
||||||
| Change rendering or support weaker PCs | [Presentation and performance](FEATURE_PRESENTATION_PERFORMANCE.md) | `world/jajce/jajce_world.gd`, `world/presentation/` |
|
| Change rendering or support weaker PCs | [Presentation and performance](FEATURE_PRESENTATION_PERFORMANCE.md) | `world/jajce/jajce_world.gd`, `world/presentation/` |
|
||||||
|
| Author humanoid animal characters or combine clothing and body shapes | [Modular character kit](../assets/characters/README.md) | `tools/CharacterWorkshop.tscn`, `CharacterAppearanceProfile.gd`, `tools/art/build_character_kit.py` |
|
||||||
| Change a saved field or migration | [Save schema and migrations](FEATURE_SAVE_SCHEMA_AND_MIGRATIONS.md) | `SimulationStateRecord.gd`, nested state records, `SaveSlotStore.gd` |
|
| Change a saved field or migration | [Save schema and migrations](FEATURE_SAVE_SCHEMA_AND_MIGRATIONS.md) | `SimulationStateRecord.gd`, nested state records, `SaveSlotStore.gd` |
|
||||||
| Add a regression, benchmark, or run the gate | [Testing and benchmarks](FEATURE_TESTING_AND_BENCHMARKS.md) | `tests/`, `tools/`, `docs/benchmarks/` |
|
| Add a regression, benchmark, or run the gate | [Testing and benchmarks](FEATURE_TESTING_AND_BENCHMARKS.md) | `tests/`, `tools/`, `docs/benchmarks/` |
|
||||||
|
|
||||||
@@ -53,9 +55,9 @@ kingdom gameplay loop. That distinction is important when extending the game.
|
|||||||
| Situations/quests | Definition-driven bounded situations, derived progress, journal metadata, commitments and idempotent outcomes | The initial Jajce slice is bounded; it is not a universal objective scripting language |
|
| Situations/quests | Definition-driven bounded situations, derived progress, journal metadata, commitments and idempotent outcomes | The initial Jajce slice is bounded; it is not a universal objective scripting language |
|
||||||
| Dialogue | Deterministic semantic `ConversationService`, authored intent/template resources, Dialogue Manager v3 presenter | Dialogue Manager renders planned turns; it is not an authority or save format |
|
| Dialogue | Deterministic semantic `ConversationService`, authored intent/template resources, Dialogue Manager v3 presenter | Dialogue Manager renders planned turns; it is not an authority or save format |
|
||||||
| Entities | Shared definition → instance/state → presentation contracts for resources, animals, enemies, items, storage | New behavior profiles and genuinely new mechanics still need typed code and tests |
|
| Entities | Shared definition → instance/state → presentation contracts for resources, animals, enemies, items, storage | New behavior profiles and genuinely new mechanics still need typed code and tests |
|
||||||
| Regional kernel | Primitive records, due-job heap, keyed random, analytical updates, caravan proof, chunk/file store | Regional service and 20-caravan trade are currently integration fixtures, not the live Jajce loop |
|
| Regional kernel | Primitive records, due-job heap, keyed random, analytical updates, caravan service, production facade, chunk/file store | One authored inbound route is live; the 20-caravan economy remains a structural fixture, not the Jajce market |
|
||||||
| Presentation | Active-world adapters, context registries, relevance cap of 40, navigation budget of 2/frame, High/Balanced/Low profiles | Hardware frame-time/VRAM evidence is still pending; structural benchmarks are not rendered claims |
|
| Presentation | Active-world adapters, context registries, relevance cap of 40, navigation budget of 2/frame, High/Balanced/Low profiles | Hardware frame-time/VRAM evidence is still pending; structural benchmarks are not rendered claims |
|
||||||
| Persistence | Local save schema v16 with migrations and scope preflight; regional chunk generations with recovery | No save menu/profile system; regional chunk store is not yet the local `SaveSlotStore` backend |
|
| Persistence | Combined manifest v1 containing unchanged local schema v16 plus the canonical regional facade; regional chunk generations with recovery | No save menu/profile system; regional chunk/history generations are not yet the `SaveSlotStore` backend |
|
||||||
|
|
||||||
## The authority pipeline
|
## The authority pipeline
|
||||||
|
|
||||||
@@ -106,9 +108,10 @@ relationship, or reward that the event claims happened.
|
|||||||
- `simulation/events/`, `knowledge/`, `relationships/`, `situations/`, and
|
- `simulation/events/`, `knowledge/`, `relationships/`, `situations/`, and
|
||||||
`dialogue/` — causal history and emergent narrative layers.
|
`dialogue/` — causal history and emergent narrative layers.
|
||||||
- `simulation/state/` — versioned primitive records and validation.
|
- `simulation/state/` — versioned primitive records and validation.
|
||||||
- `simulation/persistence/` — local quicksave safety.
|
- `simulation/persistence/` — combined quicksave manifest and atomic slot
|
||||||
- `simulation/regional/` — regional records, scheduler, caravan proof, chunk
|
replacement, plus regional chunk codecs and generations.
|
||||||
codec, and file generations.
|
- `simulation/regional/` — production facade, regional records, scheduler,
|
||||||
|
caravan transaction, retention planning, and scale contracts.
|
||||||
|
|
||||||
### Loaded world and presentation
|
### Loaded world and presentation
|
||||||
|
|
||||||
@@ -161,12 +164,11 @@ are used when an existing save is loaded.
|
|||||||
|
|
||||||
The following are deliberately **not** claimed as complete:
|
The following are deliberately **not** claimed as complete:
|
||||||
|
|
||||||
- wire `RegionalCaravanService` into the production regional facade and local
|
- integrate event retention/rollups/chunked history into one atomic,
|
||||||
save manifest;
|
lazily loaded long-horizon save;
|
||||||
- run 20 real caravan trades across five settlement economies with atomic
|
- run 20 real caravan trades across five settlement economies with atomic
|
||||||
cargo/stock ledgers;
|
cargo/stock ledgers;
|
||||||
- replace local unloaded travel freezes with authoritative route-time work;
|
- replace local unloaded travel freezes with authoritative route-time work;
|
||||||
- integrate event retention/rollups/chunked history into one long-horizon save;
|
|
||||||
- add settlement projects, cohort promotion/demotion, offices, diplomacy,
|
- add settlement projects, cohort promotion/demotion, offices, diplomacy,
|
||||||
armies, campaigns, and aggregate conflict;
|
armies, campaigns, and aggregate conflict;
|
||||||
- add visual relevance streaming to the live world view rather than only the
|
- add visual relevance streaming to the live world view rather than only the
|
||||||
|
|||||||
@@ -38,19 +38,92 @@ spawn is a next integration.
|
|||||||
`JajceWorld` owns reversible High, Balanced, and Low presentation profiles with
|
`JajceWorld` owns reversible High, Balanced, and Low presentation profiles with
|
||||||
instance-local mutable environment/grass resources and viewport-scale ownership.
|
instance-local mutable environment/grass resources and viewport-scale ownership.
|
||||||
|
|
||||||
| Profile | 3D scale | Shadow distance | Volumetric/glow | Grass |
|
| Profile | 3D scale / MSAA | Shadow distance | Volumetric/glow | Grass |
|
||||||
| --- | ---: | ---: | --- | --- |
|
| --- | ---: | ---: | --- | --- |
|
||||||
| High | authored | authored (currently 180 m) | authored when renderer supports it | authored density/interactors |
|
| High | authored / authored 4× | authored (currently 180 m) | authored when renderer supports it | authored density/interactors |
|
||||||
| Balanced (default) | 0.85 cap | 120 m cap | authored ordinary lookdev; unsupported volumetric disabled | reduced spacing/interactors/update rate |
|
| Balanced (default) | 0.85 cap / 2× cap | 120 m cap | authored ordinary lookdev; unsupported volumetric disabled | reduced spacing/interactors/update rate |
|
||||||
| Low | 0.70 cap | 80 m, orthogonal | volumetric/glow/adjustment disabled; ordinary fog retained | hidden/stopped, emitters off, shader interactors zero |
|
| Low | 0.70 cap / off | 80 m, orthogonal | volumetric/glow/adjustment disabled; ordinary fog retained | hidden/stopped, emitters off, shader interactors zero |
|
||||||
|
|
||||||
UI remains at native resolution. High/Low switches restore captured authored
|
UI remains at native resolution. High/Low switches restore captured authored
|
||||||
values; viewport scale restoration is guarded by the current quality owner so a
|
values; viewport scale restoration is guarded by the current quality owner so a
|
||||||
second world cannot clobber the active owner. Compatibility/mobile feature
|
second world cannot clobber the active owner. Compatibility/mobile feature
|
||||||
selection never forces unsupported volumetric fog on.
|
selection never forces unsupported volumetric fog on.
|
||||||
|
|
||||||
|
## Grass trails and bush contact
|
||||||
|
|
||||||
|
`GrassInteractionController` samples the existing `grass_interactors` group:
|
||||||
|
player, NPCs, hostile creatures and animals. The player keeps one slot; other
|
||||||
|
visible actors are ranked by camera distance within the loaded actor container.
|
||||||
|
High updates at 0.08 seconds with eight actors; Balanced uses 0.12 seconds and
|
||||||
|
four. Low stops updates and immediately releases both grass and bushes.
|
||||||
|
|
||||||
|
`FoliageTrailMap` maintains a disposable 128×128 RGBA float texture covering a
|
||||||
|
64 m square around the camera. Swept footprints part grass to either side and
|
||||||
|
lower its tips; the field recovers over eight seconds after contact. Sparse
|
||||||
|
history is clipped to the moving window. The 256 KiB texture is reused with
|
||||||
|
[`ImageTexture.update`](https://docs.godotengine.org/en/stable/classes/class_imagetexture.html#class-imagetexture-method-update),
|
||||||
|
at most 3.125 MiB/s of texture data at the High update rate. The vertex shader
|
||||||
|
adds one field lookup per grass vertex; there are no blade collision bodies,
|
||||||
|
per-blade scripts, render targets or added grass draw calls.
|
||||||
|
|
||||||
|
`StylizedBerryPatch` shares the field and contact positions between its leaf
|
||||||
|
and berry MultiMeshes. Both bend in world space around the planted bush root,
|
||||||
|
including under rotated/scaled parents, and spring back faster than grass.
|
||||||
|
The five village berry placeholders now use this same amount-aware presentation
|
||||||
|
as the existing riverbank bush. Contact does not change berries, resource
|
||||||
|
amounts, navigation, targets or collision geometry.
|
||||||
|
|
||||||
|
The controller clears history after `SimulationManager.state_restored`, on
|
||||||
|
quality changes and on world disposal. Missing/recreated actors, teleports over
|
||||||
|
3 m between samples and update gaps over 0.5 seconds start a new footprint rather
|
||||||
|
than a connecting trail. Actor foot height gates deformation so bridge traffic
|
||||||
|
does not flatten grass below. Trails are visual only and never enter saves.
|
||||||
|
The exported actor, primary actor, foliage and simulation paths can be rebound
|
||||||
|
when placing the controller in another loaded scene container.
|
||||||
|
|
||||||
|
`tests/foliage_interaction_test.gd` checks sweep direction, recovery, teleport
|
||||||
|
rejection, camera movement, world isolation, restore/quality reset, the primary
|
||||||
|
actor slot and shared leaf/fruit bindings. Run `tools/capture_foliage.gd` with
|
||||||
|
the native renderer for before/contact/trail/recovery images and controller
|
||||||
|
timing in `docs/baselines/foliage_*`. That capture freezes simulation and wind, and moves
|
||||||
|
the player presentation through the actual meadow, checking the state checksum
|
||||||
|
is unchanged. CPU update timing is not a GPU or weak-PC frame-rate guarantee.
|
||||||
|
Native Metal and Compatibility both render the effect; existing particle-shader
|
||||||
|
shutdown warnings (and four Compatibility texture leaks) also reproduce in the
|
||||||
|
untouched pre-art baseline. The headless quality gate retains its exact allowlist.
|
||||||
|
|
||||||
## Runtime hot paths already bounded
|
## Runtime hot paths already bounded
|
||||||
|
|
||||||
|
The woodland storybook art pass adds portable Blender animal meshes and an
|
||||||
|
opaque 35-triangle, seven-blade grass tuft. The existing camera-local grass
|
||||||
|
field and quality controls own its instance budget; four loaded path strips
|
||||||
|
provide disposable grass-clearance data after material isolation. Animal
|
||||||
|
appearance is derived from stable NPC IDs, shares mesh resources, and retains
|
||||||
|
profession tint, velocity-driven motion, inventory cues and death through the
|
||||||
|
existing presentation controllers. No saved state or navigation changes.
|
||||||
|
|
||||||
|
Characters and buildings use soft cel bands; tree crowns retain the shared
|
||||||
|
breeze with painted color patches. A painted cloud panorama, cooler
|
||||||
|
ambient fill and warm sunlight follow the simulation day/night clock. The
|
||||||
|
cloud shader adds no volumetric pass, outline pass or screen-space postprocess.
|
||||||
|
See [the asset contract and rebuild commands](../assets/storybook/README.md).
|
||||||
|
|
||||||
|
The current humanoid cast uses the [modular woodland character kit](../assets/characters/README.md):
|
||||||
|
31 Blender parts, five animal heads, six authored examples and independently
|
||||||
|
configurable proportions, outfits and accessories. `CharacterAppearanceProfile`
|
||||||
|
resources are cosmetic. Meshes are shared; `Stout` morph weights and palette
|
||||||
|
materials are per actor. The reference traveler has 20,070 base triangles across
|
||||||
|
10 opaque surfaces; Godot imports LODs. The standalone
|
||||||
|
`tools/CharacterWorkshop.tscn` previews and saves profiles. Existing motion,
|
||||||
|
profession/inventory/death cues, and shader grass interaction remain owned by
|
||||||
|
their existing controllers. The appearance scenario verifies imported parts,
|
||||||
|
independent shape weights, stable identity, extreme proportions and cue clearance.
|
||||||
|
|
||||||
|
`docs/baselines/storybook_*.png` records the native Metal review. The accompanying
|
||||||
|
`storybook_render_metrics.json` records a bounded local 1600×900 static-camera
|
||||||
|
sample on Apple M1 Max with six NPCs and High/Balanced/Low settings. Wall-frame
|
||||||
|
latency includes vsync and does not close the weak-PC evidence gap below.
|
||||||
|
|
||||||
- Player interaction and villager inspection probes are throttled/event-driven;
|
- Player interaction and villager inspection probes are throttled/event-driven;
|
||||||
they no longer rebuild all candidate data every rendered frame.
|
they no longer rebuild all candidate data every rendered frame.
|
||||||
- `LoadedResourceSpatialIndex` bounds finite-resource discovery.
|
- `LoadedResourceSpatialIndex` bounds finite-resource discovery.
|
||||||
@@ -99,3 +172,85 @@ ledger, not brittle cross-machine CI thresholds.
|
|||||||
|
|
||||||
See [the benchmark ledger](benchmarks/README.md), [regional contract](REGIONAL_SIMULATION.md),
|
See [the benchmark ledger](benchmarks/README.md), [regional contract](REGIONAL_SIMULATION.md),
|
||||||
and [the build-in-public visual plan](BUILD_IN_PUBLIC_PLAN.md).
|
and [the build-in-public visual plan](BUILD_IN_PUBLIC_PLAN.md).
|
||||||
|
|
||||||
|
## Painterly environment pass — September 2026
|
||||||
|
|
||||||
|
The reference study highlighted flat cloud bands, primitive tree silhouettes,
|
||||||
|
sparse ground cover, opaque green water, and a sun arc offset from daytime
|
||||||
|
brightness. The current pass addresses those through shared presentation assets:
|
||||||
|
|
||||||
|
- The WorldEnvironment uses cool blue ambient fill, warm sunlight, restrained
|
||||||
|
glow and ordinary aerial fog. The solar arc now peaks at noon and the warm
|
||||||
|
transitions overlap dawn/dusk. An original painted cloud panorama supplies
|
||||||
|
layered cumulus shapes; a static sky shader remaps the artwork, blends its
|
||||||
|
seam/horizon, and tints it from the simulation clock. It adds no TIME-driven
|
||||||
|
cubemap updates or volumetric cloud pass. Generation provenance and the exact
|
||||||
|
prompt are in [the asset guide](../assets/painterly/README.md).
|
||||||
|
- Original Blender broadleaf crowns/trunks and layered conifers replace the
|
||||||
|
primitive foliage. Seventeen decorative edge trees form a woodland backdrop.
|
||||||
|
The four village tree placeholders now use `HarvestableTreePresentation`,
|
||||||
|
retaining their existing IDs, amounts, interaction points and resource count.
|
||||||
|
Full/low/depleted state and restored crowns still derive from real amounts.
|
||||||
|
- `PainterlyMeadow` creates at most 1,680 decorative flower clumps in four
|
||||||
|
MultiMesh batches, with cream daisies, pink cosmos, buttercups and lavender.
|
||||||
|
Local seeded drifts follow terrain and exclude houses, paths, work sites and
|
||||||
|
resource approach points. High/Balanced/Low expose at most 1,680/1,092/536
|
||||||
|
clumps, with shorter distance fading in cheaper profiles. Flowers share the
|
||||||
|
prevailing breeze; the existing grass and berry contact field remains intact.
|
||||||
|
Explicit white instance colors preserve the painted GLB palette when custom
|
||||||
|
instance data is enabled in Compatibility; native review caught and verified
|
||||||
|
that renderer-specific regression.
|
||||||
|
Flowers have no collision, harvesting, actor-contact deformation or saved
|
||||||
|
state. See [mesh sources and budgets](../assets/meadow/README.md).
|
||||||
|
- The blue/turquoise river follows actual centerline terrain heights, keeps
|
||||||
|
level cross-sections, and clips dry edges at the shoreline. The pool and its
|
||||||
|
outlet meet without overlapping coplanar transparent sheets. Broken foam,
|
||||||
|
subtle flowing strokes and painted reflection patches replace the hard ring
|
||||||
|
and featureless surface. These reflections are stylized procedural patches
|
||||||
|
plus ordinary sky specular response, not exact reflected scene geometry,
|
||||||
|
screen-space reflections, refraction or depth sampling. Water obeys scene
|
||||||
|
lighting at night and adds no per-frame scripts.
|
||||||
|
- MSAA follows the existing viewport ownership rules: High preserves authored
|
||||||
|
4×, Balanced caps at 2×, and Low disables it. Closing an inactive world cannot
|
||||||
|
overwrite the active world's antialiasing or an external viewport override.
|
||||||
|
|
||||||
|
The editable Blender sources live under `art/painterly/` and `art/meadow/`,
|
||||||
|
protected by `.gdignore`; runtime consumes portable GLBs. The broadleaf is
|
||||||
|
5,350 triangles / five surfaces and the conifer is 1,580 / two. All flower
|
||||||
|
clumps together are capped at 362,880 triangles / four surfaces before
|
||||||
|
quality and distance reduction. No scene collision, navigation bake, terrain
|
||||||
|
heightmap, simulation rule or save schema was changed.
|
||||||
|
|
||||||
|
`tools/capture_painterly_environment.gd -- --label=after` captures the running
|
||||||
|
main scene with paused simulation, fixed daytime presentation and hidden HUD,
|
||||||
|
including the normal gameplay camera, valley, lower village view and night.
|
||||||
|
The valley/near-village `painterly_before_*` and `painterly_after_*`
|
||||||
|
images are matched cameras; `after_gameplay` fixes the normal rig at its default
|
||||||
|
yaw/zoom with mouse input disabled, and `after_vista` is an additional review angle.
|
||||||
|
The tool also records static-camera profile samples in
|
||||||
|
`painterly_after_metrics.json`; wall-frame latency includes vsync and is not
|
||||||
|
isolated GPU time or weak-PC proof. Runtime camera defaults are unchanged.
|
||||||
|
|
||||||
|
Remaining visual work is primarily authored terrain/shoreline detail and the
|
||||||
|
blockout-like building/fortress forms. This pass improves atmosphere and
|
||||||
|
foliage without treating those assets as finished film-quality environments.
|
||||||
|
|
||||||
|
Validation for this pass: the full macOS Godot 4.7 gate passed, including all
|
||||||
|
headless scenarios, the Compatibility quality scenario, and 182 GUT tests /
|
||||||
|
2,279 assertions. Main-scene headless startup passed the gate's existing exact
|
||||||
|
diagnostic allowlist. Native Metal daytime/gameplay/valley/night images were
|
||||||
|
reviewed at 1600×900, with a separate native Compatibility capture confirming
|
||||||
|
colored petals after the instance-buffer fix. The presentation-quality scenario
|
||||||
|
also passed with native Compatibility, including white instance-color readback
|
||||||
|
(the headless dummy renderer cannot provide that readback).
|
||||||
|
The static M1 Max sample reports approximately 16.7 ms
|
||||||
|
median wall-frame time in all three profiles; see its JSON for p95, draw calls
|
||||||
|
and primitives. This includes vsync and does not establish GPU headroom.
|
||||||
|
|
||||||
|
Native renderer shutdown still reports the particle shader/material RID
|
||||||
|
retention previously documented in the storybook/foliage pass; the headless
|
||||||
|
allowlist was not expanded. Compatibility has different color/tonemapping and
|
||||||
|
its existing particle/texture shutdown diagnostics. Metal is the color
|
||||||
|
reference for the saved art-review images.
|
||||||
|
The native multi-world quality scenario additionally logged a transient macOS
|
||||||
|
OpenGL cubemap-unloadable diagnostic; the final main-scene captures did not.
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
The regional package is the scale foundation for one authority spanning
|
The regional package is the scale foundation for one authority spanning
|
||||||
settlements, routes, caravans, named people, aggregate cohorts, and polities.
|
settlements, routes, caravans, named people, aggregate cohorts, and polities.
|
||||||
It is intentionally additive: the current Jajce `SimulationManager` remains
|
The production `SimulationManager` now owns a bounded regional facade for one
|
||||||
the playable local facade while the regional contracts prove deterministic
|
inbound Jajce route, while the broader regional contracts continue to prove
|
||||||
unloaded work and conservation in isolation.
|
deterministic unloaded work and conservation before wider integration.
|
||||||
|
|
||||||
## Regional record model
|
## Regional record model
|
||||||
|
|
||||||
@@ -63,6 +63,12 @@ no partial mutation. `to_dictionary()`, `from_dictionary()`, and `checksum()`
|
|||||||
provide deterministic save/restore. The test runs always-loaded, never-loaded,
|
provide deterministic save/restore. The test runs always-loaded, never-loaded,
|
||||||
and load/unload presentation modes through the same authoritative service.
|
and load/unload presentation modes through the same authoritative service.
|
||||||
|
|
||||||
|
`RegionalSimulationFacade` is the first production consumer. It owns one
|
||||||
|
authored Travnik-to-Jajce route, advances before each committed local tick, and
|
||||||
|
exposes one bounded delivery command. The cargo is a separate catalogued
|
||||||
|
`regional_trade_goods` item with no local storage tag, so this proof does not
|
||||||
|
shadow or duplicate the pantry and woodpile economy.
|
||||||
|
|
||||||
## Chunked persistence
|
## Chunked persistence
|
||||||
|
|
||||||
`RegionalChunkedPersistence` captures the service into a manifest plus fixed
|
`RegionalChunkedPersistence` captures the service into a manifest plus fixed
|
||||||
@@ -89,11 +95,20 @@ primitive-tree bounds.
|
|||||||
|
|
||||||
## Current integration boundary
|
## Current integration boundary
|
||||||
|
|
||||||
The regional package is not yet the production authority for `main.tscn`:
|
`main.tscn` now advances the regional facade and `SaveSlotStore` writes a
|
||||||
|
combined `SimulationSaveManifest` containing the unchanged local v16 record
|
||||||
|
and canonical regional envelope. Both checksums plus world, seed, location,
|
||||||
|
and tick alignment are validated before either authority is restored. Raw
|
||||||
|
local v16 slots remain compatible and create an idle regional facade;
|
||||||
|
explicit non-Jajce adapter scopes keep writing local v16 until they have a
|
||||||
|
matching regional facade. Those scopes cannot dispatch a Jajce delivery, and
|
||||||
|
local-only saving fails closed if regional state is no longer reconstructibly
|
||||||
|
idle.
|
||||||
|
|
||||||
- Jajce local saves still use `SaveSlotStore` and `SimulationStateRecord` v16;
|
The remaining boundary is intentionally narrow:
|
||||||
- `RegionalCaravanService` is a complete isolated transfer proof, not the live
|
|
||||||
economy's 20-trade scheduler;
|
- the facade has one callable inbound route, not the live economy's 20-trade
|
||||||
|
market scheduler;
|
||||||
- event retention/rollups and regional chunk files are not yet one integrated
|
- event retention/rollups and regional chunk files are not yet one integrated
|
||||||
long-horizon save transaction;
|
long-horizon save transaction;
|
||||||
- loaded/unloaded local NPC travel still has the old active-visual boundary;
|
- loaded/unloaded local NPC travel still has the old active-visual boundary;
|
||||||
@@ -101,8 +116,8 @@ The regional package is not yet the production authority for `main.tscn`:
|
|||||||
aggregate conflict remain planned.
|
aggregate conflict remain planned.
|
||||||
|
|
||||||
Do not create one `SimulationManager` per settlement or caravan. The next
|
Do not create one `SimulationManager` per settlement or caravan. The next
|
||||||
production step is an explicit regional facade that delegates to these records
|
production step is to publish retained raw events, daily rollups, and chunked
|
||||||
and services, while the active adapter presents only a bounded relevant context.
|
regional history as one atomic, lazily loaded transaction behind the facade.
|
||||||
|
|
||||||
## Scale evidence
|
## Scale evidence
|
||||||
|
|
||||||
|
|||||||
@@ -64,11 +64,22 @@ catch corruption.
|
|||||||
|
|
||||||
## Local file safety
|
## Local file safety
|
||||||
|
|
||||||
`SaveSlotStore` validates the complete JSON before replacement, restricts slot
|
`SimulationSaveManifest` v1 combines the unchanged local v16 record and the
|
||||||
names and size, preserves a previous file, and can recover a backup after an
|
canonical `RegionalSimulationFacade` envelope. It stores an independent
|
||||||
interrupted replacement. `F5`/`F9` use the current quicksave path in `main.tscn`.
|
checksum for each authority and cross-validates world, seed, active location,
|
||||||
The player transform and presentation-only scene state are intentionally not
|
and processed tick. A rejected manifest cannot partially replace local or
|
||||||
saved.
|
regional state.
|
||||||
|
|
||||||
|
`SaveSlotStore` validates the complete manifest before replacement, restricts
|
||||||
|
slot names and size, preserves a previous file, and can recover a backup after
|
||||||
|
an interrupted replacement. It also accepts existing raw local v16 files and
|
||||||
|
bootstraps an idle regional facade at the restored seed and tick. Explicit
|
||||||
|
non-Jajce adapter scopes continue writing raw local v16 records until a
|
||||||
|
matching regional facade is implemented. This fallback is permitted only while
|
||||||
|
the regional facade equals its deterministic idle bootstrap; a local-only save
|
||||||
|
fails closed rather than discarding live regional state. `F5`/`F9` use the
|
||||||
|
current quicksave path in `main.tscn`. The player transform and presentation-only
|
||||||
|
scene state are intentionally not saved.
|
||||||
|
|
||||||
## Regional persistence boundary
|
## Regional persistence boundary
|
||||||
|
|
||||||
@@ -79,9 +90,10 @@ per-file checksums. The file store uses immutable generations, a current
|
|||||||
pointer, recovery scanning, and conservative pruning. Partial active-location
|
pointer, recovery scanning, and conservative pruning. Partial active-location
|
||||||
loads are metadata-only and cannot silently resurrect a removed location.
|
loads are metadata-only and cannot silently resurrect a removed location.
|
||||||
|
|
||||||
This regional codec is not yet the backend for local `SaveSlotStore`. Integrate
|
This regional chunk codec is not yet the backend for `SaveSlotStore`. The
|
||||||
it only after the production regional facade, event retention, and lazy context
|
production facade and combined quicksave manifest are complete; retained raw
|
||||||
loading have one atomic manifest contract.
|
events, rollups, chunk generations, and lazy context loading still need one
|
||||||
|
atomic long-horizon publication contract.
|
||||||
|
|
||||||
## How to change state safely
|
## How to change state safely
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ For a new system, add tests for:
|
|||||||
|
|
||||||
## Current evidence and gaps
|
## Current evidence and gaps
|
||||||
|
|
||||||
The current full suite passes 154/154 tests with 2,047 assertions. Baseline 03
|
The current full suite passes 180/180 tests with 2,258 assertions. Baseline 03
|
||||||
measures 600 full-fidelity data-only NPC/combatant records at about 90.6 ticks/s
|
measures 600 full-fidelity data-only NPC/combatant records at about 90.6 ticks/s
|
||||||
on the reviewed development host. The regional baseline proves the target
|
on the reviewed development host. The regional baseline proves the target
|
||||||
record counts and scheduler parity. Neither measures a complete regional
|
record counts and scheduler parity. Neither measures a complete regional
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# The Steward — Learning and Reusable-Systems Roadmap
|
# The Steward — Learning and Reusable-Systems Roadmap
|
||||||
|
|
||||||
> **Current implementation checkpoint (2026-08-13):** The roadmap below is a
|
> **Current implementation checkpoint (2026-08-23):** The roadmap below is a
|
||||||
> chronological learning record. The current code has completed the authored
|
> chronological learning record. The current code has completed the authored
|
||||||
> content/catalog, shared target/command, event/social, situation/dialogue,
|
> content/catalog, shared target/command, event/social, situation/dialogue,
|
||||||
> entity-family, context-scoping, and first regional record/caravan/chunk
|
> entity-family, context-scoping, and first regional record/caravan/chunk
|
||||||
> slices. For a source-based status matrix and the remaining integration work,
|
> slices, including one production facade and combined save manifest. For a
|
||||||
|
> source-based status matrix and the remaining integration work,
|
||||||
> start with [`DEVELOPER_INDEX.md`](DEVELOPER_INDEX.md). Do not infer current
|
> start with [`DEVELOPER_INDEX.md`](DEVELOPER_INDEX.md). Do not infer current
|
||||||
> behavior from an older milestone paragraph when the index or code disagrees.
|
> behavior from an older milestone paragraph when the index or code disagrees.
|
||||||
|
|
||||||
@@ -43,8 +44,8 @@ becomes knowledge, generated semantic dialogue, a journal projection, and an
|
|||||||
event-backed social commitment whose ordinary deposit outcome survives
|
event-backed social commitment whose ordinary deposit outcome survives
|
||||||
save/load. Definition packs now cover reusable items, resources, storage,
|
save/load. Definition packs now cover reusable items, resources, storage,
|
||||||
enemies, and animals. Regional records, deterministic scheduled work, bounded
|
enemies, and animals. Regional records, deterministic scheduled work, bounded
|
||||||
presentation relevance, and the first authoritative caravan transfer form the
|
presentation relevance, and the first authoritative caravan transfer now feed
|
||||||
next world-slice foundation. See
|
one production facade and combined local/regional save boundary. See
|
||||||
[ADR 0001](decisions/0001-simulation-authority-boundary.md),
|
[ADR 0001](decisions/0001-simulation-authority-boundary.md),
|
||||||
[ADR 0002](decisions/0002-emergent-world-content-and-narrative.md), and the
|
[ADR 0002](decisions/0002-emergent-world-content-and-narrative.md), and the
|
||||||
[regional simulation contract](REGIONAL_SIMULATION.md).
|
[regional simulation contract](REGIONAL_SIMULATION.md).
|
||||||
@@ -1107,6 +1108,12 @@ resolution, and personal request accept/decline.
|
|||||||
|
|
||||||
Recently completed:
|
Recently completed:
|
||||||
|
|
||||||
|
- `Regional Jajce Facade 13`: `SimulationManager` now advances one authored
|
||||||
|
Travnik-to-Jajce caravan route through the regional service, and an atomic
|
||||||
|
combined manifest preserves both local v16 and regional authority. Separate
|
||||||
|
regional trade goods prevent duplicate pantry/woodpile ownership; tamper,
|
||||||
|
legacy-slot, rollback, conservation, and deterministic continuation
|
||||||
|
regressions define the boundary before broader markets or chunked history.
|
||||||
- `Jajce Villager Field Note 12`: a separate player-facing note selects the
|
- `Jajce Villager Field Note 12`: a separate player-facing note selects the
|
||||||
nearest loaded living villager and shows live name, action, task state, exact
|
nearest loaded living villager and shows live name, action, task state, exact
|
||||||
target, carried inventory, and a currently matching decision reason. It
|
target, carried inventory, and a currently matching decision reason. It
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# Regional simulation contract
|
# Regional simulation contract
|
||||||
|
|
||||||
> **Implementation status (2026-08-13):** The record, scheduler, caravan,
|
> **Implementation status (2026-08-23):** The record, scheduler, caravan,
|
||||||
> chunk, file-generation, relevance, and navigation-budget contracts are
|
> chunk, file-generation, relevance, and navigation-budget contracts are
|
||||||
> implemented and tested. They are still an additive regional package; the
|
> implemented and tested. `SimulationManager` now owns a production regional
|
||||||
> playable Jajce `SimulationManager` and `SaveSlotStore` remain the local
|
> facade for one inbound Jajce route, and `SaveSlotStore` persists local and
|
||||||
> production authority until the integrations listed at the end land.
|
> regional authority in one checked manifest. The scale economy and chunked
|
||||||
|
> long-horizon history remain separate integrations.
|
||||||
|
|
||||||
The regional layer extends the Jajce simulation without multiplying scene-tree
|
The regional layer extends the Jajce simulation without multiplying scene-tree
|
||||||
managers. One authority owns every loaded and unloaded location, settlement,
|
managers. One authority owns every loaded and unloaded location, settlement,
|
||||||
@@ -38,12 +39,10 @@ validated transactionally before a replacement state is published. Canonical
|
|||||||
serialization sorts record families and IDs, making checksums independent of
|
serialization sorts record families and IDs, making checksums independent of
|
||||||
construction order.
|
construction order.
|
||||||
|
|
||||||
The current local `SimulationStateRecord` remains the v16 Jajce save boundary.
|
The current local `SimulationStateRecord` remains the unchanged v16 Jajce
|
||||||
Regional state is deliberately carried in its own versioned service envelope
|
record. `SimulationSaveManifest` v1 combines that record with the canonical
|
||||||
until one playable route consumes it; this avoids placing speculative regional
|
`RegionalSimulationFacade` envelope and checksums both authorities. Regional
|
||||||
fields in every local save. The merge into the production save manifest must
|
fields are not copied into local records or scene nodes.
|
||||||
retain this canonical regional payload rather than copying its fields into
|
|
||||||
scene nodes.
|
|
||||||
|
|
||||||
## Deterministic work
|
## Deterministic work
|
||||||
|
|
||||||
@@ -92,6 +91,30 @@ never-loaded, and load/unload executions must preserve cargo, headcount, facts,
|
|||||||
arrival tick, queue state, and checksum. Nearby visuals may interpolate route
|
arrival tick, queue state, and checksum. Nearby visuals may interpolate route
|
||||||
progress; they do not report elapsed travel completion.
|
progress; they do not report elapsed travel completion.
|
||||||
|
|
||||||
|
## Production facade and combined save
|
||||||
|
|
||||||
|
`RegionalSimulationFacade` is now owned by the production `SimulationManager`.
|
||||||
|
It bootstraps an authored Travnik-to-Jajce route and advances the same
|
||||||
|
`RegionalCaravanService` before each committed local tick. Its bounded public
|
||||||
|
command can depart one exact delivery; arrival remains scheduled simulation
|
||||||
|
work and does not depend on a loaded caravan visual.
|
||||||
|
|
||||||
|
The route transports `regional_trade_goods`, a catalogued item with no local
|
||||||
|
storage tag. Jajce starts with no regional stock and Travnik starts with four
|
||||||
|
units, so the facade cannot duplicate the playable pantry's food or the
|
||||||
|
woodpile's wood. Local and regional event-ID spaces remain deliberately
|
||||||
|
separate until an explicit cross-domain history contract exists.
|
||||||
|
|
||||||
|
`SimulationSaveManifest` stores exactly the local v16 record, its checksum, the
|
||||||
|
regional facade envelope, its checksum, and the manifest schema/version. It
|
||||||
|
rejects checksum tampering and mismatched world, seed, active location, or
|
||||||
|
tick. Combined restore is transactional across both authorities. Existing raw
|
||||||
|
local v16 quicksaves remain loadable and bootstrap an idle regional route at
|
||||||
|
the restored seed and tick. Explicit non-Jajce adapter scopes continue to use
|
||||||
|
their local v16 boundary until a matching regional facade exists. That fallback
|
||||||
|
is allowed only while the regional facade equals its deterministic idle
|
||||||
|
bootstrap; Jajce delivery commands are rejected outside the combined scope.
|
||||||
|
|
||||||
## Event retention and daily rollups
|
## Event retention and daily rollups
|
||||||
|
|
||||||
`WorldEventRetentionPlanner` is a pure, deterministic query over a complete
|
`WorldEventRetentionPlanner` is a pure, deterministic query over a complete
|
||||||
@@ -157,14 +180,12 @@ active-world and hardware captures.
|
|||||||
Build in this order, keeping each step behind conservation and continuation
|
Build in this order, keeping each step behind conservation and continuation
|
||||||
tests:
|
tests:
|
||||||
|
|
||||||
1. make the Jajce route proof callable from the production regional facade and
|
1. integrate the retention/rollup and chunk codecs as one atomic,
|
||||||
save manifest;
|
|
||||||
2. integrate the retention/rollup and chunk codecs as one atomic,
|
|
||||||
lazily loaded history transaction;
|
lazily loaded history transaction;
|
||||||
3. schedule 20 ordinary caravan trades across five settlement economies;
|
2. schedule 20 ordinary caravan trades across five settlement economies;
|
||||||
4. promote/demote presentation and population fidelity while pinning named and
|
3. promote/demote presentation and population fidelity while pinning named and
|
||||||
causally referenced people;
|
causally referenced people;
|
||||||
5. add data-defined settlement projects, tier promotion, offices, diplomacy,
|
4. add data-defined settlement projects, tier promotion, offices, diplomacy,
|
||||||
armies, campaigns, and aggregate conflict.
|
armies, campaigns, and aggregate conflict.
|
||||||
|
|
||||||
Context-owned world-target registries and the first shared player/NPC activity
|
Context-owned world-target registries and the first shared player/NPC activity
|
||||||
|
|||||||
@@ -451,12 +451,20 @@ replayed after restore.
|
|||||||
|
|
||||||
## Local quicksave boundary
|
## Local quicksave boundary
|
||||||
|
|
||||||
`SaveSlotStore` writes the existing versioned local JSON record to
|
`SaveSlotStore` writes `SimulationSaveManifest` v1 to
|
||||||
`user://saves/quicksave.json`. It validates the serialized record before
|
`user://saves/quicksave.json`. The manifest contains the unchanged local v16
|
||||||
replacement, limits file size, restricts slot names, preserves the previous
|
record and canonical regional facade envelope, with an independent checksum
|
||||||
file during replacement, and can recover that backup if replacement is
|
for each. Restore also requires world, seed, active location, and tick to agree
|
||||||
interrupted. Loading parses and validates the complete record before mutating
|
across both authorities, and rolls both back if local restoration fails.
|
||||||
the simulation.
|
|
||||||
|
The store validates the complete serialized manifest before replacement,
|
||||||
|
limits file size, restricts slot names, preserves the previous file during
|
||||||
|
replacement, and can recover that backup if replacement is interrupted. Raw
|
||||||
|
local v16 quicksaves remain loadable; they bootstrap an idle regional facade
|
||||||
|
at the restored seed and tick. Explicit non-Jajce adapter scopes continue
|
||||||
|
writing local v16 records until a matching regional facade exists. Local-only
|
||||||
|
saving is allowed only while regional state equals its deterministic idle
|
||||||
|
bootstrap; otherwise it fails closed rather than discarding authority.
|
||||||
|
|
||||||
In `main.tscn`, F5 saves and F9 loads this slot. Restoration rebuilds active NPC
|
In `main.tscn`, F5 saves and F9 loads this slot. Restoration rebuilds active NPC
|
||||||
visuals from authoritative state.
|
visuals from authoritative state.
|
||||||
@@ -476,8 +484,8 @@ This phase does not yet provide:
|
|||||||
- persistence of a currently open dialogue UI/turn. Semantic conversation
|
- persistence of a currently open dialogue UI/turn. Semantic conversation
|
||||||
history and commitment authority persist, while a balloon is regenerated
|
history and commitment authority persist, while a balloon is regenerated
|
||||||
after restore;
|
after restore;
|
||||||
- production integration of the regional chunked save manifest, lazy location
|
- production integration of regional retained/rolled-up chunk history, lazy
|
||||||
loading, or aggregate population promotion/persistence;
|
location loading, or aggregate population promotion/persistence;
|
||||||
- persistence for the player transform or presentation-only scene state.
|
- persistence for the player transform or presentation-only scene state.
|
||||||
|
|
||||||
Those features should build on this boundary rather than inventing parallel
|
Those features should build on this boundary rather than inventing parallel
|
||||||
|
|||||||
|
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"]
|
[ext_resource type="Script" path="res://player/player_visual.gd" id="1_visual"]
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="Material_tunic"]
|
|
||||||
albedo_color = Color(0.18, 0.46, 0.48, 1)
|
|
||||||
roughness = 0.84
|
|
||||||
|
|
||||||
[sub_resource type="CylinderMesh" id="Mesh_body"]
|
|
||||||
material = SubResource("Material_tunic")
|
|
||||||
top_radius = 0.28
|
|
||||||
bottom_radius = 0.42
|
|
||||||
height = 0.9
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="Material_skin"]
|
|
||||||
albedo_color = Color(0.84, 0.61, 0.39, 1)
|
|
||||||
roughness = 0.88
|
|
||||||
|
|
||||||
[sub_resource type="SphereMesh" id="Mesh_head"]
|
|
||||||
material = SubResource("Material_skin")
|
|
||||||
radius = 0.29
|
|
||||||
height = 0.56
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="Material_hair"]
|
|
||||||
albedo_color = Color(0.2, 0.105, 0.05, 1)
|
|
||||||
roughness = 0.92
|
|
||||||
|
|
||||||
[sub_resource type="CylinderMesh" id="Mesh_hair"]
|
|
||||||
material = SubResource("Material_hair")
|
|
||||||
top_radius = 0.24
|
|
||||||
bottom_radius = 0.29
|
|
||||||
height = 0.18
|
|
||||||
|
|
||||||
[sub_resource type="CylinderMesh" id="Mesh_arm"]
|
|
||||||
material = SubResource("Material_tunic")
|
|
||||||
top_radius = 0.095
|
|
||||||
bottom_radius = 0.11
|
|
||||||
height = 0.58
|
|
||||||
|
|
||||||
[sub_resource type="SphereMesh" id="Mesh_hand"]
|
|
||||||
material = SubResource("Material_skin")
|
|
||||||
radius = 0.12
|
|
||||||
height = 0.22
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="Material_pants"]
|
|
||||||
albedo_color = Color(0.15, 0.21, 0.24, 1)
|
|
||||||
roughness = 0.9
|
|
||||||
|
|
||||||
[sub_resource type="CapsuleMesh" id="Mesh_leg"]
|
|
||||||
material = SubResource("Material_pants")
|
|
||||||
radius = 0.115
|
|
||||||
height = 0.62
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="Material_boot"]
|
|
||||||
albedo_color = Color(0.13, 0.075, 0.04, 1)
|
|
||||||
roughness = 0.95
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="Mesh_foot"]
|
|
||||||
material = SubResource("Material_boot")
|
|
||||||
size = Vector3(0.23, 0.15, 0.36)
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="Material_accent"]
|
|
||||||
albedo_color = Color(0.86, 0.4, 0.18, 1)
|
|
||||||
roughness = 0.82
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="Mesh_scarf"]
|
|
||||||
material = SubResource("Material_accent")
|
|
||||||
size = Vector3(0.48, 0.1, 0.38)
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="Material_sword"]
|
[sub_resource type="StandardMaterial3D" id="Material_sword"]
|
||||||
albedo_color = Color(0.72, 0.74, 0.78, 1)
|
albedo_color = Color(0.72, 0.74, 0.78, 1)
|
||||||
roughness = 0.32
|
roughness = 0.32
|
||||||
@@ -85,67 +20,29 @@ top_radius = 0.045
|
|||||||
bottom_radius = 0.045
|
bottom_radius = 0.045
|
||||||
height = 0.16
|
height = 0.16
|
||||||
|
|
||||||
[sub_resource type="SphereMesh" id="Mesh_satchel"]
|
|
||||||
material = SubResource("Material_accent")
|
|
||||||
radius = 0.22
|
|
||||||
height = 0.34
|
|
||||||
|
|
||||||
[node name="PlayerVisual" type="Node3D"]
|
[node name="PlayerVisual" type="Node3D"]
|
||||||
script = ExtResource("1_visual")
|
script = ExtResource("1_visual")
|
||||||
|
|
||||||
[node name="Body" type="MeshInstance3D" parent="."]
|
[node name="Body" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(0, 1.05, 0)
|
position = Vector3(0, 1.05, 0)
|
||||||
mesh = SubResource("Mesh_body")
|
|
||||||
|
|
||||||
[node name="Scarf" type="MeshInstance3D" parent="."]
|
|
||||||
position = Vector3(0, 1.42, 0.02)
|
|
||||||
mesh = SubResource("Mesh_scarf")
|
|
||||||
|
|
||||||
[node name="Head" type="MeshInstance3D" parent="."]
|
[node name="Head" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(0, 1.68, 0)
|
position = Vector3(0, 1.68, 0)
|
||||||
mesh = SubResource("Mesh_head")
|
|
||||||
|
|
||||||
[node name="Hair" type="MeshInstance3D" parent="."]
|
[node name="Hair" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(0, 1.9, -0.015)
|
position = Vector3(0, 1.9, -0.015)
|
||||||
mesh = SubResource("Mesh_hair")
|
|
||||||
|
|
||||||
[node name="ArmLeft" type="MeshInstance3D" parent="."]
|
[node name="ArmLeft" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(-0.39, 1.08, 0)
|
position = Vector3(-0.39, 1.3, 0)
|
||||||
rotation = Vector3(0, 0, -0.139626)
|
|
||||||
mesh = SubResource("Mesh_arm")
|
|
||||||
|
|
||||||
[node name="Hand" type="MeshInstance3D" parent="ArmLeft"]
|
|
||||||
position = Vector3(0, -0.35, 0)
|
|
||||||
mesh = SubResource("Mesh_hand")
|
|
||||||
|
|
||||||
[node name="ArmRight" type="MeshInstance3D" parent="."]
|
[node name="ArmRight" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(0.39, 1.08, 0)
|
position = Vector3(0.39, 1.3, 0)
|
||||||
rotation = Vector3(0, 0, 0.139626)
|
|
||||||
mesh = SubResource("Mesh_arm")
|
|
||||||
|
|
||||||
[node name="Hand" type="MeshInstance3D" parent="ArmRight"]
|
|
||||||
position = Vector3(0, -0.35, 0)
|
|
||||||
mesh = SubResource("Mesh_hand")
|
|
||||||
|
|
||||||
[node name="LegLeft" type="MeshInstance3D" parent="."]
|
[node name="LegLeft" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(-0.18, 0.43, 0)
|
position = Vector3(-0.18, 0.61, 0)
|
||||||
mesh = SubResource("Mesh_leg")
|
|
||||||
|
|
||||||
[node name="Foot" type="MeshInstance3D" parent="LegLeft"]
|
|
||||||
position = Vector3(0, -0.28, 0.09)
|
|
||||||
mesh = SubResource("Mesh_foot")
|
|
||||||
|
|
||||||
[node name="LegRight" type="MeshInstance3D" parent="."]
|
[node name="LegRight" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(0.18, 0.43, 0)
|
position = Vector3(0.18, 0.61, 0)
|
||||||
mesh = SubResource("Mesh_leg")
|
|
||||||
|
|
||||||
[node name="Foot" type="MeshInstance3D" parent="LegRight"]
|
|
||||||
position = Vector3(0, -0.28, 0.09)
|
|
||||||
mesh = SubResource("Mesh_foot")
|
|
||||||
|
|
||||||
[node name="Satchel" type="MeshInstance3D" parent="."]
|
|
||||||
position = Vector3(-0.43, 0.87, -0.04)
|
|
||||||
mesh = SubResource("Mesh_satchel")
|
|
||||||
|
|
||||||
[node name="Sword" type="Node3D" parent="."]
|
[node name="Sword" type="Node3D" parent="."]
|
||||||
position = Vector3(0.52, 1.22, 0.12)
|
position = Vector3(0.52, 1.22, 0.12)
|
||||||
@@ -158,3 +55,6 @@ mesh = SubResource("Mesh_blade")
|
|||||||
[node name="Grip" type="MeshInstance3D" parent="Sword"]
|
[node name="Grip" type="MeshInstance3D" parent="Sword"]
|
||||||
position = Vector3(0, -0.05, 0)
|
position = Vector3(0, -0.05, 0)
|
||||||
mesh = SubResource("Mesh_grip")
|
mesh = SubResource("Mesh_grip")
|
||||||
|
|
||||||
|
[node name="Tail" type="MeshInstance3D" parent="."]
|
||||||
|
position = Vector3(0, 0.8, -0.22)
|
||||||
|
|||||||