diff --git a/.agents/design/README.md b/.agents/design/README.md new file mode 100644 index 0000000..5c4fc09 --- /dev/null +++ b/.agents/design/README.md @@ -0,0 +1,32 @@ +# Blog design — agent rules + +Design and content rules for agents writing tinqs/blog posts. + +## Voice + +- **Confident, shipped.** Every feature is presented as built, complete, and working. Never "we plan to," "we're working on," "next we'll." +- **No evolution narrative.** Don't tell the "first we tried X, then we fixed Y" story. Present the final design as if it was the plan from day one. +- **External audience.** Readers are game developers and technical audiences outside Tinqs. No internal project names, no team org charts, no "Ozan decided" or "Uygar built." + +## What to never mention + +- **Asset pack names or vendors.** Polyperfect, Low Poly Ultimate Pack, Quaternius, Kevin Iglesias, etc. Say "our animal models" or nothing at all. +- **Unity import details.** FBX source format, `.anim` files, `.meta` clip ranges, `isleborn/` paths. +- **Internal tooling specifics.** `migrate_animals.py` internals, Blender pipeline details, repo paths. +- **Things that failed or were removed.** Failed migrations, broken assets, animals we deleted, bugs we shipped then fixed. If you must mention a bug, frame it as a design insight learned during development — never "we shipped this broken." +- **Roadmaps, tiers, trade-offs, future plans.** The post describes what exists. No "Tier A/B/C," no "recommended build order," no "what's next." + +## Structure + +- **Title:** technical, specific, bold. "How We Made 1,000 Animals Animate Without a Single Skeleton" not "Crowd Animation Update." +- **Opening:** state the problem (what stock Godot can't do), state our solution, give the numbers. +- **Body:** architecture, shader code, data flow, VRAM math, benchmarks. Ground everything in numbers. +- **Closing:** where to get it, what it drives (Ariki), related posts. No roadmap. + +## Numbers rule + +Every claim about performance, VRAM, or scale must cite a measured number. "60 FPS at 1,000 agents on M1 Pro" not "great performance." "1.6 MB per type" not "tiny VRAM." + +## Code + +Shader code and architecture diagrams are encouraged — this is a technical blog. But keep code blocks focused on the key insight, not the whole file. diff --git a/gpu-skinned-herds.html b/gpu-skinned-herds.html index 6c99e2a..2449704 100644 --- a/gpu-skinned-herds.html +++ b/gpu-skinned-herds.html @@ -357,7 +357,7 @@ NORMAL = normalize((skin * vec4(NORMAL, 0.0)).xyz);
In Ariki, the sim tracks animal migration across a 12km archipelago. AnimalHerdRenderer.cs groups sim ViewerState.animals by type, feeds world positions and yaw rotations to skinned_herd.gd — the reusable per-type herd backend. The herd bakes the palette once at setup, then set_positions() updates transforms each sim tick. set_clip_for_state() switches the active clip block in the custom data when the sim FSM changes state. set_speed_scale() adjusts the per-instance playback rate to match ground speed — feet stay planted.
The sim owns all behavior — 30 data-driven animals with per-animal senses, diet, combat stats, and FSM states (graze, drink, sleep, hunt, flee, scavenge, die). The client just renders. This is the same code in single-player and multiplayer — the sim is the host.
-Bird flocks use the same system. BirdFlock.cs runs boid flocking on top of skinned_herd, sharing the palette with synchronized phases (airborne flapping in unison is intentional). 25 bird species migrated from the Low Poly Bird Ultimate Pack, each a single draw call.
Bird flocks use the same system. BirdFlock.cs runs boid flocking on top of skinned_herd, sharing the palette with synchronized phases (airborne flapping in unison is intentional). 25 bird species, each a single draw call.
Per-instance custom data means a walking Boar, a running Boar, an idle Boar, and an attacking Boar all share the same baked palette — they just point at different rows. The renderer groups by type, not by state. One palette, one draw call, any number of states.
The module had data-plane doctests from day one — round-trip pose get/set, dirty tracking, size clamping, AABB, column-major layout. All green. Then we put it on screen and two things were wrong.
@@ -369,7 +369,7 @@ NORMAL = normalize((skin * vec4(NORMAL, 0.0)).xyz);Engine version: 4.6.5.
No C# wrapper is generated — instantiate from GDScript via ClassDB.instantiate() and call the bound methods. The binding surface is small and stable. See ariki-game/scenes/animals/skinned_herd.gd for the reference backend.
The migrate_animals.py tool converts polyperfect FBX packs to game-ready GLBs — imports, cleans hierarchy, rebuilds named NLA clips from frame ranges, strips duplicate meshes, bakes into the flat assets/models/glbs/ directory. Each animal gets a catalog entry in animals_catalog.json with clip metadata, default state mapping, and an animSpeedRef for foot-sync.
The migrate_animals.py tool converts source FBX files to game-ready GLBs — imports, cleans hierarchy, rebuilds named NLA clips from frame ranges, strips duplicate meshes, bakes into the flat assets/models/glbs/ directory. Each animal gets a catalog entry in animals_catalog.json with clip metadata, default state mapping, and an animSpeedRef for foot-sync.
At runtime, AnimalHerdRenderer spawns one skinned_herd per animal type. The herd bakes the palette from the catalog GLB's clips. AnimalAnimationLogic maps sim FSM states to clip keywords (attack → "attack"/"bite", flee → "run"/"gallop", wander → "walk"). The renderer lerps positions between sim ticks for smooth motion and writes per-instance custom data each frame. Zero per-frame CPU on the animation path.
The bone-matrix palette technique is the same architecture used by Assassin's Creed Unity, Total War: Warhammer, and Hitman for their crowd systems. We're using the same core idea, in a Godot fork, with smaller VRAM — our low-poly animals keep textures tiny.
diff --git a/posts/gpu-skinned-herds.md b/posts/gpu-skinned-herds.md index 143f23a..4d35a67 100644 --- a/posts/gpu-skinned-herds.md +++ b/posts/gpu-skinned-herds.md @@ -125,7 +125,7 @@ In [Ariki](https://www.arikigame.com), the sim tracks animal migration across a The sim owns all behavior — 30 data-driven animals with per-animal senses, diet, combat stats, and FSM states (graze, drink, sleep, hunt, flee, scavenge, die). The client just renders. This is the same code in single-player and multiplayer — the sim is the host. -Bird flocks use the same system. `BirdFlock.cs` runs boid flocking on top of `skinned_herd`, sharing the palette with synchronized phases (airborne flapping in unison is intentional). 25 bird species migrated from the Low Poly Bird Ultimate Pack, each a single draw call. +Bird flocks use the same system. `BirdFlock.cs` runs boid flocking on top of `skinned_herd`, sharing the palette with synchronized phases (airborne flapping in unison is intentional). 25 bird species, each a single draw call. Per-instance custom data means a walking Boar, a running Boar, an idle Boar, and an attacking Boar all share the same baked palette — they just point at different rows. The renderer groups by type, not by state. One palette, one draw call, any number of states. @@ -149,7 +149,7 @@ No C# wrapper is generated — instantiate from GDScript via `ClassDB.instantiat ## The production pipeline -The `migrate_animals.py` tool converts polyperfect FBX packs to game-ready GLBs — imports, cleans hierarchy, rebuilds named NLA clips from frame ranges, strips duplicate meshes, bakes into the flat `assets/models/glbs/` directory. Each animal gets a catalog entry in `animals_catalog.json` with clip metadata, default state mapping, and an `animSpeedRef` for foot-sync. +The `migrate_animals.py` tool converts source FBX files to game-ready GLBs — imports, cleans hierarchy, rebuilds named NLA clips from frame ranges, strips duplicate meshes, bakes into the flat `assets/models/glbs/` directory. Each animal gets a catalog entry in `animals_catalog.json` with clip metadata, default state mapping, and an `animSpeedRef` for foot-sync. At runtime, `AnimalHerdRenderer` spawns one `skinned_herd` per animal type. The herd bakes the palette from the catalog GLB's clips. `AnimalAnimationLogic` maps sim FSM states to clip keywords (attack → "attack"/"bite", flee → "run"/"gallop", wander → "walk"). The renderer lerps positions between sim ticks for smooth motion and writes per-instance custom data each frame. Zero per-frame CPU on the animation path.