File size: 6,126 Bytes
84e8204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# MonkeyStudio Architecture

## 1. The core problem: 100+ features on 4 GB of RAM

A phone with 4 GB total RAM gives an app roughly **1.2-1.5 GB** before the OS
starts killing it. Blender-style tools (sculpt grids, cloth solvers, hair
particles, rigging) are memory-hungry. The only way to offer many features
without OOM is to **never have them all resident at once**.

MonkeyStudio's answer is a lazy-load + reference-count + LRU-evict memory
layer, plus a fixed pool for GPU objects. Below is how each piece works.

## 2. Memory layer (`scripts/core/`)

### 2.1 `MemoryManager` (autoload)

- Owns a hard **budget** (`asset_budget_mb`, default 512 MB, auto-capped to
  1/8 of physical RAM).
- Assets are registered by **id -> path + estimated cost**, *not* loaded.
- `acquire(id)` loads on first use and increments a reference count.
- `release(id)` drops a reference. At zero references the asset becomes an
  **eviction candidate**.
- `_ensure_room(need)` evicts **least-recently-used, zero-reference** assets
  until the new asset fits. Assets with live references are never evicted, so
  eviction can never yank something out from under a running feature.
- Every second it polls `OS.get_memory_info()`; when the OS says < 128 MB is
  available it force-evicts to buy headroom.

### 2.2 `FeatureRegistry` (autoload)

- A **catalog** of every tool. Each entry is just metadata
  (`name`, `module_path`, `cost_mb`).
- `activate(id)` asks MemoryManager for the module script, instantiates it,
  and adds it to the `Features` host node.
- `deactivate(id)` destroys the module node and releases its memory. This is
  what makes "close the tool, free the RAM" automatic.

### 2.3 `MeshPool` (autoload)

- Recycles `ArrayMesh` objects. A released mesh has its surfaces cleared
  (freeing GPU buffers) but the object is kept for reuse, avoiding per-frame
  allocation spikes and fragmentation.
- Bounded by `max_pooled` so the pool itself can't leak.

### 2.4 `VertexBufferManager` (autoload)

- `estimate_cost_mb(vertex_count)` predicts RAM+VRAM before a mesh is built so
  MemoryManager can evict first.
- `build_surface()` / `commit()` do a **single** `add_surface_from_arrays()`
  upload. Mobile GPUs hate thousands of tiny buffer updates; sculpting strokes
  accumulate on the CPU and flush once.
- `recompute_normals()` restores correct lighting after edits.

### 2.5 Adaptive quality

`StudioBus` emits `fps_throttled` when FPS stays below 30. `StudioMain` reacts
by disabling shadows, then the simulation substep budget drops (see below).
This keeps the frame budget bounded on weak GPUs.

## 3. Feature modules (`scripts/modules/`)

Modules implement `_activate(host)` / `_deactivate()` and receive the scene
through the `StudioBus` signal hub (low coupling, safe lazy unload).

| Module | Features | Notes |
|---|---|---|
| `mesh_gen.gd` | 1-20 | Procedural cube/sphere/cylinder/plane/torus/ico + a code-generated monkey head |
| `sculpt.gd` | 21-40 | draw / grab / pinch / smooth / flatten, batched GPU flush |
| `rigging.gd` | 41-60 | Skeleton3D builder + inverse-distance auto-weighting + `Skin` bind |
| `simulation.gd` | 61-80 | Verlet mass-spring cloth & hair with wind + pinned constraints |
| `timeline.gd` | 81-100 | Engine-native `Animation` + `AnimationPlayer` keyframing |

### 3.1 Mesh generation

Pure functions returning `{verts, indices, uvs}`. No GPU allocation until
`spawn()` commits. Costs are predictable, so a 5000-vertex sphere is refused
before it is built if it would blow the budget.

### 3.2 Sculpting

A stroke is `begin -> apply* -> end`. All `apply` calls mutate CPU
`PackedVector3Array`s; `end` performs one upload. Brushes use a smoothstep
falloff. `raycast_screen()` prefers a physics ray against the mesh collider and
falls back to a bounding-sphere test when the mesh has no collider yet.

### 3.3 Rigging

`generate_default_armature()` builds a spine + limbs from the mesh bounds.
`bind_weights()` ranks bones by distance, keeps the nearest 4, normalizes, and
writes `ARRAY_BONES` + `ARRAY_WEIGHTS` (the standard 4-influence mobile
skinning path) plus a `Skin` resource. Result is a normal engine skinned mesh.

### 3.4 Cloth & hair

Fixed-timestep (`1/30 s`) Verlet integration with Jacobi constraint relaxation.
Sub-steps are capped at 4 and backlog is dropped, so a slow frame can never
spiral into a death march. Particles and springs live in `PackedArray`s (no
per-frame allocation). Cloth uploads a triangle grid; hair uploads ribbons.

### 3.5 Timeline

Wraps a real `Animation` resource and `AnimationPlayer`, so interpolation,
looping and playback are engine-native and cached. The module exposes
`play/pause/scrub/add_keyframe/add_bone_keyframe` for a touch UI.

## 4. UI (`scripts/studio/studio_ui.gd`)

Material-ish dark theme built in code (resolution-independent, scaled by
`UI_SCALE`). Layout: top bar (FPS + memory HUD), left tool rail, right
collapsible panels (Tools / Layers / Timeline), bottom playback + scrubber.
Touch targets are >= 48 px.

## 5. Camera (`scripts/studio/touch_camera.gd`)

- 1 finger: orbit (or paint when a sculpt brush is active)
- 2 fingers: pinch = dolly zoom, drag = pan
- 3 fingers: pan
- All targets are exponentially smoothed so jittery touch sampling still feels
  fluid. No allocation in the input hot path.

## 6. Testing

`tests/self_test.tscn` runs headless and verifies every subsystem:

```bash
godot --headless res://tests/self_test.tscn
```

It checks mesh generation, all sculpt brushes (vertices actually move), the
skinning channels, cloth falling under gravity, hair creation, and timeline
keyframing. Exit code 0 = pass.

## 7. What is production-ready vs. roadmap

Honest status:

- Memory layer, mesh generation, sculpting, rigging, simulation, timeline and
  UI shell are **implemented and tested** as described above.
- Production polish still to do: adaptivity grid (`DynamicLOD`), UV unwrap,
  boolean ops, baking, USD/OBJ/glTF import-export, undo stack, a broader brush
  set, and device-specific tuning. The architecture is designed so these plug
  in as new `FeatureRegistry` entries without touching the core.