diff --git a/.gitignore b/.gitignore index 2802132..14173e0 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,7 @@ Thumbs.db # NOTE: evidence intentionally committed under reports/visual/v0/ is NOT ignored. # (*.png screenshots, *.mp4 / *.gif recordings, *.csv / *.md performance & reports) # The audio/binary-art ignores above (*.wav/*.mp3/*.ogg/*.fbx/*.glb/...) do not match them. + +# python bytecode +tools/blender/__pycache__/ +*.pyc diff --git a/docs/art/V0-ANIMATION-CORRECTION-BRIEF.md b/docs/art/V0-ANIMATION-CORRECTION-BRIEF.md new file mode 100644 index 0000000..b8c0879 --- /dev/null +++ b/docs/art/V0-ANIMATION-CORRECTION-BRIEF.md @@ -0,0 +1,6 @@ +# V0 Animation-Led Visual Correction — Brief +Combined, animation-led correction (Art PM option C) on the PR #6 environment. Make the service +read through MOTION, not static poses/meters/close-ups. This branch is a **Loops 1–3 checkpoint** +of a min-10 / target-12 phase; NOT shipped. Not M1, no backend, no production nav/AI. +Re-authored a deformation-ready rig (per the PM ruling) rather than rigging PR #6's pose-baked meshes. +See V0-ANIMATION-CORRECTION-REPORT.md for the checkpoint gate (Incomplete checkpoint / Continue). diff --git a/docs/art/V0-ANIMATION-CURRENT-STATE.md b/docs/art/V0-ANIMATION-CURRENT-STATE.md new file mode 100644 index 0000000..ce25da1 --- /dev/null +++ b/docs/art/V0-ANIMATION-CURRENT-STATE.md @@ -0,0 +1,11 @@ +# V0 Animation Correction — Current State (living handoff) +Re-confirm against git log before trusting. Branch fix/v0-animation-led-visual-correction from +2ef1bb1. DRAFT PR → fix/v0-visual-baseline-correction (unmerged). Blender 5.2 / Godot 4.7.1. +COMPLETE: rig pipeline PROVEN (skeleton/skin/anim → Godot playback deforming); 19-bone base + 3 +profiles; 7 clips; locomotion (walk+facing+grounding); animated healthy+kitchen integration (35 +rigs); GPU-counter perf; loops 1-3 archived. +PARTIAL/DEFERRED (loops 4-12): manual shoulder/hip/wrist weights + Sit corrective (~8-14 h); +AnimationTree blending; BoneAttachment3D props; LOD (11.8k tris/char); FOH+host motion +choreography; at-overview bottleneck-via-motion; full passes. Deformation & operational- +storytelling self-scored 3 (< the 4 bar). Independent critique queued (not run this session). +Latest Art PM decision: option C authorized; awaiting decision on the manual deformation pass. diff --git a/docs/art/V0-ANIMATION-DECISION-LOG.md b/docs/art/V0-ANIMATION-DECISION-LOG.md new file mode 100644 index 0000000..35d978b --- /dev/null +++ b/docs/art/V0-ANIMATION-DECISION-LOG.md @@ -0,0 +1,9 @@ +# V0 Animation Correction — Decision Log (2026-07-30) +| # | Decision | State | Rationale | +|---|---|---|---| +| AD1 | Re-author deformation-ready rig (not rig PR#6 pose-baked meshes) | Locked (PM) | pose-baked meshes have no skin/joints; would create weak shoulders/hips | +| AD2 | Procedural rig: continuous mesh + subsurf-pre-skin + ARMATURE_AUTO | Provisional | best headless deformation; clean joints still need manual paint | +| AD3 | In-place clips + scripted path movement (no root motion, no nav) | Locked (V0) | keeps mock/visual boundary; readable choreography | +| AD4 | Stop at 3 genuine archived loops this session | Locked | honest scope; PM authorized 3-loop checkpoint; no faked loops | +| AD5 | Perf via RenderingServer counters, not 1/process_delta | Locked | fixes PR#6 review E1; GPU-measured-time unavailable offscreen (flagged) | +| AD6 | Manual deformation pass (~8-14 h) required before loops 4-12 | Proposed | clean shoulders/hips/wrists unreachable procedurally; gates downstream | diff --git a/docs/art/V0-ANIMATION-ITERATION-LOG.md b/docs/art/V0-ANIMATION-ITERATION-LOG.md new file mode 100644 index 0000000..579aed0 --- /dev/null +++ b/docs/art/V0-ANIMATION-ITERATION-LOG.md @@ -0,0 +1,8 @@ +# V0 Animation Iteration Log +| Loop | Hypothesis | Outcome | Decision | Commit | +|---|---|---|---|---| +| 01 base | subsurf+auto-weight continuous mesh deforms > boxes | 19-bone skinned base, 3 profiles, Idle; skeleton/skin PROVEN | Accept; shoulders/hips need manual | iter 01 | +| 02 locomotion | walk+path-follow reads at mgmt camera | walks, faces, grounded; mild foot slide, no blend | Accept | iter 02 | +| 03 task language | task clips + animated actors read as service | populated animated healthy+kitchen; 35 rigs; storytelling@overview unproven | Accept; stop at 3 (no budget for L4) | iter 03 | +Evidence: reports/visual/v0-animation-correction/iteration-01..03/ (hypothesis/critique/scores/ +decision + screenshots + recordings). 3 of min 10; target 12 NOT complete. No evidence reconstructed. diff --git a/docs/art/V0-ANIMATION-NAMING-STANDARD.md b/docs/art/V0-ANIMATION-NAMING-STANDARD.md new file mode 100644 index 0000000..c4aceb8 --- /dev/null +++ b/docs/art/V0-ANIMATION-NAMING-STANDARD.md @@ -0,0 +1,5 @@ +# V0 Animation Naming Standard +Clip names (glTF actions, stable): Idle, Walk, WalkStop, Carry, Cook, Sit, Wipe. +Planned (loops 4-12): TakeOrder, Serve, Plate, Grill, WipeReset, QueueWait, Impatient, SitDown, +StandUp, Greet, Escort. Bones: lowercase role_side (upperarm_L). One shared skeleton across all +roles/variants; roles differ by material + prop + clip, not by skeleton. diff --git a/docs/art/V0-ANIMATION-NEXT-ACTION.md b/docs/art/V0-ANIMATION-NEXT-ACTION.md new file mode 100644 index 0000000..cffbba9 --- /dev/null +++ b/docs/art/V0-ANIMATION-NEXT-ACTION.md @@ -0,0 +1,9 @@ +# V0 Animation Correction — Next Authorized Action (living handoff) +Re-confirm the repository's actual state before trusting this document. +ONE next action: review this DRAFT Loops-1-3 checkpoint PR, and approve the ~8-14 h MANUAL +Blender deformation pass (weight-paint shoulders/hips/wrists + shoulder & Sit corrective shapes + +a decimated LOD1) as the start of Loop 4 — because clean deformation gates loops 4-12 and cannot +be reached by procedural auto-weights. Then continue loops 4-12 (blending, bone-props, FOH/host +motion choreography, at-overview storytelling, full passes). +NOT authorized: merge, human test, backend/M1, production nav/AI, final art. +Reproduce: blender --background --python tools/blender/build_rig.py -- --out visual/RestaurantVisualSandbox/assets/generated --clips Idle,Walk,WalkStop,Carry,Cook,Sit,Wipe ; then open res://scenes/diagnostics/AnimatedService.tscn. diff --git a/docs/art/V0-ANIMATION-PERFORMANCE-STANDARD.md b/docs/art/V0-ANIMATION-PERFORMANCE-STANDARD.md new file mode 100644 index 0000000..dc35b9c --- /dev/null +++ b/docs/art/V0-ANIMATION-PERFORMANCE-STANDARD.md @@ -0,0 +1,8 @@ +# V0 Animation Performance Standard +DO NOT report 1/get_process_delta_time() as GPU perf (the PR #6 review's E1). Use RenderingServer +counters + measured render time. Checkpoint (animated-performance.csv, M3 Max, 1920x1080): +35 skeletons/35 AnimationPlayers, 118 mesh instances, 475 draw calls, 541k primitives, ~291 MB +VRAM, CPU render ~0.25 ms. NOTE: viewport_get_measured_render_time_gpu returned 0.00 offscreen +(context limit) — a true GPU-ms number needs an interactive window / GPU timer query. Perf task: +LOD the 11.8k-tri characters (~541k prims is the main cost driver). Target 60 FPS on the M3 Max +(not min-spec). Draw calls & VRAM are already low. diff --git a/docs/art/V0-CHARACTER-RIG-STANDARD.md b/docs/art/V0-CHARACTER-RIG-STANDARD.md new file mode 100644 index 0000000..a7de854 --- /dev/null +++ b/docs/art/V0-CHARACTER-RIG-STANDARD.md @@ -0,0 +1,8 @@ +# V0 Character Rig Standard +Source: tools/blender/build_rig.py (procedural, deterministic). 1 m = 1 unit. +Mesh: continuous cylinder body + Subdivision-Surface level applied BEFORE skinning (smoother +auto-weights than disjoint boxes). Skeleton (19 bones): pelvis, spine, chest, neck, head; +clavicle/upperarm/lowerarm/hand ×L/R; upperleg/lowerleg/foot ×L/R. Skin via ARMATURE_AUTO. +Body profiles: slim/average/broad (torso/limb scale). ~11,800 tris/char (subsurf-1) — LOD needed. +KNOWN CEILING (needs manual weight painting, see report §I): shoulders, hips (Sit), wrists. +Export: GLB, export_skins=True, export_animations=True, export_animation_mode='ACTIONS'. diff --git a/docs/art/V0-PROP-SOCKET-STANDARD.md b/docs/art/V0-PROP-SOCKET-STANDARD.md new file mode 100644 index 0000000..3c4e873 --- /dev/null +++ b/docs/art/V0-PROP-SOCKET-STANDARD.md @@ -0,0 +1,5 @@ +# V0 Prop Socket Standard (checkpoint status) +Target sockets: hand_L, hand_R, both-hands (tray), head (toque). STATUS: props are currently +child-offset approximations (toque on head, tray in front), NOT bone-attached. Loop-4+ task: +add BoneAttachment3D to hand_R/hand_L/head so tray/plate/pad/cloth/tub/utensil/toque track the +clip. Avoid floating/embedded props, vertical plates, detached tools. diff --git a/docs/art/V0-SERVICE-CHOREOGRAPHY-STANDARD.md b/docs/art/V0-SERVICE-CHOREOGRAPHY-STANDARD.md new file mode 100644 index 0000000..6d3c235 --- /dev/null +++ b/docs/art/V0-SERVICE-CHOREOGRAPHY-STANDARD.md @@ -0,0 +1,7 @@ +# V0 Service Choreography Standard (checkpoint status) +Actors are placed from the frozen Mock snapshot (owns no gameplay truth): seated customers (Sit), +cooks at active stations (Cook; cluster at congested grill), servers (Walk in aisle + Carry/Idle +at pass), busser (Wipe/Walk), host (Idle). STATUS: healthy + kitchen staged (loop 3). DEFERRED +(loops 7-10): FOH "food piling + overloaded servers" in motion, host entrance-queue in motion, +congestion buildup, table turns, and proving the bottleneck reads at the default overview via +behaviour. Movement = scripted paths only (no production navigation). diff --git a/reports/visual/v0-animation-correction/V0-ANIMATION-CORRECTION-REPORT.md b/reports/visual/v0-animation-correction/V0-ANIMATION-CORRECTION-REPORT.md new file mode 100644 index 0000000..9b044ce --- /dev/null +++ b/reports/visual/v0-animation-correction/V0-ANIMATION-CORRECTION-REPORT.md @@ -0,0 +1,95 @@ +# V0 Animation-Led Visual Correction — Loops 1–3 Checkpoint Report + +> **CHECKPOINT, NOT A SHIPPED PHASE.** 3 of a minimum 10 loops complete; target 12 **not** +> complete. Human testing **not** authorized. **Do not merge.** Backend / M1 **not** authorized. +> Manual deformation refinement still required. +> +> ``` +> Verdict: Incomplete checkpoint +> Action: Continue animation phase +> ``` + +## A. Repository +- Base `2ef1bb1` (reviewed PR #6 head). Branch `fix/v0-animation-led-visual-correction`, + worktree `../Restaurant-AnimationCorrection`. Target: **DRAFT** PR → `fix/v0-visual-baseline-correction`. +- Commits: `iter 01 base` → `iter 02 locomotion` → `iter 03 task language + integration` (one per loop). +- PR #6, PR #4, and the M0 branches are untouched; nothing merged. + +## B. Tools +Blender **5.2.0 LTS** (`/Applications/Blender.app/…`) · Godot **4.7.1-stable** (`~/.local/opt/godot/…`). +Pipeline: `tools/blender/build_rig.py` → rigged GLB (`export_skins`, `export_animations`, +`export_animation_mode=ACTIONS`) → Godot Skeleton3D + AnimationPlayer. Reproduce: +`blender --background --python tools/blender/build_rig.py -- --out visual/RestaurantVisualSandbox/assets/generated --clips Idle,Walk,WalkStop,Carry,Cook,Sit,Wipe`. + +## C. What was proven (the review's Blocker resolved) +The PR #6 review's **Blocker** was `skins=0, animations=0` — no skeleton. **Resolved:** +`CHAR_Base_Average.glb` has `skins=1`, **19 joints**, `JOINTS_0 + WEIGHTS_0`, **7 clips** +(Idle/Walk/WalkStop/Carry/Cook/Sit/Wipe). Godot imports `Skeleton3D(19)` + `AnimationPlayer`; +**characters visibly deform** on playback (not rigid). 35 rigged actors animate simultaneously +in the restaurant. + +## D. Loops completed (genuinely archived — see `iteration-01..03/`) +| Loop | Result | Self-scored weak point | +|---|---|---| +| 1 base | 19-bone skeleton, subsurf-smoothed skin, 3 body profiles, Idle | Deformation **3** (shoulder crease) | +| 2 locomotion | Walk + WalkStop, path-follow + facing + grounding at mgmt camera | mild foot slide; no blend | +| 3 task language | Carry/Cook/Sit/Wipe; animated actors in healthy + kitchen | Operational-storytelling **3**, Sit hip crease | + +Each iteration folder has HYPOTHESIS/CRITIQUE/SCORES/DECISION + screenshots + (loops 2-3) recordings. +**No iteration evidence was reconstructed after the fact; no folders are empty.** + +## E. Character base +Continuous cylinder body + Subdivision-Surface (applied pre-skin) + `ARMATURE_AUTO` weights; +19 bones (pelvis/spine/chest/neck/head, clavicle/upperarm/lowerarm/hand ×2, upperleg/lowerleg/foot ×2). +3 body profiles (slim/average/broad). **~11,800 tris/char** (subsurf-1) — needs a LOD/decimated variant. + +## F. Animation library (7 clips) +Idle, Walk, WalkStop, Carry, Cook, Sit, Wipe — procedural keyframed, looped where appropriate. +Known: no AnimationTree blending yet (hard-switch); props child-offset, not bone-attached. + +## G. Scenario results (motion) +Healthy and kitchen-bottleneck are **populated with animated actors** and recorded +(`recordings/loop03-*.mp4`). They differ by animated staffing (cook cluster at the congested +grill, walker count). **Honest:** reading the *specific* bottleneck at the default overview +through behaviour alone is **not yet proven** — that is the loop 7–10 choreography work; FOH and +host motion tells are not yet staged. + +## H. Performance (honest; fixes the review's E1) +`performance/animated-performance.csv` — measured via `RenderingServer` (not `1/process_delta`): +**35 skeletons / 35 AnimationPlayers, 118 mesh instances, 475 draw calls, 541k primitives, +~291 MB VRAM, CPU render ~0.25 ms.** The `viewport_get_measured_render_time_gpu` returned **0.00** +in the offscreen capture context (a measurement-context limitation) — a true GPU-ms number needs +an interactive windowed session / GPU timer query. Draw-call & VRAM cost is low; the ~541k +primitives (11.8k-tri chars ×35) is the main increase → **LOD is the perf task**. + +## I. Deformation ceiling (procedural vs manual — the core honesty) +| Joint / case | Current defect | Solvable procedurally? | Needs manual work | +|---|---|---|---| +| Shoulder → torso | creases/pinches | partial | **manual weight paint + corrective shape** | +| Hips under Sit | compression crease | no | **manual hip weights + Sit corrective** | +| Wrists / prop grip | approximate, no fingers | partial (mitten) | **manual for clean grip + BoneAttachment3D** | +| Elbows / knees | segment volume loss | partial (more loops) | corrective shapes for ≥4 | +**Estimated manual Blender hours to reach rubric Deformation ≥4:** ~**8–14 h** (shoulders ~3, +hips/sit ~3, wrists/hands ~2, correctives ~2–4) + ~2 h LOD. **Recommended next-session workflow:** +open the generated base in Blender, weight-paint shoulders/hips/wrists, add shoulder + sit +corrective shape keys, build a decimated LOD1 (~2–3k tris), then re-export and re-run loops 4+. + +## J. Regression & isolation +PR #6 preserved; backend/`main`/M0 untouched; no M1; no production navigation (scripted paths +only); no authoritative gameplay (reuses the pure Mock snapshot); no restricted assets +(all original procedural). Confirmed by diff. + +## K. Gate +``` +Verdict: Incomplete checkpoint +Action: Continue animation phase +``` +Not Pass / Pass-with-notes. The authorized 12-loop phase is **not** shipped. This checkpoint +proves the rig pipeline and delivers 3 genuine loops; loops 4–12 (blending, bone-props, manual +deformation, LOD, FOH/host motion choreography, at-overview storytelling, full passes) remain. + +## L. Required Art PM decision +> **Approve the ~8–14 h manual Blender deformation pass (shoulders/hips/wrists + correctives + +> LOD) as the start of Loop 4, before continuing loops 4–12** — since clean deformation is the +> gate on everything downstream and cannot be reached by procedural auto-weights alone. Keep the +> DRAFT PR open/unmerged; human test still deferred. diff --git a/reports/visual/v0-animation-correction/iteration-01/CRITIQUE.md b/reports/visual/v0-animation-correction/iteration-01/CRITIQUE.md new file mode 100644 index 0000000..4ce9c19 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-01/CRITIQUE.md @@ -0,0 +1,27 @@ +# Loop 1 — Critique (lead self-review; independent critique queued per §21) + +## Confirmed working +- **Real skeleton + skin:** `CHAR_Base_Average.glb` has `skins=1`, **19 joints**, `JOINTS_0 + WEIGHTS_0` + attributes, `animations=[Idle]`. Godot imports a `Skeleton3D` (19 bones) + `AnimationPlayer` + playing `Idle` with visible deformation. **The PR #6 "no skeleton" blocker is resolved.** +- Three distinct body profiles (slim/average/broad) from one rig; ~1.8 m scale; smooth stylized + silhouette that clearly beats both capsules and the static boxes. + +## Observed deformation defects (honest) +1. **Shoulder → torso junction creases** — the clavicle/upperarm weights pinch where the arm + meets the chest (visible in `joint-closeup.png`). The weakest joint. **Auto-weight limit.** +2. **Elbows / knees are "segment" joints** — upper/lower capsules meet with slight volume loss + under bend; acceptable at idle, will show more under Walk (Loop 2). +3. **Hands are spheres, no fingers** — fine at management distance, but prop grip (tray/plate) + will be approximate (prop-hand contact = Loop 3 risk). +4. **Feet are simple blocks** — acceptable; toe articulation absent. +5. **Tri budget high: ~11,800 tris/char** (subsurf-1). ~25 characters ≈ 295k tris — OK on M3 Max + but wants a LOD / lower-subsurf pass before crowding the scene. +6. Hip/pelvis deformation untested until Sit (Loop 3) and Walk (Loop 2). + +## Procedural vs manual (deformation ceiling) +- **Solvable procedurally:** overall silhouette, body profiles, elbow/knee acceptable-for-early, + lower tri budget (decimate/LOD or subsurf-0 variant). +- **Needs manual weight painting / corrective shapes (next-session Blender work):** clean + **shoulders**, **hips/pelvis under sit**, **wrists** for prop contact. Auto-weights will not + reach rubric Deformation ≥4 at these joints on this topology. diff --git a/reports/visual/v0-animation-correction/iteration-01/DECISION.md b/reports/visual/v0-animation-correction/iteration-01/DECISION.md new file mode 100644 index 0000000..82c5447 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-01/DECISION.md @@ -0,0 +1,13 @@ +# Loop 1 — Decision + +**ACCEPT as the working base** and proceed to Loop 2 (locomotion). The base meets Loop 1 +acceptance (real skeleton/skin/weights, deforms without major collapse, correct scale, better +than PR #6). + +**Carried forward as required next-session manual work (not attempted procedurally):** +- Manual weight-paint the shoulders + hips; add corrective shape keys for shoulder and sit compression. +- Add a subsurf-0 / decimated LOD1 for crowd use (drop from ~11.8k toward ~2-3k tris). +- Better hands (at least a mitten/thumb split) for prop grip. + +**Estimated manual Blender hours to reach Deformation ≥4:** ~8-14 h (shoulders ~3, hips/sit ~3, +wrists/hands ~2, corrective shapes ~2-4), plus ~2 h LOD. Out of scope for this automated session. diff --git a/reports/visual/v0-animation-correction/iteration-01/HYPOTHESIS.md b/reports/visual/v0-animation-correction/iteration-01/HYPOTHESIS.md new file mode 100644 index 0000000..9419a18 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-01/HYPOTHESIS.md @@ -0,0 +1,18 @@ +# Loop 1 — Deformation-ready character base + +**Hypothesis:** A continuous body (overlapping tapered cylinders + a Subdivision-Surface level +applied *before* skinning) auto-weighted to a full 19-bone humanoid skeleton will deform +markedly better than the PR #6 static pose-baked boxes, and will import into Godot as a real +`Skeleton3D` + `AnimationPlayer` playing an Idle clip. + +**Target defect (from the PR #6 review):** characters had `skins=0, animations=0` — no skeleton, +no deformation. Loop 1 must produce actual skeleton + skin + weights + a playing clip. + +**Method:** `tools/blender/build_rig.py` — build mesh, apply subsurf, build skeleton, parent with +`ARMATURE_AUTO` weights, keyframe Idle, export GLB (`export_skins=True`, `export_animations=True`, +`export_animation_mode='ACTIONS'`). Three body profiles (slim/average/broad) from the same rig. + +**Acceptance (§15 Loop 1):** skeleton+skin+weights exist; deforms without major collapse; scale +matches the restaurant (~1.8 m); at least as appealing as the PR #6 static version. + +**Evidence:** `idle-lineup.png` (3 profiles), `joint-closeup.png` (Average, joint inspection). diff --git a/reports/visual/v0-animation-correction/iteration-01/SCORES.md b/reports/visual/v0-animation-correction/iteration-01/SCORES.md new file mode 100644 index 0000000..2614df8 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-01/SCORES.md @@ -0,0 +1,15 @@ +# Loop 1 — Rubric scores (lead self-assessment, 1-5; independent critique queued) + +Scoring only the dimensions Loop 1 addresses (the base in isolation, not the full scene): + +| Category | Score | Note | +|---|---|---| +| Role readability | n/a | roles come in Loop 3 (clothing/props) | +| Deformation | 3 | functional; shoulders crease, elbows/knees segment-y (needs manual weights for ≥4) | +| Character appeal (vs PR#6 static) | 4 | smooth mannequin, 3 profiles, clearly better than boxes | +| Technical integrity | 4 | real skeleton(19)+skin+weights+clip; imports+plays clean; deterministic build | +| Environment integration | n/a | Loop 3 | +| Performance (asset) | 3 | 11.8k tris/char is high; LOD/decimate needed | + +Base average (scored dims): ~3.5. **Deformation 3 < the required final bar of 4** — this is the +known ceiling; recorded, not hidden. Not a final-gate score (Loop 1 is a foundation). diff --git a/reports/visual/v0-animation-correction/iteration-01/idle-lineup.png b/reports/visual/v0-animation-correction/iteration-01/idle-lineup.png new file mode 100644 index 0000000..48612c2 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-01/idle-lineup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-01/joint-closeup.png b/reports/visual/v0-animation-correction/iteration-01/joint-closeup.png new file mode 100644 index 0000000..a84ccdb Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-01/joint-closeup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-02/CRITIQUE.md b/reports/visual/v0-animation-correction/iteration-02/CRITIQUE.md new file mode 100644 index 0000000..314b131 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-02/CRITIQUE.md @@ -0,0 +1,16 @@ +# Loop 2 — Critique (lead; independent queued) + +## Working +- Walk reads clearly as walking at the management camera; legs swing, arms counter-swing, torso bob. +- **Facing works** (character turns to face each path segment); **grounding works** (feet at y=0, + contact shadow present; no float/sink). Runtime path movement is smooth. Walk loops cleanly. +- WalkStop settle clip exists. + +## Defects (honest) +1. **Mild–moderate foot sliding** — node speed (1.4 m/s) is not stride-matched to the clip, so feet + skate somewhat. Acceptable at management distance; needs speed↔stride tuning or root-motion-aware + speed. Not "severe," but not locked. +2. **No blend between Idle↔Walk↔WalkStop** — clips hard-switch (no Godot AnimationTree yet). Start/stop + pops. AnimationTree blending is a later loop. +3. **Knee/hip deformation under swing is segment-y** (Loop-1 ceiling) — reads fine small, shows at close range. +4. Turns are instant (rotation snaps per segment) — fine for a demo; a turn clip/lerp would smooth it. diff --git a/reports/visual/v0-animation-correction/iteration-02/DECISION.md b/reports/visual/v0-animation-correction/iteration-02/DECISION.md new file mode 100644 index 0000000..fa5d0ed --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-02/DECISION.md @@ -0,0 +1,6 @@ +# Loop 2 — Decision +ACCEPT locomotion as functional; proceed to Loop 3 (task language + scene integration). +Carried forward (next-session): stride↔speed matching to kill foot slide (~1-2 h); a Godot +AnimationTree for Idle/Walk/Stop blending (~2-3 h); optional turn clip; hip/knee weight-paint +(folded into the Loop-1 manual deformation pass). PERF: recording ran at movie-maker cadence; +real GPU-timed scene perf is measured in Loop 3 integration. diff --git a/reports/visual/v0-animation-correction/iteration-02/HYPOTHESIS.md b/reports/visual/v0-animation-correction/iteration-02/HYPOTHESIS.md new file mode 100644 index 0000000..4619123 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-02/HYPOTHESIS.md @@ -0,0 +1,14 @@ +# Loop 2 — Locomotion and grounding + +**Hypothesis:** A 24-frame in-place Walk clip + a Godot path-follower (translate the node, +face the movement vector, feet at floor) will read as walking at the management camera without +severe foot sliding, and a WalkStop settle covers the start/stop transition. + +**Target defect:** Loop-1 base only had Idle; no movement language. + +**Method:** add `clip_walk` + `clip_walk_stop` to build_rig.py; `anim_demo.gd` walks +CHAR_Base_Average around a 4-waypoint loop at 1.4 m/s, `rotation.y = atan2(dir.x, dir.z)`, +`position.y = 0`. Walk animation `loop_mode = LOOP_LINEAR`. + +**Acceptance (§15 Loop 2):** no severe foot slide at the intended speed; no float/sink; movement +reads clearly at overview. **Evidence:** walk-still.png, recording loop02-locomotion.mp4, walk-frameA/B. diff --git a/reports/visual/v0-animation-correction/iteration-02/SCORES.md b/reports/visual/v0-animation-correction/iteration-02/SCORES.md new file mode 100644 index 0000000..e34bfb7 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-02/SCORES.md @@ -0,0 +1,9 @@ +# Loop 2 — Scores (1-5, lead self-assessment) +| Category | Score | Note | +|---|---|---| +| Action readability (walk) | 4 | clearly reads as walking at mgmt camera | +| Overview readability | 4 | movement legible at the default distance | +| Animation appeal | 3 | functional cycle; no blend, mild foot slide | +| Deformation (legs/hips in motion) | 3 | segment-y knees/hips (Loop-1 ceiling) | +| Technical integrity | 4 | clip imports, loops, path-follow + facing + grounding work | +Locomotion is functional; appeal/deformation held back by no-blend + auto-weight ceiling. diff --git a/reports/visual/v0-animation-correction/iteration-02/walk-frameA.png b/reports/visual/v0-animation-correction/iteration-02/walk-frameA.png new file mode 100644 index 0000000..d6042e0 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-02/walk-frameA.png differ diff --git a/reports/visual/v0-animation-correction/iteration-02/walk-frameB.png b/reports/visual/v0-animation-correction/iteration-02/walk-frameB.png new file mode 100644 index 0000000..e9ca097 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-02/walk-frameB.png differ diff --git a/reports/visual/v0-animation-correction/iteration-02/walk-still.png b/reports/visual/v0-animation-correction/iteration-02/walk-still.png new file mode 100644 index 0000000..63102b3 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-02/walk-still.png differ diff --git a/reports/visual/v0-animation-correction/iteration-03/CRITIQUE.md b/reports/visual/v0-animation-correction/iteration-03/CRITIQUE.md new file mode 100644 index 0000000..4e2756b --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-03/CRITIQUE.md @@ -0,0 +1,23 @@ +# Loop 3 — Critique (lead; independent queued) +## Working +- The restaurant is populated with ANIMATED rigged actors (not static boxes): cooks working the + line, servers at the pass + walking, customers seated (Sit), a busser, a host. Role reads by + colour + clip + position. Motion gives the room life (recordings). +- Healthy vs kitchen differ by animated staffing (cook cluster at the congested grill; walker count). +- 35 skeletons + 35 AnimationPlayers run at once: 475 draw calls, 541k prims, ~291 MB VRAM — cheap. +## Defects (honest) +1. **Sit hip compression creases** — the pelvis-down Sit pose creases at the hips (auto-weight + ceiling; needs manual hip weights/corrective shape). Visible at close range. +2. **Props are child-offset, not bone-attached** — toque/tray/clipboard approximate the hand/head, + don't track the clip precisely (need BoneAttachment3D). Prop-hand contact is approximate. +3. **No clip blending** (no AnimationTree) — actions hard-switch; walkers still have mild foot slide. +4. **Kitchen-vs-FOH-via-motion not yet fully staged** — this loop staged healthy + kitchen; the + FOH "food piling / servers overloaded in motion" and host-queue-in-motion tells are Loop 9/10 work. +5. **Overview still busy** — motion helps but reading the *specific* bottleneck at overview via + behaviour alone is not yet proven (needs the choreography loops 7-10 + blending). +6. **11.8k-tri chars × 35 = 413k char prims** — LOD needed before crowding further. +## Perf-method honesty (review E1) +Used RenderingServer measured render-time (not 1/process_delta). GPU-measured-time returned 0.00 +in the offscreen capture context (measurement-context limit); trustworthy signals reported: +draw calls 475, prims 541k, VRAM 291 MB, CPU render ~0.25 ms, 35 skeletons/anim-players. A true +GPU-ms number needs an interactive windowed session or a GPU timer query. diff --git a/reports/visual/v0-animation-correction/iteration-03/DECISION.md b/reports/visual/v0-animation-correction/iteration-03/DECISION.md new file mode 100644 index 0000000..867f468 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-03/DECISION.md @@ -0,0 +1,6 @@ +# Loop 3 — Decision +ACCEPT as the Loop-3 checkpoint of the task language + first animated integration. This +completes the 3 authorized loops. Do NOT proceed to Loop 4 (no meaningful budget remains after +fully documenting Loops 1-3). Remaining for the animation phase (loops 4-12): AnimationTree +blending; BoneAttachment3D props; manual hip/shoulder/wrist weights + Sit corrective; LOD; the +FOH + host motion choreography; the full healthy/kitchen/FOH/host at-overview storytelling pass. diff --git a/reports/visual/v0-animation-correction/iteration-03/HYPOTHESIS.md b/reports/visual/v0-animation-correction/iteration-03/HYPOTHESIS.md new file mode 100644 index 0000000..ba613dd --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-03/HYPOTHESIS.md @@ -0,0 +1,12 @@ +# Loop 3 — Core task language + healthy/kitchen integration +**Hypothesis:** Carry/Cook/Sit/Wipe clips + placing rigged animated actors in the restaurant +(from a Mock snapshot, role-tinted, playing role clips) makes the service read through MOTION, +and healthy vs kitchen-bottleneck become distinguishable by animated staffing (cook density, +servers at the pass, walkers). +**Method:** add clips to build_rig.py; animated_service.gd builds CorrectedRestaurant + spawns +CHAR_Base_Average actors: seated customers (Sit), cooks (Cook, cluster at congested grill), +servers (Walk in aisle + Carry/Idle at pass), busser (Wipe/Walk), host (Idle); role tint + +toque/tray/clipboard props. Reuses the frozen MockService. +**Acceptance (§15 Loop 3):** a role's task is understandable from motion; healthy vs kitchen differ. +**Evidence:** healthy-animated.png, kitchen-animated.png, recordings loop03-{healthy,kitchen}-service.mp4, +kitchen-mid-frame.png, performance/animated-performance.csv. diff --git a/reports/visual/v0-animation-correction/iteration-03/SCORES.md b/reports/visual/v0-animation-correction/iteration-03/SCORES.md new file mode 100644 index 0000000..ff63c5e --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-03/SCORES.md @@ -0,0 +1,11 @@ +# Loop 3 — Scores (1-5, lead self-assessment) +| Category | Score | Note | +|---|---|---| +| Restaurant fantasy (alive) | 4 | motion makes it feel alive vs the static diorama | +| Role readability | 4 | colour + clip + position read; props approximate | +| Action readability | 3.5 | cook/sit/carry/wipe read; needs blending + bone-props | +| Operational storytelling (overview) | 3 | motion helps but specific bottleneck at overview not yet proven (loops 7-10) | +| Deformation (sit/hips) | 3 | hip crease on Sit (ceiling) | +| Environment integration | 4 | actors belong in the kit restaurant | +| Technical integrity | 4 | 35 rigs+players run; imports/plays clean; cheap | +Not final-gate scores. Operational-storytelling & deformation remain < 4 (the known gaps). diff --git a/reports/visual/v0-animation-correction/iteration-03/healthy-animated.png b/reports/visual/v0-animation-correction/iteration-03/healthy-animated.png new file mode 100644 index 0000000..9d99b58 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-03/healthy-animated.png differ diff --git a/reports/visual/v0-animation-correction/iteration-03/kitchen-animated.png b/reports/visual/v0-animation-correction/iteration-03/kitchen-animated.png new file mode 100644 index 0000000..578487f Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-03/kitchen-animated.png differ diff --git a/reports/visual/v0-animation-correction/iteration-03/kitchen-mid-frame.png b/reports/visual/v0-animation-correction/iteration-03/kitchen-mid-frame.png new file mode 100644 index 0000000..ee2fc8b Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-03/kitchen-mid-frame.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/COMPLETION.md b/reports/visual/v0-animation-correction/iteration-04/COMPLETION.md new file mode 100644 index 0000000..ba5a9a2 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-04/COMPLETION.md @@ -0,0 +1,32 @@ +# Loop 4 — Completion of required deliverables (gap-closure) + +The initial Loop-4 commit (da7bcf0) adopted the hybrid + met the deformation/style/Godot/repro/ +license gate, but on re-reading the full authorization several REQUIRED deliverables were missing. +Closed here honestly (not back-dated): + +## Standardized prop sockets (was: described only) — NOW DELIVERED +`prop_socket.gd` attaches a tray (plate+glass) to the **`hand_r`** bone via **BoneAttachment3D**; +it tracks the hand across clips. Sockets available: `hand_r`, `hand_l`, `Head` (UAL rig also has +full finger bones `index_01_l`… for future precise grip). Evidence: `prop-carry-walk.png`, +`prop-attach-closeup.png`. +- **Prop grip — honest re-score: 3.5/5.** The tray tracks the hand bone correctly (carry reads), + but the grip is APPROXIMATE (open hand, tray offset in the palm) — not a closed-finger grasp. + My earlier "prop grip 4" was unsubstantiated; corrected to 3.5 (a real socket, approximate grip). + +## Required evidence added +- **Blender neutral render** — `blender-neutral.png` (Workbench clay). +- **Skeleton view** — `blender-skeleton.png` (joint dots at every bone head/tail). +- **Weight visualization** — `blender-weights-thigh_l.png`: the UAL body coloured by the `thigh_l` + weights shows a CLEAN smooth falloff (red thigh → blue at hip/knee) — evidence the UAL weights + are professional (the reason deformation is clean). +- **Carry comparison** — `prop-carry-walk.png` (server walking with the tray on the hand socket). +- **Performance counters (real, windowed path)** — `hybrid-perf.csv`: 4 hybrid rigs (4 Skeleton3D) + = **362 draw calls, 187,016 primitives, 288.9 MB VRAM**. (The Loop-4 headless probe had returned + 0; this windowed capture is the correct measurement.) At full-scene density (~25 chars) primitives + scale toward ~400k → an **LOD on the ~15.5k-tri body is the perf task**, but draw calls/VRAM are low. + +## Net gate effect +No gate score drops below threshold: overall deformation 4.5, Godot 5, reproducibility 4, style 4, +license 5; prop grip re-scored 3.5 (still ≥3). **Loop 4 deliverables + evidence are now complete.** +Still a min-10 / target-12 phase; loops 5-12 remain. Recommended Loop-5 polish unchanged +(faces, midriff/sleeves, customer variant, skinned hair, LOD, closed-finger grip). diff --git a/reports/visual/v0-animation-correction/iteration-04/CRITIQUE.md b/reports/visual/v0-animation-correction/iteration-04/CRITIQUE.md new file mode 100644 index 0000000..8faa0b0 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-04/CRITIQUE.md @@ -0,0 +1,17 @@ +# Loop 4 — Critique (lead; independent queued) +## Working +- **Deformation (body): clean.** The UAL 65-bone rig sits/walks/reaches with natural shoulder/ + hip/knee/ankle deformation (proc-vs-hybrid-sit: hybrid = clean seated chef; procedural = crude crouch). +- **Skinned clothing deforms with the rig** — per-leg trousers bend at the knees when sitting + (hybrid-sit); the earlier boxy version was fixed (tapered top, real toque band+puff, split trousers). +- **Customized, not untouched Quaternius:** distinct Server (blue top + hair) vs Cook (white coat + + toque); skin tones, palette, eyes. Roles read at a glance (hybrid-role-lineup). +- **Godot: 65-bone Skeleton3D + 43 clips import & play; runtime in the restaurant works** (hybrid-runtime). +- Validator: all 4 GLBs skin=1/65 joints/43 clips/weighted clothing. +## Defects (honest — style is at the bar, not above it) +1. **Faces minimal** (eyes only; no nose/mouth) — heads still read semi-blank at close range. +2. **Midriff gap / stubby sleeves** — the top ends above the waist; sleeves are short cones. +3. **Hair/toque are rigid (bone-parented), not skinned** — fine for head, but hair doesn't sway. +4. **Tris ~15.5k/char** (UAL body + clothing) — heavier than procedural; **LOD needed** before crowding. +5. **Perf counters not captured** in the -s headless context (0 values); needs a windowed GPU-timed pass. +6. Body-profile scale is a uniform XY scale (safe) — true proportion changes would need re-skinning. diff --git a/reports/visual/v0-animation-correction/iteration-04/DECISION.md b/reports/visual/v0-animation-correction/iteration-04/DECISION.md new file mode 100644 index 0000000..2abb603 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-04/DECISION.md @@ -0,0 +1,15 @@ +# Loop 4 — Decision +**Adopt the hybrid foundation. Loop 4 gate: PASS-WITH-NOTES.** All hard thresholds met (deformation +4.5, Godot 5, reproducibility 4, style 4, license 5; no critical joint <3). The hybrid deforms cleanly +(decisive over procedural) and is customized to the restaurant (distinct server/cook, palette, toque, +hair, eyes) — not an untouched Quaternius asset. +**Do NOT auto-continue to Loop 5.** Style sits AT the bar, not above it; report back for the Art PM +before Loop 5. Recommended polish (next loop, ~6-12 h): face treatment (nose/mouth/expression), +close the midriff + refine sleeves + conform clothing, a hybrid CUSTOMER variant (casual clothing), +skinned hair, an LOD1 (drop ~15.5k→~3-4k tris), a windowed GPU-timed perf pass, and prop-socket +BoneAttachment3D for tray/plate. +## Inherited (Quaternius UAL, CC0) vs Custom (this project) +- INHERITED: 65-bone skeleton, body mesh topology + weights (clean deformation), 43 animation clips. +- CUSTOM: skin-tone recolor; role clothing (tapered top/coat, sleeves, apron, per-leg trousers) SKINNED + to the UAL armature; hair cap + chef toque (band+puff) + eyes bone-parented to head; body-profile + scale; restaurant palette; Godot presentation/lighting/camera; runtime placement; prop-socket approach. diff --git a/reports/visual/v0-animation-correction/iteration-04/HYPOTHESIS.md b/reports/visual/v0-animation-correction/iteration-04/HYPOTHESIS.md new file mode 100644 index 0000000..5e0dbcf --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-04/HYPOTHESIS.md @@ -0,0 +1,12 @@ +# Loop 4 — Hybrid foundation adoption + deformation gate +**Hypothesis:** Importing the Quaternius UAL CC0 rig (65-bone, 43 clips, clean pro deformation) +and adding CUSTOM restaurant clothing/hair/headwear skinned to the SAME armature yields a +character that (a) deforms cleanly (unlike the procedural base) AND (b) looks customized for +this game (not an untouched Quaternius asset), and re-exports with all clips intact. +**Method:** build_hybrid.py imports UAL, recolors the body to a skin tone, adds a tapered top + +sleeves + apron panel + per-leg trousers (SKINNED to the armature so they deform) + hair/toque + +eyes (bone-parented to the head), scales body profile, exports the CUSTOMIZED derivative GLB. +Server + Cook roles × body/skin/hair variants. Godot runtime in the restaurant. +**Gate:** overall deformation ≥4, Godot ≥4, reproducibility ≥4, style ≥4, license 5; no critical joint <3. +**Evidence:** hybrid-role-lineup, hybrid-sit, hybrid-walk, hybrid-reach, proc-vs-hybrid-sit, +hybrid-runtime.png; recordings/loop04-hybrid-runtime.mp4; VALIDATOR.txt. diff --git a/reports/visual/v0-animation-correction/iteration-04/SCORES.md b/reports/visual/v0-animation-correction/iteration-04/SCORES.md new file mode 100644 index 0000000..4645667 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-04/SCORES.md @@ -0,0 +1,11 @@ +# Loop 4 — Gate scores (1-5, lead self-assessment; independent critique queued) +| Category | Score | Gate | Pass | +|---|:--:|:--:|:--:| +| Overall deformation | 4.5 | ≥4 | YES | +| Godot reliability | 5 | ≥4 | YES | +| Pipeline reproducibility | 4 | ≥4 | YES (CC0 UAL download + script; deterministic) | +| Style compatibility | 4 | ≥4 | YES (at the bar; polish notes) | +| License confidence | 5 | =5 | YES (CC0 verified at source) | +Critical joints (all ≥3): shoulders 5, elbows 4, wrists 4, spine 5, neck 4, hips 5, sitting 4, +knees 5, ankles 4, foot grounding 4, clothing intersections 3.5 (minor). **No critical joint <3.** +**Gate: MET** — but style is AT the 4 threshold (not comfortably above); recorded honestly. diff --git a/reports/visual/v0-animation-correction/iteration-04/VALIDATOR.txt b/reports/visual/v0-animation-correction/iteration-04/VALIDATOR.txt new file mode 100644 index 0000000..626cec8 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-04/VALIDATOR.txt @@ -0,0 +1,7 @@ +=== VALIDATOR: each hybrid GLB (skin/joints/clips/meshes/tris) === +CHAR_Hybrid_Cook_Average.glb skin=1 joints=65 clips=43 meshes=11 tris=15499 weighted=True +CHAR_Hybrid_Cook_Broad.glb skin=1 joints=65 clips=43 meshes=11 tris=15499 weighted=True +CHAR_Hybrid_Server_Average.glb skin=1 joints=65 clips=43 meshes=10 tris=15423 weighted=True +CHAR_Hybrid_Server_Slim.glb skin=1 joints=65 clips=43 meshes=10 tris=15423 weighted=True +=== perf counters (hybrid runtime scene) === +[hperf] draw_calls=0 prims=0 vram_mb=0.0 skeletons=4 diff --git a/reports/visual/v0-animation-correction/iteration-04/blender-neutral.png b/reports/visual/v0-animation-correction/iteration-04/blender-neutral.png new file mode 100644 index 0000000..98e436e Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/blender-neutral.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/blender-skeleton.png b/reports/visual/v0-animation-correction/iteration-04/blender-skeleton.png new file mode 100644 index 0000000..f82568a Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/blender-skeleton.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/blender-weights-thigh_l.png b/reports/visual/v0-animation-correction/iteration-04/blender-weights-thigh_l.png new file mode 100644 index 0000000..45f4cd6 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/blender-weights-thigh_l.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/hybrid-perf.csv b/reports/visual/v0-animation-correction/iteration-04/hybrid-perf.csv new file mode 100644 index 0000000..8136da4 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-04/hybrid-perf.csv @@ -0,0 +1 @@ +hybrid_runtime,skeletons=4,draw_calls=362,primitives=187016,vram_mb=288.9 diff --git a/reports/visual/v0-animation-correction/iteration-04/hybrid-reach.png b/reports/visual/v0-animation-correction/iteration-04/hybrid-reach.png new file mode 100644 index 0000000..40845a6 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/hybrid-reach.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/hybrid-role-lineup.png b/reports/visual/v0-animation-correction/iteration-04/hybrid-role-lineup.png new file mode 100644 index 0000000..593543a Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/hybrid-role-lineup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/hybrid-runtime.png b/reports/visual/v0-animation-correction/iteration-04/hybrid-runtime.png new file mode 100644 index 0000000..b8ce08f Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/hybrid-runtime.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/hybrid-sit.png b/reports/visual/v0-animation-correction/iteration-04/hybrid-sit.png new file mode 100644 index 0000000..8c8a9c0 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/hybrid-sit.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/hybrid-walk.png b/reports/visual/v0-animation-correction/iteration-04/hybrid-walk.png new file mode 100644 index 0000000..efbf0dc Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/hybrid-walk.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/proc-vs-hybrid-sit.png b/reports/visual/v0-animation-correction/iteration-04/proc-vs-hybrid-sit.png new file mode 100644 index 0000000..03e708a Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/proc-vs-hybrid-sit.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/prop-attach-closeup.png b/reports/visual/v0-animation-correction/iteration-04/prop-attach-closeup.png new file mode 100644 index 0000000..7ba8ce4 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/prop-attach-closeup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/prop-attach-reach.png b/reports/visual/v0-animation-correction/iteration-04/prop-attach-reach.png new file mode 100644 index 0000000..1e1742e Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/prop-attach-reach.png differ diff --git a/reports/visual/v0-animation-correction/iteration-04/prop-carry-walk.png b/reports/visual/v0-animation-correction/iteration-04/prop-carry-walk.png new file mode 100644 index 0000000..e9df0f3 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-04/prop-carry-walk.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/COMPLETION.md b/reports/visual/v0-animation-correction/iteration-05/COMPLETION.md new file mode 100644 index 0000000..90d5373 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-05/COMPLETION.md @@ -0,0 +1,47 @@ +# Loop 5 — COMPLETION + +**Verdict: Conditional-Pass · Action: Defer.** (Full reasoning in DECISION.md; honest scores in +SCORES.md; independent critiques both rounds in CRITIQUE.md.) + +## Deliverables checklist + +| # | Required | Status | Evidence | +|---|---|---|---| +| 1 | ≥1 complete customer character (hybrid foundation, not server-minus-apron) | ✅ | customer-blender-neutral.png, population-standing | +| 2 | Deterministic variation (body×skin×hair×palette×face + trouser) | ✅ | customer-population-standing.png (V1–V6) | +| 3 | Customer animation language reusing the 43-clip UAL library | ✅ | +Eat/Impatient/SeekAttention authored; enter/sit/leave reuse UAL | +| 4 | Sitting gate — ≥4 customers, no float/sink/collapse | ✅ 4.0 | population-seated, lod-compare-sit, customer-sit | +| 5 | Prop-grip correction via finger bones (tray/tool) | ⚠️ 3.5 | grip-cook-tool.png (fist wraps handle), grip-serve-closeup.png | +| 6 | Hair/headwear socketed & deformation-safe | ✅ | bone-parented to Head; population shots, seek/eat | +| 7 | Clothing correction where materially needed | ⚠️ | trousers fixed to full-leg + varied; chest panel deferred | +| 8 | Animated LOD1 (~7–10k tris, skeleton/skin/clips/sit/identity kept) | ✅ 4.5 | 8,655 tris; lod-compare-sit.png, lod-compare-seek.png | +| 9 | Mini healthy-service scene (scripted enter→sit→eat→seek→leave, no AI/nav) | ✅ | service-front/hero/top.png; mini_service.gd | +| 10 | Full iteration-05 evidence archive | ✅ | this folder | +| 11 | Performance (server+cook+4cust, LOD0 vs LOD1, real counters) | ✅ | perf-lod.csv (29.5% scene tri reduction; honest gpu/fps note) | +| 12 | ≥1 independent critique each (art/readability/tech), failures documented | ✅ | CRITIQUE.md (3 initial + 1 re-review) | +| 13 | One focused commit | ✅ | `art: v0 anim iter 05 build customer and interaction baseline` | +| 14 | PR #8 kept draft/open/unmerged, description updated | ✅ | see PR #8 | + +## What was built +- `tools/blender/build_hybrid.py` extended: `customer` role (casual jacket/collar, full-leg + trousers, face with nose/mouth/brows, hair/cap), `--name/--trouser/--lod` args, deterministic + variation, and **authored clips** via sample-base-pose→hold-all-bones→overlay-deltas: + ServeCarry (cupped tray hand), CookHold (fist around tool), seated Eat / Impatient / SeekAttention. +- `mini_service.gd` + MiniService.tscn — scripted, deterministic healthy-service timeline + (staggered so one frame shows enter/sit/eat/seek/idle/leave), a tray-carrying server, a cook. +- `mini_perf.gd` + MiniPerf.tscn — windowed perf probe reporting render-independent scene tris. +- `prop_socket.gd` — tray vs tool props on hand_r; `hybrid_review.gd` — pop / twoup modes. +- 6 committed customer derivative GLBs (V1–V6) + 1 animated LOD1; rebuilt server/cook grips. +- 18 evidence PNGs + perf-lod.csv + Blender deformation/skeleton/weight renders. + +## What honestly fell short (carried forward, not hidden) +- **Server tray contact** clips the hand / lacks a clean rest → prop interaction 3.5 (<4). +- **Eat** reads as toward-mouth, not unambiguously eating (no food prop) → action readability 3.5 (<4). +- **Chest garment** still a flat floating panel → style compatibility 3.5 (target 4.2), deliberately + deferred per "Loop 5 is not clothing polish." +- Population palette recycles (V3≈V6, V1≈V5) → ~4–5 distinct individuals, not 6. + +## Loop accounting & constraints +5 of a minimum 10 loops complete (target 12 not complete). No PR merged. Backend/M0/`.cs`/`src`/ +`tests` untouched. M1 not begun. No human test run. No production AI/navigation. Superseded +procedural Loops 1–3 retained as evidence, not resumed. Raw Quaternius mesh remains git-ignored. diff --git a/reports/visual/v0-animation-correction/iteration-05/CRITIQUE.md b/reports/visual/v0-animation-correction/iteration-05/CRITIQUE.md new file mode 100644 index 0000000..2a5c3ca --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-05/CRITIQUE.md @@ -0,0 +1,58 @@ +# Loop 5 — Independent Critique (two rounds) + +Three independent critics (art / readability / technical) reviewed the rendered evidence and +the pipeline code. Their findings drove a refinement pass; a fourth independent critic then +re-reviewed the changed items. Nothing below is self-scored — these are the independent verdicts, +recorded verbatim in substance including the parts that fail. + +## Round 1 — initial independent critiques + +### Art critic — style-compatibility 3.6 / 5 (target 4.2, NOT met) +- Population variation real but shallow: concentrated in torso color + hair; **legs uniformly + tan**, faces blank. "Closer to jersey-swapping than a cast of individuals." +- **Biggest art risk: the chest garment reads as a hard, floating, boxy debug panel** that does + not wrap the torso — exactly the placeholder look to avoid. +- Customers ARE clearly distinct from crew (not "server minus apron"). Palette & soft forms do + belong to the warm restaurant world. + +### Readability critic — action 3.0 / 5, role 3.5 / 5 +- SEEK reads (single high arm, best action). Seated-idle reads. +- **EAT is AMBIGUOUS** — hand stalls at chest holding nothing; does not read as eating. +- **IMPATIENT is WEAK** — both-arms-outward collides with SEEK; reads as a shrug/confusion. +- Server tray reads in closeup; not verifiable at hero distance. + +### Technical critic +- SIT **PASS** (4/5) — feet on floor, hips/knees plausible, shadow contact, no float/sink/collapse. +- DEFORM **PASS** (4/5) — clean single-bone weights; clothing bends with the leg; minor cylinder + interpenetration at the inner knee (cosmetic). +- PROP GRIP **CONCERN** (2/5) — fingers genuinely curl but **the tray is a floating prop the hand + clips through; the fingers close on empty air.** Gesture, not contact. +- LOD **PASS** (4.5/5) — LOD0/LOD1 visually equivalent in sit and seek; rig+skin+clips+sit preserved. +- PIPELINE **PASS** (4/5) — the hold-all-bones/overlay-delta approach is correct and reproducible; + docstring slightly oversells "overlay" (interior frames are linear-interpolated); base-clip + name match is fragile (mitigated by exact/`_Loop` preference). +- PERF **honest** — real geometry cost; `gpu_ms/fps=unavailable` is honest disclosure, not fabrication. + +## Refinement pass (in response to Round 1) +- **Prop grip:** split by role — cook now grips a **cylindrical tool handle in a closed fist** + (fingers wrap the handle); server carries the tray on a **lighter cupped supporting hand**. +- **Eat:** hand raised to face height with curled food-holding fingers + head tilt. +- **Impatient:** retargeted to a **two-arm open-palm exasperated shrug**, distinct from SEEK's + single overhead raise. +- **Population:** added **per-customer trouser-color variation** (charcoal/tan/blue/grey). +- **Not addressed** (deliberately, per PM "Loop 5 is not clothing polish"): the chest-garment + silhouette. Carried forward. + +## Round 2 — independent re-review of the changed items +- PROP-INTERACTION **BETTER → 3.5 / 5.** Cook's fist convincingly wraps the handle; server tray + reads as carried but still clips the hand / lacks a clean rest point. (Up from 2.0.) +- ACTION-READABILITY **BETTER → 3.5 / 5.** **Impatient-vs-seek collision decisively resolved** + (two-arm shrug vs single overhead raise). Eat reads as toward-mouth but stops short with no + food prop — legible, not unambiguous. (Up from 3.0.) +- POPULATION-VARIATION **BETTER → 4.0 / 5.** Trouser variation is real; standing lineup reads as + distinct outfits. Caveat: palette recycles (V3≈V6, V1≈V5) → ~4–5 distinct individuals, not 6. +- STYLE-COMPATIBILITY **3.5 / 5 (target 4.2, still NOT met).** The floating boxy chest panel is + unchanged and remains the weakest link. + +**Residual (carried forward, honestly unmet):** server-tray clean contact; an eat food-prop; +the chest-garment silhouette (wrap the torso, add neckline/shoulders). diff --git a/reports/visual/v0-animation-correction/iteration-05/DECISION.md b/reports/visual/v0-animation-correction/iteration-05/DECISION.md new file mode 100644 index 0000000..64a2f61 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-05/DECISION.md @@ -0,0 +1,49 @@ +# Loop 5 — DECISION + +> **UPDATE after targeted remediation (`remediation/REMEDIATION.md`):** the three carried-forward +> gates were fixed and independently re-reviewed — prop interaction **4.3**, action readability +> **4.2**, style compatibility **4.4**, all ≥ bar, no regression. With those cleared, **Loop 5 now +> meets all gate thresholds (Pass).** The original Conditional-Pass rationale below is retained for +> the record. Loop 6 remains not-yet-authorized pending owner sign-off. + +## Verdict (original 89aed1f): **Conditional-Pass** + +## Action: **Defer** (do not auto-authorize Loop 6; owner decides whether to accept the 3.5s or +require the carried-forward fixes first) + +## Rationale +The core Loop-5 gates — the ones the PM named as "the actual gate" — are met: + +- **Animated customers** ✓ — a complete customer character on the hybrid foundation, with an + authored seated action language (idle / eat / seek / impatient) plus reused enter/sit/leave. +- **Sitting** ✓ (4.0) — clean, no float/sink/collapse; the hard gate holds. +- **Population variation** ✓ (4.0) — deterministic, reads as distinct outfits/people. +- **Prop gripping** — proven in principle (cook fist wraps a tool handle), but the server tray + contact is imperfect → 3.5, below the 4.0 bar. +- **One functioning LOD** ✓ (4.5) — animated LOD1 preserves skeleton/skin/clips/sit/identity. + +Three items sit below their bars and are reported without inflation: **prop interaction 3.5**, +**customer action readability 3.5**, **style compatibility 3.5** (target 4.2). Per the PM's own +stated trigger — *"if prop interaction stays <4 … report Conditional-Pass honestly rather than +inflate"* — the correct verdict is Conditional-Pass, not Pass. + +This is **not a Rewrite**: the pipeline is sound, the sit/LOD/deformation/variation gates pass, +and the two failing readability items improved materially in a single refinement pass (prop 2.0→3.5, +action 3.0→3.5, the impatient/seek collision fully resolved). The shortfalls are bounded polish, +not structural. + +## Carried forward (explicitly unmet, for the owner's decision) +1. **Server tray contact** — rest the tray cleanly on the hand (no clip/float). Highest-value fix + for the prop-interaction bar. +2. **Eat food-prop + hand-to-mouth** — socket a small food item and close the last gap to the + mouth so eat reads unambiguously. +3. **Chest-garment silhouette** — reshape the torso panel to wrap the body (neckline/shoulders) + so it stops reading as a floating boxy slab. This is the single biggest lift toward the 4.2 + style bar. (Intentionally deferred this loop per "Loop 5 is not clothing polish.") +4. **Population palette spread** — de-duplicate V3≈V6 and V1≈V5 for a richer crowd. + +## Standing constraints honored +No PR merged. No backend / M0 / `.cs` / `src` / `tests` touched. M1 not begun. No human test run. +No production AI/navigation (the mini-scene is a scripted, deterministic timeline). Superseded +procedural Loops 1–3 retained as evidence, not resumed. Raw Quaternius mesh remains git-ignored; +only customized derivatives are committed. This is **5 of a minimum 10 loops** (target 12). diff --git a/reports/visual/v0-animation-correction/iteration-05/HYPOTHESIS.md b/reports/visual/v0-animation-correction/iteration-05/HYPOTHESIS.md new file mode 100644 index 0000000..43b0538 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-05/HYPOTHESIS.md @@ -0,0 +1,40 @@ +# Loop 5 — Customer Cast & Interaction Readiness — HYPOTHESIS + +**Loop goal (PM framing):** Loop 5 is NOT facial/clothing polish. The actual gate is proving +*animated customers, sitting, population variation, prop gripping, and one functioning LOD.* + +## Hypotheses under test + +1. **Customer character** — a complete customer can be built on the same hybrid foundation + (Quaternius UAL CC0 rig/skin/43 clips + custom skinned clothing + bone-parented hair/face) + as the server/cook, with a casual silhouette that is NOT "a server minus apron." + +2. **Deterministic population variation** — sampling `2 body × 3+ skin × 3 hair × 6 palette × + 2 face(+trouser)` yields a cast that reads as distinct individuals, reproducibly, from seed + parameters only (no `Math.random`). + +3. **Customer animation language** — the seated action set (idle / eat / seek-attention / + impatient) can be authored on top of the clean UAL `Sitting_Idle` base and read as + themselves, reusing the 43-clip library for enter/walk/sit/stand/leave. + +4. **Sitting gate** — the UAL seated pose survives into every authored seated clip with no + floating / sinking / collapse. (A broken sit = Loop 5 failure.) + +5. **Prop grip** — finger bones can produce an *intentional* grip: a cook's closed fist around + a tool handle, and a server's cupped hand under a tray. + +6. **Animated LOD1** — a Blender-Decimate LOD1 in the 7k–10k range preserves skeleton, skin, + all clips, the sit, and identity. + +7. **Mini healthy-service scene** — a purely scripted, deterministic timeline (no AI / no nav) + can stage enter→sit→eat→seek→stand→leave for a customer crowd plus a carrying server and a + cook, along straight paths. + +8. **Honest performance** — LOD0 vs LOD1 cost can be measured with real, render-independent + geometry counts (not a `1/process_delta` fabrication). + +## Method +Blender 5.2 headless Python builds the customized derivative GLBs (`tools/blender/build_hybrid.py`); +Godot 4.7 windowed capture harness renders all evidence. Authored clips use a +*sample-clean-base-pose → hold all bones → overlay acting-bone deltas* strategy so an authored +clip can never fall back to the T-pose rest. Raw UAL mesh stays git-ignored; only derivatives commit. diff --git a/reports/visual/v0-animation-correction/iteration-05/SCORES.md b/reports/visual/v0-animation-correction/iteration-05/SCORES.md new file mode 100644 index 0000000..7642bd2 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-05/SCORES.md @@ -0,0 +1,48 @@ +# Loop 5 — SCORES + +> **UPDATE (targeted remediation — see `remediation/REMEDIATION.md`):** the three sub-bar gates +> were remediated and independently re-reviewed. **Prop interaction 3.5 → 4.3**, **customer action +> readability 3.5 → 4.2**, **style compatibility 3.5 → 4.4** — all now clear their bars, with no +> regression in sitting / deformation / LOD / Godot / reproducibility. The table below is the +> ORIGINAL 89aed1f state, retained for the record. + +# Loop 5 — SCORES (original 89aed1f state, honest, not inflated) + +Scores are the independent critics' post-refinement numbers. Where an item is below its bar it +is marked ✗ and left below its bar — no rounding up to pass. + +| Gate item | Bar | Score | Pass | +|---|---|---|---| +| Overall deformation | ≥ 4.0 | **4.0** | ✓ | +| Sitting (no float/sink/collapse) | ≥ 4.0 | **4.0** | ✓ | +| Customer role readability | ≥ 4.0 | **~3.7** | ✗ (marginal) | +| Customer action readability | ≥ 4.0 | **3.5** | ✗ | +| Prop interaction | ≥ 4.0 | **3.5** (was 2.0) | ✗ | +| Style compatibility | ≥ 4.2 | **3.5** | ✗ | +| Godot reliability | ≥ 4.0 | **4.5** | ✓ | +| Pipeline reproducibility | ≥ 4.0 | **4.0** | ✓ | +| LOD integrity | ≥ 4.0 | **4.5** | ✓ | +| No critical dimension < 3.0 | — | min = 3.5 | ✓ | + +## What passes +- **Sitting** is solid: the UAL seated pose survives into every authored seated clip; feet rest + on the floor with correct shadow contact; no float, sink, or collapse. The hard gate is met. +- **LOD1** (8,655 tris) is visually equivalent to LOD0 (15,527) in both sit and seek and retains + the 65-bone skeleton, skinning, all 47 clips, and identity. +- **Deformation / pipeline / Godot reliability** all clear their bars; the authoring approach is + correct and reproducible from seed parameters. +- **Population variation** reaches 4.0 on the strength of real per-customer trouser + top + hair + + cap + build variation. + +## What does NOT pass (below bar, reported honestly) +- **Prop interaction 3.5** — the cook's fist genuinely wraps the tool handle (strong), but the + server's tray still clips the hand and lacks a clean rest point. Improved from a failing 2.0 + but short of 4.0. This is the PM's explicit Conditional-Pass trigger. +- **Customer action readability 3.5** — impatient-vs-seek collision is fixed, but *eat* reads as + "toward-mouth" rather than unambiguously eating (no food prop, hand stops short of the mouth). +- **Style compatibility 3.5** (target 4.2) — the chest garment is still a flat floating panel that + does not wrap the torso. Deliberately not deep-fixed (PM: "Loop 5 is not clothing polish"). +- **Customer role readability ~3.7** — customers are clearly distinct from crew (tank vs + coat/toque/apron), but at true hero distance the server's tray is not reliably legible. + +No dimension is critical (<3.0); the lowest is 3.5. diff --git a/reports/visual/v0-animation-correction/iteration-05/VALIDATOR.txt b/reports/visual/v0-animation-correction/iteration-05/VALIDATOR.txt new file mode 100644 index 0000000..414d47b --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-05/VALIDATOR.txt @@ -0,0 +1,71 @@ +LOOP 5 — VALIDATOR / REPRODUCIBILITY +==================================== +Godot 4.7.1-stable (Forward+/Metal, Apple M3 Max, macOS). Blender 5.2.0 LTS headless Python. +All assets are deterministic from seed parameters. No Math.random anywhere. Raw Quaternius UAL +mesh stays git-ignored (CC0); only customized derivatives are committed. + +COMMITTED HYBRID CHARACTER INVENTORY (joints / clips / triangles) +---------------------------------------------------------------- + CHAR_Hybrid_Server_Average 65 44 15575 (+ServeCarry; tray on cupped hand) + CHAR_Hybrid_Server_Slim 65 43 15423 + CHAR_Hybrid_Cook_Average 65 44 15651 (+CookHold; fist grips tool handle) + CHAR_Hybrid_Cook_Broad 65 43 15499 + CHAR_Hybrid_Customer_V1..V6 65 46 ~15.5k (+Eat +Impatient +SeekAttention) + CHAR_Hybrid_Customer_V1_LOD1 65 46 8655 (Decimate ~0.557; rig+skin+clips+sit kept) + 43 clips = Quaternius UAL CC0 base library (Godot strips the "_Loop" suffix). + +DETERMINISTIC POPULATION (build_hybrid.py args) +----------------------------------------------- + V1 average skin_1 hair_dark blue outfit a trouser charcoal + V2 slim skin_2 hair_brown red outfit b trouser wood_oak (face B: brows + wider mouth) + V3 broad skin_3 hair_light green outfit a trouser deep_blue (cap on light hair) + V4 average skin_4 hair_dark sand outfit b trouser steel_dark (face B) + V5 slim skin_3 hair_brown plum outfit a trouser charcoal + V6 broad skin_1 hair_light olive outfit b trouser deep_blue (cap, face B) + Axes exercised: 3 body x 4 skin x 3 hair x 6 palette x 2 face x varied trouser. + +REBUILD A CHARACTER +------------------- + BL=/Applications/Blender.app/Contents/MacOS/Blender + UAL=visual/RestaurantVisualSandbox/placeholders/_assetlab/UAL1_Standard.glb # git-ignored; extract: + # ditto -x -k "~/Desktop/Restaurant Empire/Universal Animation Library[Standard].zip" DEST + $BL --background --python tools/blender/build_hybrid.py -- --ual $UAL \ + --out visual/RestaurantVisualSandbox/assets/generated \ + --role customer --name CHAR_Hybrid_Customer_V1 --body average --skin skin_1 \ + --hair hair_dark --palette blue --outfit a --trouser charcoal [--lod 0.5] + +RE-RENDER EVIDENCE (Godot windowed capture; run editor reimport first) +---------------------------------------------------------------------- + G=~/.local/opt/godot/Godot.app/Contents/MacOS/Godot ; P=visual/RestaurantVisualSandbox + $G --path $P --editor --quit --headless # REQUIRED after any GLB rebuild (reimport) + cap() { $G --path $P res://scenes/diagnostics/Capture.tscn -- --scene="$1" --out="$PWD/$2" \ + --w=1400 --h=800 --wait=30 "${@:3}"; } + cap res://scenes/diagnostics/HybridReview.tscn pop-stand.png --mode=pop --clip=idle + cap res://scenes/diagnostics/HybridReview.tscn pop-seat.png --mode=pop --clip=seated + cap res://scenes/diagnostics/HybridReview.tscn lod.png --mode=twoup --a=CHAR_Hybrid_Customer_V1 \ + --b=CHAR_Hybrid_Customer_V1_LOD1 --clip=sit + cap res://scenes/diagnostics/PropSocket.tscn cook-tool.png --char=CHAR_Hybrid_Cook_Average \ + --clip=hold --prop=tool --closeup=1 --oz=0.06 + cap res://scenes/diagnostics/MiniService.tscn service.png --t=3.0 --cam=hero + +PERFORMANCE (real, render-independent geometry cost; windowed, NO --headless) +----------------------------------------------------------------------------- + $G --path $P res://scenes/diagnostics/MiniPerf.tscn -- --lod=0 --out=/tmp/mp0.csv + $G --path $P res://scenes/diagnostics/MiniPerf.tscn -- --lod=1 --out=/tmp/mp1.csv + server+cook+4customers: LOD0 = 93,336 scene tris LOD1 = 65,848 scene tris (-29.5% scene, + -44.3% on the 4 customers). draw_calls 238 both (LOD does not change draw-call count). + gpu_ms/fps NOT reported: headless-offscreen macOS throttles to fps=1 and per-frame GPU time is + 0/unavailable here. That is an honest limitation, not a fabricated number. + +KEY PIPELINE NOTES / PITFALLS CONFIRMED THIS LOOP +------------------------------------------------- + * Blender 5.2 removed action.fcurves (slotted actions). Authored clips are built WITHOUT fcurve + surgery: sample the clean base clip's pose once, hold ALL bones at it on frames 1..length, + then overlay acting-bone deltas (base_matrix @ euler_delta). This makes a T-pose fallback + impossible and inherits the clean UAL sit into every seated clip. + * _find_action() must prefer an exact / "_Loop" match BEFORE substring, or "Idle" wrongly + matches "Crouch_Idle_Loop" (which pitched the carry pose into a forward dive). + * After ANY GLB rebuild, run the Godot editor reimport (--editor --quit --headless) BEFORE + capture, or the old cached .scn import plays. + * hybrid_review CLIP map must contain eat/impatient/seek/carry/hold keys, else CLIP.get() falls + back to "Idle" and a seated clip renders standing. (A silent dict-order patch miss caused this.) diff --git a/reports/visual/v0-animation-correction/iteration-05/blender-eat-check.png b/reports/visual/v0-animation-correction/iteration-05/blender-eat-check.png new file mode 100644 index 0000000..7c66be8 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/blender-eat-check.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/blender-seek-check.png b/reports/visual/v0-animation-correction/iteration-05/blender-seek-check.png new file mode 100644 index 0000000..bb54764 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/blender-seek-check.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/customer-blender-neutral.png b/reports/visual/v0-animation-correction/iteration-05/customer-blender-neutral.png new file mode 100644 index 0000000..65f7600 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/customer-blender-neutral.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/customer-blender-skeleton.png b/reports/visual/v0-animation-correction/iteration-05/customer-blender-skeleton.png new file mode 100644 index 0000000..38820fb Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/customer-blender-skeleton.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/customer-blender-weights-thigh_l.png b/reports/visual/v0-animation-correction/iteration-05/customer-blender-weights-thigh_l.png new file mode 100644 index 0000000..d642482 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/customer-blender-weights-thigh_l.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/customer-eat.png b/reports/visual/v0-animation-correction/iteration-05/customer-eat.png new file mode 100644 index 0000000..0e7f886 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/customer-eat.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/customer-impatient.png b/reports/visual/v0-animation-correction/iteration-05/customer-impatient.png new file mode 100644 index 0000000..bcc1f16 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/customer-impatient.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/customer-population-seated.png b/reports/visual/v0-animation-correction/iteration-05/customer-population-seated.png new file mode 100644 index 0000000..84cf223 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/customer-population-seated.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/customer-population-standing.png b/reports/visual/v0-animation-correction/iteration-05/customer-population-standing.png new file mode 100644 index 0000000..94bcbd5 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/customer-population-standing.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/customer-seek.png b/reports/visual/v0-animation-correction/iteration-05/customer-seek.png new file mode 100644 index 0000000..5f0505d Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/customer-seek.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/customer-sit.png b/reports/visual/v0-animation-correction/iteration-05/customer-sit.png new file mode 100644 index 0000000..f2a26d7 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/customer-sit.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/grip-cook-tool.png b/reports/visual/v0-animation-correction/iteration-05/grip-cook-tool.png new file mode 100644 index 0000000..578e8ea Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/grip-cook-tool.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/grip-serve-closeup.png b/reports/visual/v0-animation-correction/iteration-05/grip-serve-closeup.png new file mode 100644 index 0000000..abf99f9 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/grip-serve-closeup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/grip-serve-wide.png b/reports/visual/v0-animation-correction/iteration-05/grip-serve-wide.png new file mode 100644 index 0000000..ef63287 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/grip-serve-wide.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/lod-compare-seek.png b/reports/visual/v0-animation-correction/iteration-05/lod-compare-seek.png new file mode 100644 index 0000000..ccabd84 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/lod-compare-seek.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/lod-compare-sit.png b/reports/visual/v0-animation-correction/iteration-05/lod-compare-sit.png new file mode 100644 index 0000000..583ad94 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/lod-compare-sit.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/perf-lod.csv b/reports/visual/v0-animation-correction/iteration-05/perf-lod.csv new file mode 100644 index 0000000..cdcee5f --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-05/perf-lod.csv @@ -0,0 +1,8 @@ +metric,LOD0,LOD1,note +scene_triangles(server+cook+4cust),99552,72064,authoritative in-engine geometry cost (render-independent); post-remediation garment adds geometry +scene_reduction_pct,,27.6,whole-cast reduction when the 4 customers use LOD1 +customer_tris_each,16543,9671,Blender Decimate ~0.5; still within 7-10k target; 65-bone skeleton + skin + 46 clips + sit preserved +customer_reduction_pct,,41.5,per-customer triangle reduction +draw_calls,350,342,RenderingServer counter (LOD does not materially change draw-call count) +vram_mb,183.0,182.3,RenderingServer counter +gpu_ms,unavailable,unavailable,HONEST LIMITATION: per-frame GPU time reads 0 offscreen. fps read 84/69 this run but is not a reliable GPU benchmark headless; geometry counts are authoritative. diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/REMEDIATION.md b/reports/visual/v0-animation-correction/iteration-05/remediation/REMEDIATION.md new file mode 100644 index 0000000..db8e2f7 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-05/remediation/REMEDIATION.md @@ -0,0 +1,71 @@ +# Loop 5 — Targeted Remediation (interaction + style gates) + +Follow-up to the Loop-5 Conditional-Pass (commit 89aed1f). Three gates were below bar and were +remediated here. Verdict from an independent adversarial re-review: **all three now pass.** + +## Scope (exactly the three authorized items) +1. **Server-tray contact and intentional grip** +2. **Eating action with a visible food/utensil prop** +3. **Chest-garment silhouette and fit** + +## What changed +- **Garment (style):** the flat floating chest panel is replaced by a **body-hugging t-shirt** — + tapered torso that extends down to the trouser waist (no bare midriff), **rounded shoulder caps + + short skinned sleeves** (deform with the arms), and a **collar/neckline**. Applied to server, cook, + and all six customers. (`build_hybrid.py` garment block.) +- **Server tray (prop):** serve forearm lowered to a natural carry height; tray now rests **level on + the supporting hand** (`prop_socket.gd` tray default `rx=-20`, forward on the palm) instead of + clipping the wrist. Cook keeps the closed-fist grip **wrapped around a tool handle**. +- **Continuous carry (prop):** new **WalkCarry** clip — legs keep the Walk motion while the right arm + is pinned in the carry pose, so the tray stays gripped through a full walk cycle + (`author_walkcarry` in `build_hybrid.py`; recording via `carry_rec.gd`). +- **Eating (action readability):** a **fork with a visible food morsel** is socketed to the eating + hand (`prop_socket.gd` `_food()`), raised to the mouth — eating now reads unambiguously. + +## Updated scores (independent adversarial re-review) + +| Gate | Bar | Before (89aed1f) | After remediation | Result | +|---|---|---|---|---| +| **Prop interaction** | ≥ 4.0 | 3.5 | **4.3** | ✅ PASS | +| **Customer action readability** | ≥ 4.0 | 3.5 | **4.2** | ✅ PASS | +| **Style compatibility** | ≥ 4.2 | 3.5 | **4.4** | ✅ PASS | + +Independent findings (verbatim substance): tray underside sits flat on the hand with no gap; +cook's fist genuinely wraps the tool handle; the tray stays held through all 3 walk-cycle frames; +eat now shows a fork + orange morsel at the mouth; impatient (two-arm shrug) is distinct from seek +(single overhead raise); garment reads as a real fitted shirt with sleeves/collar, floating-panel +defect gone. Remaining items are cosmetic polish (slightly tilted tray, small morsel, mildly puffy +shoulder caps), not gate failures. + +## Regression — previously-accepted work verified unchanged + +| Dimension | Bar | Result | Evidence | +|---|---|---|---| +| Sitting (no float/sink/collapse) | ≥4 | ✅ feet flat, grounded shadows, clean seat | regression-sit.png, regression-lod-sit.png | +| Overall deformation | ≥4 | ✅ clean thigh_l weight gradient, no tearing | regression-weights-thigh_l.png, regression-skeleton.png | +| LOD integrity | ≥4 | ✅ LOD1 matches LOD0 in sit+seek | regression-lod-sit.png, regression-lod-seek.png | +| Godot reliability | ≥4 | ✅ no script/parse errors across all captures | (clean capture logs) | +| Pipeline reproducibility | ≥4 | ✅ deterministic from seed params, no Math.random | VALIDATOR.txt | + +**No regression found.** + +## Performance (real, render-independent geometry cost — final assets) +Garment adds geometry, so absolute tris rose vs 89aed1f, but the LOD saving holds: + +| Metric | LOD0 | LOD1 | +|---|---|---| +| Scene tris (server+cook+4 customers) | 99,552 | 72,064 (**−27.6% scene**) | +| Customer tris each | 16,543 | 9,671 (**−41.5%**, still in 7–10k target) | +| Draw calls | 350 | 342 | + +(gpu_ms unavailable/0 offscreen as before; this run did report fps 84/69 but that is not a reliable +GPU benchmark on this headless session — geometry counts are the authoritative figure.) + +## Evidence in this folder +Before/after prop: `tray-contact-before.png` → `tray-contact-after.png`, `cook-tool-grip.png`. +Continuous carry: `tray-carry.mp4`, `tray-carry.gif`, `tray-carry-filmstrip.png`. +Eating with prop: `eating-before.png` → `eating-with-prop.png`. +Before/after garment: `garment-before.png` → `garment-after.png`, `garment-clay-before.png` → +`garment-clay-after.png`. +Regression: `regression-sit.png`, `regression-lod-sit.png`, `regression-lod-seek.png`, +`regression-skeleton.png`, `regression-weights-thigh_l.png`. diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/cook-tool-grip.png b/reports/visual/v0-animation-correction/iteration-05/remediation/cook-tool-grip.png new file mode 100644 index 0000000..97a3b49 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/cook-tool-grip.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/eating-before.png b/reports/visual/v0-animation-correction/iteration-05/remediation/eating-before.png new file mode 100644 index 0000000..0e7f886 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/eating-before.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/eating-with-prop.png b/reports/visual/v0-animation-correction/iteration-05/remediation/eating-with-prop.png new file mode 100644 index 0000000..7efcab4 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/eating-with-prop.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/garment-after.png b/reports/visual/v0-animation-correction/iteration-05/remediation/garment-after.png new file mode 100644 index 0000000..0ec6bbe Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/garment-after.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/garment-before.png b/reports/visual/v0-animation-correction/iteration-05/remediation/garment-before.png new file mode 100644 index 0000000..494b5a7 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/garment-before.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/garment-clay-after.png b/reports/visual/v0-animation-correction/iteration-05/remediation/garment-clay-after.png new file mode 100644 index 0000000..dc67481 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/garment-clay-after.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/garment-clay-before.png b/reports/visual/v0-animation-correction/iteration-05/remediation/garment-clay-before.png new file mode 100644 index 0000000..65f7600 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/garment-clay-before.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/regression-lod-seek.png b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-lod-seek.png new file mode 100644 index 0000000..548ab42 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-lod-seek.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/regression-lod-sit.png b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-lod-sit.png new file mode 100644 index 0000000..60416cc Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-lod-sit.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/regression-sit.png b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-sit.png new file mode 100644 index 0000000..5bd30c9 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-sit.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/regression-skeleton.png b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-skeleton.png new file mode 100644 index 0000000..834f758 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-skeleton.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/regression-weights-thigh_l.png b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-weights-thigh_l.png new file mode 100644 index 0000000..b06cc43 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/regression-weights-thigh_l.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/tray-carry-filmstrip.png b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-carry-filmstrip.png new file mode 100644 index 0000000..0cc32ff Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-carry-filmstrip.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/tray-carry.gif b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-carry.gif new file mode 100644 index 0000000..4ea7570 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-carry.gif differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/tray-carry.mp4 b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-carry.mp4 new file mode 100644 index 0000000..bc26680 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-carry.mp4 differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/tray-contact-after.png b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-contact-after.png new file mode 100644 index 0000000..d8450ce Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-contact-after.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/remediation/tray-contact-before.png b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-contact-before.png new file mode 100644 index 0000000..abf99f9 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/remediation/tray-contact-before.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/service-front.png b/reports/visual/v0-animation-correction/iteration-05/service-front.png new file mode 100644 index 0000000..a0fdb47 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/service-front.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/service-hero.png b/reports/visual/v0-animation-correction/iteration-05/service-hero.png new file mode 100644 index 0000000..847bb5f Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/service-hero.png differ diff --git a/reports/visual/v0-animation-correction/iteration-05/service-top.png b/reports/visual/v0-animation-correction/iteration-05/service-top.png new file mode 100644 index 0000000..a842c3d Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-05/service-top.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/CRITIQUE.md b/reports/visual/v0-animation-correction/iteration-06/CRITIQUE.md new file mode 100644 index 0000000..8759e91 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-06/CRITIQUE.md @@ -0,0 +1,65 @@ +# Loop 6 — Independent Review (four reviewers) + required review-question answers + +Four independent critics reviewed the rendered evidence and pipeline code. Findings are recorded in +substance including everything that fails. Nothing is self-scored; nothing was fabricated. + +## Character-art review +- **Host role readability 2.5 — FAIL.** Reads as "server-minus-apron with a clipboard." The torso's + white-to-blue lower hem reads as a *ragged/torn shirt*, not formalwear; a stray white shoulder-cap + reads as an orphan pad/artifact; the clipboard says manager/inspector more than host. +- **Busser role readability 3.5 — FAIL.** The bus-tub prop is strong and legible, but the body is + colour-only differentiation — drop the tub and it's a green customer. No rolled sleeves/rag/half-apron + on the body. +- **Style compatibility 3.8 — FAIL (bar 4.2).** Busser fits the family; the host's jagged two-tone torso + uses a shading language no other role uses, breaking coherence. +- **Overall deformation 4.0 — PASS (borderline).** Skinning/proportions stable; defects are the hem seam + and the shoulder-cap "pad", which are modeling/texture not rig. + +## Animation-readability review +- **Host action readability 3.5 — FAIL.** check-list & apologetic read; **greet & gesture-to-dining are + weak/ambiguous**, and greet collides with the busser's reach. +- **Busser action readability 2.5 — FAIL.** tubhold and tubcarry are the same static pose; **wipe does not + read as wiping** and is indistinguishable from reach/clear. Only the tub prop + standing-vs-seated + contrast carry any signal. +- **Table-turn storytelling 3.5 — FAIL.** Standing-busser-among-seated-diners reads; the specific + clear/wipe/reset beat does not read in context and the tub is lost at scene scale. + +## Gameplay-readability review (the primary gate) +- **Entrance-queue readability 3.5 — FAIL (near-miss).** Crowd-density delta is the one genuine win, but + only reads as an A/B against the healthy baseline; a lone overload frame is ambiguous. +- **Dirty-table readability 1.5 — FAIL.** The table itself carries almost no state; dirty/available/reset + are separated only by tiny tabletop props that vanish at overview and in greyscale. +- **Table-turn storytelling 2.5 — FAIL.** The why-are-guests-waiting causal story does not read at overview. +- **Host/turnover failure at overview 2.0 — FAIL.** +- **Host role at overview 2.0 — FAIL. Busser role at overview 1.5 — FAIL.** Neither is a distinct + silhouette at management-camera distance. +- **Verdict: overload does NOT read at default overview.** With the HUD off, the only true reading is + "the front is crowded"; *why* (dirty/reset bottleneck), host-vs-busser, and turnover-vs-simply-full are + carried by the readability pips / presentation bar — the inverse of the gate. + +## Technical-art review +- **Prop interaction 3.5 — FAIL.** Clipboard credible. **The two-handed bus tub is socketed to `spine_02` + (torso), not a hand** — hands are posed to meet it but do not close on the handles; a reliable-but- + decoupled choice that will drift on the walking bake. Clears float geometry (~2 cm torso clearance). +- **Overall deformation 4.0 — PASS.** Host vest / busser apron+cuffs / sleeves hold; shared hem artifact + is pre-existing, not a regression. +- **Pipeline reproducibility 4.5 — PASS.** sample-base-pose + overlay-delta + pinned-walk bake is sound, + deterministic, Blender-5 safe. +- **Godot reliability 4.0 — PASS.** +- **REGRESSIONS: NONE.** server-tray, cook-tool, customer sit/eat/seek, LOD compare, healthy-service all + intact (Loop-5 assets unchanged; role branches cleanly gated). +- **Perf honesty: verified.** LOD1 24.8% reduction exact; per-char cost identical Loop5↔Loop6; gpu/fps + caveat honest. + +## Required review-question answers (§13), judged HUD-off at overview +1. Host reads without a label? **No** at overview (clipboard-in-a-crowd; costume weak). +2. Busser reads without a label? **Only via the tub prop**, not the body; not at overview. +3. Entrance queue understood spatially? **Partly** — crowd density reads as A/B, not in isolation. +4. Can the viewer see WHY guests are waiting? **No** at overview — open-but-dirty is sub-pixel. +5. Dirty vs clean without colour? **No** — fails the greyscale test on the tables themselves. +6. Table reset reads as a process not an instant swap? **Only in close-up**, not at overview. +7. Busser overloaded in the failure sequence? **Weakly** — via a second busser + loaded tub, not clearly. +8. Host occupied rather than idle? **Weakly** — inferred from the crowd, not the host's pose. +9. Kitchen visibly not the primary cause? **Partly** — by absence (calm back-of-house), not affirmatively. +10. Scenario reads at default overview? **No** — only "front is crowded" reads without the HUD. +11. Physical scene doing more explanatory work than the HUD? **No** — table state/why-waiting rely on pips. diff --git a/reports/visual/v0-animation-correction/iteration-06/DECISION.md b/reports/visual/v0-animation-correction/iteration-06/DECISION.md new file mode 100644 index 0000000..23ba73c --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-06/DECISION.md @@ -0,0 +1,45 @@ +# Loop 6 — DECISION + +## Loop 6 Verdict: **Fail** +## Action: **Targeted remediation** (then re-review; do NOT authorize Loop 7 yet) + +## Why Fail (not Conditional-Pass) +The loop's **stated purpose** — the entrance + table-turn state must read at the default management +overview, primarily through behaviour rather than the HUD — is **not met**. Independent gameplay-readability +review is explicit: with the HUD off, the physical scene reads only as "the front is crowded"; the *why* +(dirty/reset bottleneck), host-vs-busser roles, and turnover-vs-simply-full all rely on the readability +pips / presentation bar. Eleven of thirteen gate categories are below bar and **six fall below the +critical-3 floor** (dirty-table 1.5, busser-at-overview 1.5, host-at-overview 2.0, host/turnover-failure +2.0, busser-action 2.5, table-turn 2.5). Per §14 "no critical category may fall below 3" and §20 stop +conditions (host/busser role <4; dirty tables colour-dependent; reset reads as instant swap at overview; +host overload not distinct from idle; busser overload not distinct from normal cleaning; tub not +hand-gripped) — several stop conditions are triggered. Scores are not inflated and the bar was not lowered. + +## What is sound and should be PRESERVED (do not redo) +- Host + busser characters build on the accepted hybrid foundation; all authored clips export. +- **No regression** in Loop 5 work (server tray, cook tool, customer sit/eat/seek, LOD1, healthy-service). +- Pipeline reproducibility (4.5), Godot reliability (4.5), overall deformation (4.0) all pass. +- Performance is honest and shows no blowup (10-char cast 166k tris / 590 draw calls; LOD1 −24.8%). +- The bus-tub, clipboard, and table-state-lineup props are reusable; the queue-density delta is a real win. + +## Targeted-remediation path (the specific, addressable fixes) +1. **Table carries its own state at overview + in greyscale (root cause).** Replace tiny tabletop props + as the primary cue with a bold, silhouette/value-level physical difference per state that survives + zoom-out and desaturation — e.g. dirty = a tall dark dish-stack + clearly knocked-askew chairs (big + displacement) + stripped surface; being-cleaned = busser+tub committed at the table; reset/available = + neat low settings + squared chairs. Verify greyscale AND at the management camera, not just close-up. +2. **Host costume identity.** Give a real formalwear silhouette cue (proper waistcoat/collar) in clean + family-consistent solid shading; remove the ragged white-to-blue hem seam and the orphan shoulder-cap + "pad". Make host read without the clipboard. +3. **Busser body identity + pose-distinct actions.** Add on-body cues (rolled sleeves/rag/half-apron) so + the busser reads without the tub; make **wipe** a flat repeated horizontal hand-plane over a table + surface (distinct from reach/clear) and separate tubhold from tubcarry. +4. **Two-handed tub actually gripped.** Socket to `hand_l` (or a two-hand/midpoint) with both hands' fingers + closed on the handles; verify on the walking bake, not only the static closeup. +5. **Host/busser must read at overview**, not only in close-up — larger role-coded silhouette cues. + +## Standing constraints honored +No PR merged. No `.cs`/`src`/`tests`/M0/backend touched. M1 not begun. No human test. No production +AI/pathfinding/seating logic (all choreography scripted; snapshot controls table state). Superseded +procedural Loops 1–3 retained. Raw Quaternius mesh git-ignored; only derivatives committed. This is +**6 of a minimum 10 loops (target 12 not complete)**. Loop 7 NOT begun — awaiting the Art PM decision. diff --git a/reports/visual/v0-animation-correction/iteration-06/HYPOTHESIS.md b/reports/visual/v0-animation-correction/iteration-06/HYPOTHESIS.md new file mode 100644 index 0000000..c22dc7e --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-06/HYPOTHESIS.md @@ -0,0 +1,30 @@ +# Loop 6 — Host & Busser Operational Language — HYPOTHESIS + +**Loop purpose (PM framing):** from the DEFAULT MANAGEMENT OVERVIEW camera — scenario title, explanatory +sentence, and debug labels all hidden — a viewer can tell whether the entrance + table-turn process is +functioning normally or failing, **primarily through host / guest / busser / table behaviour, not a HUD.** +Still scripted visual choreography. No production AI / pathfinding / seating logic / authoritative table +availability. + +## Hypotheses under test +1. **Host character** on the accepted hybrid foundation reads as a host (not server-minus-apron / + customer-behind-podium / untouched-Quaternius / colour-only) via ≥2 identity cues. +2. **Busser character** reads as responsible for table turnover via ≥2 identity cues. +3. **Host animation language** (podium idle / check-list / greet / gesture-to-dining / lead / return / + address-queue / apologetic / overloaded / spare) — each distinguishable from generic idle. +4. **Busser animation language** (carry tub / approach / collect / place-in-tub / wipe / reset / carry-away / + wait / hurried / idle) — physically distinct. +5. **Physical table-state language** — Available / Occupied / Dirty / Being-cleaned / Reset conveyed by + physical cues (dishes, chairs, busser+tub, settings) that survive greyscale and read at overview. +6. **Choreography** — healthy vs overloaded entrance + table-turn sequences that read at overview. +7. **Props** — clipboard, bus tub, cloth, dirty/clean table sets attach credibly (hands contact, no float). +8. **Regression** — no damage to accepted Loop 5 work (server tray, cook tool, customer sit/eat/seek, LOD1, + healthy-service). + +## Method +Blender 5.2 headless authors host/busser derivative GLBs (`build_hybrid.py`: garment identity + authored +clips via sample-base-pose→hold-all-bones→overlay-delta, and `author_pinned_walk` bakes for two-handed +carry). Godot 4.7 renders: `host_turnover.gd` (integrated healthy/overload scene + presentation/readability +HUD), `table_states.gd` (state lineup + greyscale), review-scene reel/family/bodyline modes, `mini_perf` +extended to the Loop-6 cast. Four independent reviews (character-art / animation-readability / +gameplay-readability / technical-art) judge the result. Raw UAL stays git-ignored; only derivatives commit. diff --git a/reports/visual/v0-animation-correction/iteration-06/SCORES.md b/reports/visual/v0-animation-correction/iteration-06/SCORES.md new file mode 100644 index 0000000..77df83c --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-06/SCORES.md @@ -0,0 +1,39 @@ +# Loop 6 — SCORES (independent, honest, not inflated) + +Scores are the independent reviewers' numbers. Below-bar is left below-bar. Below the critical-3 floor is +flagged. (§14 bars: all ≥4.0, style ≥4.2; "no critical category may fall below 3".) + +| Gate category | Bar | Score | Pass | Critical<3 | +|---|---|---|---|---| +| Host role readability | ≥4.0 | **2.5** | ✗ | — | +| Busser role readability | ≥4.0 | **3.5** | ✗ | — | +| Host action readability | ≥4.0 | **3.5** | ✗ | — | +| Busser action readability | ≥4.0 | **2.5** | ✗ | **<3** | +| Entrance-queue readability | ≥4.0 | **3.5** | ✗ | — | +| Dirty-table readability | ≥4.0 | **1.5** | ✗ | **<3** | +| Table-turn storytelling | ≥4.0 | **2.5** | ✗ | **<3** | +| Host/turnover failure at overview | ≥4.0 | **2.0** | ✗ | **<3** | +| Prop interaction | ≥4.0 | **3.5** | ✗ | — | +| Overall deformation | ≥4.0 | **4.0** | ✓ | — | +| Style compatibility | ≥4.2 | **3.8** | ✗ | — | +| Godot reliability | ≥4.0 | **4.5** | ✓ | — | +| Pipeline reproducibility | ≥4.0 | **4.5** | ✓ | — | + +Additional at-overview role reads (gameplay reviewer): host-at-overview **2.0** (<3), busser-at-overview +**1.5** (<3). + +## Summary +- **PASS:** Overall deformation (4.0), Godot reliability (4.5), Pipeline reproducibility (4.5). Regression: + NONE. Perf: honest, no blowup (Loop6 10-char cast 166k tris / 590 draw calls; LOD1 −24.8%). +- **BELOW BAR:** every readability/role/action/prop/style category. +- **CRITICAL-FLOOR VIOLATIONS (<3):** busser action (2.5), dirty-table (1.5), table-turn (2.5), + host/turnover-failure-at-overview (2.0), host-at-overview (2.0), busser-at-overview (1.5). + +## Root cause (single) +**The table itself carries no state.** Available/Dirty/Reset are separated only by small tabletop props +that are invisible at the management-camera distance and essentially invisible in greyscale. Because +table state is the linchpin of the whole "why are guests waiting past open tables" story, its +illegibility collapses the causal chain — the physical scene reads only as "front is crowded," and the +operational story is carried by the HUD pips, which is the inverse of the gate. Secondary: host has no +costume identity (prop-only + a ragged-hem torso), and busser actions are prop-dependent (wipe≈reach, +tub not hand-gripped). diff --git a/reports/visual/v0-animation-correction/iteration-06/busser-anim-reel.png b/reports/visual/v0-animation-correction/iteration-06/busser-anim-reel.png new file mode 100644 index 0000000..69c841c Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/busser-anim-reel.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/busser-neutral-lineup.png b/reports/visual/v0-animation-correction/iteration-06/busser-neutral-lineup.png new file mode 100644 index 0000000..bd9e2bb Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/busser-neutral-lineup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/busser-prop-closeup.png b/reports/visual/v0-animation-correction/iteration-06/busser-prop-closeup.png new file mode 100644 index 0000000..46d2976 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/busser-prop-closeup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/entrance-healthy.png b/reports/visual/v0-animation-correction/iteration-06/entrance-healthy.png new file mode 100644 index 0000000..c924cf0 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/entrance-healthy.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/entrance-overload.png b/reports/visual/v0-animation-correction/iteration-06/entrance-overload.png new file mode 100644 index 0000000..a8f6130 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/entrance-overload.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/host-anim-reel.png b/reports/visual/v0-animation-correction/iteration-06/host-anim-reel.png new file mode 100644 index 0000000..6428524 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/host-anim-reel.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/host-neutral-lineup.png b/reports/visual/v0-animation-correction/iteration-06/host-neutral-lineup.png new file mode 100644 index 0000000..4d8c2e9 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/host-neutral-lineup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/host-prop-closeup.png b/reports/visual/v0-animation-correction/iteration-06/host-prop-closeup.png new file mode 100644 index 0000000..83fd9ed Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/host-prop-closeup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/loop5-vs-loop6.png b/reports/visual/v0-animation-correction/iteration-06/loop5-vs-loop6.png new file mode 100644 index 0000000..459dcf6 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/loop5-vs-loop6.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/overview-healthy-recording.mp4 b/reports/visual/v0-animation-correction/iteration-06/overview-healthy-recording.mp4 new file mode 100644 index 0000000..c707b33 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/overview-healthy-recording.mp4 differ diff --git a/reports/visual/v0-animation-correction/iteration-06/overview-healthy.png b/reports/visual/v0-animation-correction/iteration-06/overview-healthy.png new file mode 100644 index 0000000..0471a44 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/overview-healthy.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/overview-overload-presentation.png b/reports/visual/v0-animation-correction/iteration-06/overview-overload-presentation.png new file mode 100644 index 0000000..6489226 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/overview-overload-presentation.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/overview-overload-readability.png b/reports/visual/v0-animation-correction/iteration-06/overview-overload-readability.png new file mode 100644 index 0000000..9a43331 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/overview-overload-readability.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/overview-overload-recording.mp4 b/reports/visual/v0-animation-correction/iteration-06/overview-overload-recording.mp4 new file mode 100644 index 0000000..606af66 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/overview-overload-recording.mp4 differ diff --git a/reports/visual/v0-animation-correction/iteration-06/overview-overload.png b/reports/visual/v0-animation-correction/iteration-06/overview-overload.png new file mode 100644 index 0000000..11d6753 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/overview-overload.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/performance-counters.csv b/reports/visual/v0-animation-correction/iteration-06/performance-counters.csv new file mode 100644 index 0000000..14ef822 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-06/performance-counters.csv @@ -0,0 +1,14 @@ +metric,Loop6_LOD0,Loop6_LOD1,Loop5_LOD0,note +cast,host+busser+server+cook+6cust,same(LOD1 customers),server+cook+4cust,windowed real counters +characters,10,10,6,character count +active_skeletons,10,10,6,Skeleton3D nodes +active_anim_players,10,10,6,AnimationPlayer nodes playing clips +scene_triangles,166040,124808,99552,authoritative in-engine geometry (render-independent) +lod1_reduction_pct,,24.8,,LOD1 vs LOD0 on the Loop6 cast +draw_calls,590,589,350,RenderingServer counter +primitives,338570,339058,206458,RenderingServer counter (single-frame) +vram_mb,185.4,184.6,183.0,RenderingServer counter +cpu_ms,0.203,0.204,0.186,viewport measured CPU +gpu_ms,unavailable,unavailable,unavailable,HONEST: per-frame GPU time reads 0 offscreen; fps (65/85) is NOT a reliable benchmark headless +warnings,glTF >4-joint-influence (benign),,,export-time normalization note only +errors,none,none,none,no script/parse/runtime errors across captures diff --git a/reports/visual/v0-animation-correction/iteration-06/presentation-mode-recording.mp4 b/reports/visual/v0-animation-correction/iteration-06/presentation-mode-recording.mp4 new file mode 100644 index 0000000..6ed26af Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/presentation-mode-recording.mp4 differ diff --git a/reports/visual/v0-animation-correction/iteration-06/readability-mode-recording.mp4 b/reports/visual/v0-animation-correction/iteration-06/readability-mode-recording.mp4 new file mode 100644 index 0000000..06a578f Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/readability-mode-recording.mp4 differ diff --git a/reports/visual/v0-animation-correction/iteration-06/regression-checklist.md b/reports/visual/v0-animation-correction/iteration-06/regression-checklist.md new file mode 100644 index 0000000..5cd13c9 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-06/regression-checklist.md @@ -0,0 +1,20 @@ +# Loop 6 — Regression Checklist (accepted Loop 5 work) + +Loop 5 character GLBs were NOT rebuilt in Loop 6 (server/cook/customer assets are byte-unchanged); only +host/busser were added and the shared `build_hybrid.py` role branches are cleanly gated by `if role ==`. +Each accepted item was re-rendered and independently confirmed intact. + +| Item | Result | Evidence | +|---|---|---| +| Server tray carry | PASS — tray level on cupped hand, fingers wrapped | regression/server-tray.png | +| Cook tool grip | PASS — spatula through closed fist, toque on | regression/cook-tool.png | +| Customer sitting | PASS — clean seat, no float/sink | regression/customer-sit.png | +| Customer eating | PASS — fork+morsel to mouth, seated | regression/customer-eat.png | +| Customer seek-attention | PASS — single raised arm, seated | regression/customer-seek.png | +| Customer enter→leave | PASS — reused UAL Walk/Sitting clips unchanged | (mini_service healthy-service) | +| LOD1 playback | PASS — LOD1 matches LOD0 (sit) | regression/lod-compare.png | +| Healthy-service scene | PASS — full cast populates, roles coexist | regression/healthy-service.png | + +Confirmed no regression in: prop attachment, sitting, deformation, character style, LOD identity, +animation import, Godot runtime, pipeline reproducibility. Independent technical-art review: "REGRESSIONS +FOUND: NO." diff --git a/reports/visual/v0-animation-correction/iteration-06/regression/cook-tool.png b/reports/visual/v0-animation-correction/iteration-06/regression/cook-tool.png new file mode 100644 index 0000000..3a6bcb9 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/regression/cook-tool.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/regression/customer-eat.png b/reports/visual/v0-animation-correction/iteration-06/regression/customer-eat.png new file mode 100644 index 0000000..49cee6f Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/regression/customer-eat.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/regression/customer-seek.png b/reports/visual/v0-animation-correction/iteration-06/regression/customer-seek.png new file mode 100644 index 0000000..499aff0 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/regression/customer-seek.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/regression/customer-sit.png b/reports/visual/v0-animation-correction/iteration-06/regression/customer-sit.png new file mode 100644 index 0000000..f0f3cd7 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/regression/customer-sit.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/regression/healthy-service.png b/reports/visual/v0-animation-correction/iteration-06/regression/healthy-service.png new file mode 100644 index 0000000..49a8d1e Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/regression/healthy-service.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/regression/lod-compare.png b/reports/visual/v0-animation-correction/iteration-06/regression/lod-compare.png new file mode 100644 index 0000000..0fc1828 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/regression/lod-compare.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/regression/server-tray.png b/reports/visual/v0-animation-correction/iteration-06/regression/server-tray.png new file mode 100644 index 0000000..51a0da2 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/regression/server-tray.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/role-family-lineup.png b/reports/visual/v0-animation-correction/iteration-06/role-family-lineup.png new file mode 100644 index 0000000..384edf4 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/role-family-lineup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/table-state-greyscale.png b/reports/visual/v0-animation-correction/iteration-06/table-state-greyscale.png new file mode 100644 index 0000000..795f7c5 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/table-state-greyscale.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/table-state-lineup.png b/reports/visual/v0-animation-correction/iteration-06/table-state-lineup.png new file mode 100644 index 0000000..4828e88 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/table-state-lineup.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/tableturn-healthy.png b/reports/visual/v0-animation-correction/iteration-06/tableturn-healthy.png new file mode 100644 index 0000000..e9c82ba Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/tableturn-healthy.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/tableturn-overload.png b/reports/visual/v0-animation-correction/iteration-06/tableturn-overload.png new file mode 100644 index 0000000..53477d9 Binary files /dev/null and b/reports/visual/v0-animation-correction/iteration-06/tableturn-overload.png differ diff --git a/reports/visual/v0-animation-correction/iteration-06/validator.txt b/reports/visual/v0-animation-correction/iteration-06/validator.txt new file mode 100644 index 0000000..14486c6 --- /dev/null +++ b/reports/visual/v0-animation-correction/iteration-06/validator.txt @@ -0,0 +1,54 @@ +LOOP 6 — VALIDATOR / REPRODUCIBILITY +==================================== +Godot 4.7.1-stable (Forward+/Metal, Apple M3 Max). Blender 5.2.0 LTS headless Python. +Deterministic from seed params. No Math.random. Raw Quaternius UAL mesh git-ignored (CC0); only +customized derivatives committed. NO production AI / pathfinding / seating logic / authoritative table +availability — all choreography is a scripted snapshot. + +NEW COMMITTED CHARACTERS (joints / clips / tris) + CHAR_Hybrid_Host_{Average,Slim,Broad} 65 47 ~16,647 (+CheckList,Greet,GestureDining,Apologetic) + CHAR_Hybrid_Busser_{Average,Slim,Broad} 65 46 ~16,755 (+TubHold,TubCarry,Wipe) + (server/cook/customer GLBs unchanged from the accepted Loop-5 remediation) + +BUILD A HOST / BUSSER + BL=/Applications/Blender.app/Contents/MacOS/Blender + UAL=visual/RestaurantVisualSandbox/placeholders/_assetlab/UAL1_Standard.glb # git-ignored + $BL --background --python tools/blender/build_hybrid.py -- --ual $UAL \ + --out visual/RestaurantVisualSandbox/assets/generated \ + --role host --body average --skin skin_2 --hair hair_dark --palette teal + $BL --background --python tools/blender/build_hybrid.py -- --ual $UAL --out ... \ + --role busser --body average --skin skin_1 --hair hair_brown --palette green + +RE-RENDER EVIDENCE (Godot windowed capture; run editor reimport after any GLB rebuild) + G=~/.local/opt/godot/Godot.app/Contents/MacOS/Godot ; P=visual/RestaurantVisualSandbox + $G --path $P --editor --quit --headless # REQUIRED reimport + cap(){ $G --path $P res://scenes/diagnostics/Capture.tscn -- --scene="$1" --out="$PWD/$2" --w=1500 --h=850 --wait=40 "${@:3}"; } + cap res://scenes/diagnostics/HybridReview.tscn role-family.png --mode=family + cap res://scenes/diagnostics/HybridReview.tscn host-reel.png --mode=reel --char=CHAR_Hybrid_Host_Average --clips=checklist,greet,gesture,apologetic + cap res://scenes/diagnostics/TableStates.tscn states.png + cap res://scenes/diagnostics/HostTurnover.tscn overview.png --scenario=overload --hud=off --cam=overview + # HUD layers: --hud=presentation | --hud=readability (default off = physical scene only) + # scenarios: --scenario=healthy | overload cameras: --cam=overview|host|turn + +RECORDINGS (windowed movie writer; engine flags BEFORE the scene, user args after --) + $G --path $P --write-movie /tmp/x.avi --fixed-fps 30 res://scenes/diagnostics/HostTurnover.tscn -- \ + --scenario=overload --hud=off --cam=overview --rec=1 + ffmpeg -y -i /tmp/x.avi -vf scale=1000:-2 -c:v libx264 -pix_fmt yuv420p out.mp4 + +PERFORMANCE (windowed; real geometry counts; NOT process-delta) + $G --path $P res://scenes/diagnostics/MiniPerf.tscn -- --cast=loop6 --lod=0 --out=/tmp/p.csv + Loop6 (host+busser+server+cook+6cust, 10 skeletons): LOD0 166,040 tris / 590 draw calls / 185.4 MB; + LOD1 124,808 tris (-24.8%). Loop5 (6): 99,552 / 350. Per-char cost ~16.6k both loops. + gpu_ms unavailable offscreen (fps 65/85 not a reliable benchmark) — geometry counts are authoritative. + +NEW FILES THIS LOOP + scripts: host_turnover.gd, table_states.gd, carry_rec.gd(from L5), + prop_socket/hybrid_review/mini_perf + extensions (clipboard/cloth/tub props, bone arg; reel/family/bodyline modes; loop6 cast). + scenes/diagnostics: HostTurnover.tscn, TableStates.tscn + build_hybrid.py: host+busser roles (vest/tie, utility apron/cuffs), author_pinned_walk, TUB_ARMS, host+busser clips. + +KNOWN-GOOD PITFALL NOTES (still apply) + * After any GLB rebuild, run the Godot editor reimport before capture. + * GDScript strict mode: annotate types for any var inferred from Dictionary/Array-index (Variant), + and avoid unused vars (warnings are errors). + * --write-movie / --fixed-fps are ENGINE args (before the scene path); scene args go after `--`. diff --git a/reports/visual/v0-animation-correction/performance/animated-performance.csv b/reports/visual/v0-animation-correction/performance/animated-performance.csv new file mode 100644 index 0000000..d92f556 --- /dev/null +++ b/reports/visual/v0-animation-correction/performance/animated-performance.csv @@ -0,0 +1,3 @@ +scenario,skeletons,anim_players,mesh_instances,draw_calls,primitives,gpu_ms_avg,gpu_ms_worst,cpu_render_ms_avg,vram_mb +healthy,35,35,118,475,541606,0.00,0.00,0.25,290.7 +kitchen,35,35,118,475,541606,0.00,0.00,0.23,291.0 diff --git a/reports/visual/v0-animation-correction/recordings/loop02-locomotion.mp4 b/reports/visual/v0-animation-correction/recordings/loop02-locomotion.mp4 new file mode 100644 index 0000000..d1e5759 Binary files /dev/null and b/reports/visual/v0-animation-correction/recordings/loop02-locomotion.mp4 differ diff --git a/reports/visual/v0-animation-correction/recordings/loop03-healthy-service.mp4 b/reports/visual/v0-animation-correction/recordings/loop03-healthy-service.mp4 new file mode 100644 index 0000000..a02a6af Binary files /dev/null and b/reports/visual/v0-animation-correction/recordings/loop03-healthy-service.mp4 differ diff --git a/reports/visual/v0-animation-correction/recordings/loop03-kitchen-service.mp4 b/reports/visual/v0-animation-correction/recordings/loop03-kitchen-service.mp4 new file mode 100644 index 0000000..7a2069b Binary files /dev/null and b/reports/visual/v0-animation-correction/recordings/loop03-kitchen-service.mp4 differ diff --git a/reports/visual/v0-animation-correction/recordings/loop04-hybrid-runtime.mp4 b/reports/visual/v0-animation-correction/recordings/loop04-hybrid-runtime.mp4 new file mode 100644 index 0000000..c6dc684 Binary files /dev/null and b/reports/visual/v0-animation-correction/recordings/loop04-hybrid-runtime.mp4 differ diff --git a/tools/blender/__pycache__/_bkit.cpython-313.pyc b/tools/blender/__pycache__/_bkit.cpython-313.pyc deleted file mode 100644 index d59d0d2..0000000 Binary files a/tools/blender/__pycache__/_bkit.cpython-313.pyc and /dev/null differ diff --git a/tools/blender/blender_review.py b/tools/blender/blender_review.py new file mode 100644 index 0000000..9fc458b --- /dev/null +++ b/tools/blender/blender_review.py @@ -0,0 +1,89 @@ +"""Loop-4 Blender evidence: neutral clay render, skeleton-dots view, weight visualization. +blender --background --python tools/blender/blender_review.py -- --glb PATH --outdir DIR""" +import bpy, sys, os, math +from mathutils import Vector + +def _arg(flag, default): + a = sys.argv + if "--" in a: + r = a[a.index("--") + 1:] + for i, x in enumerate(r): + if x == flag and i + 1 < len(r): + return r[i + 1] + return default + +glb = _arg("--glb", "visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Average.glb") +outdir = _arg("--outdir", "reports/visual/v0-animation-correction/iteration-04") +os.makedirs(outdir, exist_ok=True) + +def setup_scene(): + sc = bpy.context.scene + sc.render.engine = 'BLENDER_WORKBENCH' + sc.render.resolution_x = 900; sc.render.resolution_y = 1100 + sc.render.film_transparent = False + sc.world = bpy.data.worlds.new("W"); sc.world.use_nodes = True + sc.world.node_tree.nodes["Background"].inputs[0].default_value = (0.11, 0.12, 0.14, 1) + # camera + cam_data = bpy.data.cameras.new("cam"); cam = bpy.data.objects.new("cam", cam_data) + sc.collection.objects.link(cam); sc.camera = cam + cam.location = (2.2, -2.8, 1.15); cam.rotation_euler = (math.radians(78), 0, math.radians(38)) + return sc + +def render(path): + bpy.context.scene.render.filepath = path + bpy.ops.render.render(write_still=True) + print("RENDER", os.path.basename(path)) + +# ---- 1. neutral clay ---- +bpy.ops.wm.read_factory_settings(use_empty=True) +sc = setup_scene() +bpy.ops.import_scene.gltf(filepath=glb) +sc.display.shading.light = 'STUDIO' +sc.display.shading.color_type = 'SINGLE' +sc.display.shading.single_color = (0.8, 0.78, 0.74) +sc.display.shading.show_shadows = True +render(os.path.join(outdir, "blender-neutral.png")) + +# ---- 2. skeleton-dots (spheres at each bone head+tail) ---- +arm = next((o for o in bpy.data.objects if o.type == 'ARMATURE'), None) +if arm: + for m in [o for o in bpy.data.objects if o.type == 'MESH']: + m.hide_render = True + matj = bpy.data.materials.new("joint"); matj.diffuse_color = (1.0, 0.5, 0.15, 1) + for b in arm.data.bones: + for pt in (b.head_local, b.tail_local): + w = arm.matrix_world @ pt + bpy.ops.mesh.primitive_ico_sphere_add(radius=0.018, subdivisions=1, location=w) + bpy.context.active_object.data.materials.append(matj) + # thin bone segments + matb = bpy.data.materials.new("boneseg"); matb.diffuse_color = (0.6, 0.8, 1.0, 1) + sc.display.shading.color_type = 'OBJECT' + render(os.path.join(outdir, "blender-skeleton.png")) + for m in [o for o in bpy.data.objects if o.type == 'MESH' and 'sphere' not in o.name.lower() and 'Ico' not in o.name]: + m.hide_render = False + +# ---- 3. weight visualization: colour the body by a bone's weights ---- +bpy.ops.wm.read_factory_settings(use_empty=True) +sc = setup_scene() +bpy.ops.import_scene.gltf(filepath=glb) +body = max((o for o in bpy.data.objects if o.type == 'MESH'), key=lambda o: len(o.data.vertices)) +target = None +for cand in ("upperleg_l", "thigh_l", "upperarm_l", "spine_02", "hips", "pelvis"): + if cand in [g.name for g in body.vertex_groups]: + target = body.vertex_groups[cand]; break +if target: + col = body.data.color_attributes.new(name="w", type='FLOAT_COLOR', domain='POINT') + for v in body.data.vertices: + wt = 0.0 + for g in v.groups: + if g.group == target.index: + wt = g.weight + # blue(0) -> red(1) + col.data[v.index].color = (wt, 0.15, 1.0 - wt, 1.0) + for m in [o for o in bpy.data.objects if o.type == 'MESH' and o != body]: + m.hide_render = True + sc.display.shading.light = 'FLAT' + sc.display.shading.color_type = 'VERTEX' + render(os.path.join(outdir, "blender-weights-%s.png" % target.name)) + print("WEIGHTVIZ bone=", target.name) +print("DONE") diff --git a/tools/blender/build_hybrid.py b/tools/blender/build_hybrid.py new file mode 100644 index 0000000..6968594 --- /dev/null +++ b/tools/blender/build_hybrid.py @@ -0,0 +1,337 @@ +"""V0 HYBRID character — Quaternius UAL CC0 rig/skin/clips + CUSTOM restaurant identity. +blender --background --python tools/blender/build_hybrid.py -- --ual PATH --out DIR + --role server|cook|customer --body average|slim|broad --skin skin_1 --hair hair_dark + --palette blue|green|red|... [--lod 0.55] [--outfit a|b] + +Keeps the UAL mesh + 65-bone skeleton + 43 clips, adds custom clothing/hair/headwear/face SKINNED +or bone-parented to the same armature, authors grip + seated-action clips, optional decimated LOD1. +Only the CUSTOMIZED derivative is committed; the raw UAL stays git-ignored (CC0).""" +import bpy, sys, os, math +from mathutils import Euler +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import _bkit as k + +PALETTES = {"blue": "server_blue", "teal": "host_teal", "green": "busser_green", + "red": "terracotta", "sand": "cust_warm", "plum": "deep_blue", "olive": "muted_green"} + +def _arg(flag, default): + a = sys.argv + if "--" in a: + r = a[a.index("--") + 1:] + for i, x in enumerate(r): + if x == flag and i + 1 < len(r): + return r[i + 1] + return default + +def import_ual(path): + bpy.ops.wm.read_factory_settings(use_empty=True) + bpy.ops.import_scene.gltf(filepath=path) + arm = next(o for o in bpy.data.objects if o.type == 'ARMATURE') + meshes = [o for o in bpy.data.objects if o.type == 'MESH'] + return arm, meshes + +def set_mat(obj, key, rough=0.6): + obj.data.materials.clear(); obj.data.materials.append(k.mat("MAT_" + str(key), key, rough)) + +def skin(mesh, arm): + bpy.ops.object.select_all(action='DESELECT') + mesh.select_set(True); arm.select_set(True) + bpy.context.view_layer.objects.active = arm + bpy.ops.object.parent_set(type='ARMATURE_AUTO') + +def bone_parent(mesh, arm, bone): + mesh.parent = arm; mesh.parent_type = 'BONE'; mesh.parent_bone = bone + mesh.matrix_parent_inverse = (arm.matrix_world @ arm.pose.bones[bone].matrix).inverted() + +# ---- grip + seated clip authoring ----------------------------------------- +# Approach: DUPLICATE a clean UAL clip (Idle for standing carry, Sitting_Idle for +# seated actions) and override ONLY the acting bones as a delta ON TOP of the sampled +# base pose. This inherits natural arms/legs/pelvis from the UAL clip so an authored +# clip never falls back to the T-pose rest, and seated clips keep the clean sit. + +def _find_action(sub): + s = sub.lower() + for a in bpy.data.actions: # prefer exact or "_Loop" before any substring hit + if a.name.lower() == s or a.name.lower() == s + "_loop": + return a + for a in bpy.data.actions: + if s in a.name.lower(): + return a + return None + +def _fingers_curl(deg): + d = {} + for fn in ("index", "middle", "ring", "pinky"): + for seg in ("01", "02", "03"): + d["%s_%s_r" % (fn, seg)] = (0, 0, -deg) + d["thumb_01_r"] = (0, -deg * 0.5, -18); d["thumb_02_r"] = (0, -deg * 0.5, 0) + return d + +def author_from(arm, name, base_sub, specs): + """specs: {bone: [(frame, (dx,dy,dz)deg), ...]} — deltas onto the sampled base pose. + Samples the clean base clip's pose once, holds ALL bones at it (frame 1..length), + then overlays the acting-bone deltas. No fcurve surgery (Blender-5 slotted-action safe).""" + base = _find_action(base_sub) + if base is None: + print("WARN no base clip", base_sub); return + if arm.animation_data is None: + arm.animation_data_create() + arm.animation_data.action = base + bpy.context.scene.frame_set(int(base.frame_range[0]) + 3) + bpy.context.view_layer.update() + basep = {pb.name: pb.matrix_basis.copy() for pb in arm.pose.bones} + length = max(f for keys in specs.values() for f, _ in keys) + new = bpy.data.actions.new(name); arm.animation_data.action = new + # hold every bone at the clean base pose at both ends (defines range + natural rest) + for pb in arm.pose.bones: + pb.rotation_mode = 'QUATERNION' + for f in (1, length): + pb.matrix_basis = basep[pb.name] + pb.keyframe_insert("rotation_quaternion", frame=f) + if pb.name in ("root", "pelvis"): + pb.keyframe_insert("location", frame=f) + # overlay acting-bone deltas (overwrites the hold keys at those frames) + for b, keys in specs.items(): + if b not in basep: + continue + pb = arm.pose.bones[b] + for f, delta in keys: + dm = Euler([math.radians(x) for x in delta], 'XYZ').to_matrix().to_4x4() + pb.matrix_basis = basep[b] @ dm + pb.keyframe_insert("rotation_quaternion", frame=f) + return new + +# right-arm carry pose shared by ServeCarry (standing) and WalkCarry (walking) so the tray +# sits in the same place in both. Firmer curl so the fingers wrap the tray edge. +SERVE_ARM = {"upperarm_r": (42, 0, 12), "lowerarm_r": (0, 0, -95)} +SERVE_GRIP = 27 + +def author_pinned_walk(arm, name, deltas, base_walk="Walk"): + """Walk with the given bones PINNED to (Idle-pose + delta) while legs/torso keep the walk + motion. Bakes every bone per frame. Used for one-handed WalkCarry and two-handed TubCarry.""" + idle = _find_action("Idle"); walk = _find_action(base_walk) + if idle is None or walk is None: + print("WARN pinned_walk missing base", base_walk); return + if arm.animation_data is None: + arm.animation_data_create() + arm.animation_data.action = idle + bpy.context.scene.frame_set(int(idle.frame_range[0]) + 3); bpy.context.view_layer.update() + fixed = {} + for b, delta in deltas.items(): + if b in arm.pose.bones: + dm = Euler([math.radians(x) for x in delta], 'XYZ').to_matrix().to_4x4() + fixed[b] = arm.pose.bones[b].matrix_basis.copy() @ dm + f0 = int(walk.frame_range[0]); f1 = int(walk.frame_range[1]) + new = bpy.data.actions.new(name) + for f in range(f0, f1 + 1): + arm.animation_data.action = walk + bpy.context.scene.frame_set(f); bpy.context.view_layer.update() + pose = {pb.name: pb.matrix_basis.copy() for pb in arm.pose.bones} + for b, m in fixed.items(): + pose[b] = m + arm.animation_data.action = new + for pb in arm.pose.bones: + pb.rotation_mode = 'QUATERNION'; pb.matrix_basis = pose[pb.name] + pb.keyframe_insert("rotation_quaternion", frame=f) + if pb.name in ("root", "pelvis"): + pb.keyframe_insert("location", frame=f) + return new + +def author_walkcarry(arm): + d = dict(SERVE_ARM); d.update(_fingers_curl(SERVE_GRIP)) + author_pinned_walk(arm, "WalkCarry", d) + +# two-handed bus-tub hold: upper arms stay low, elbows bend so both forearms point FORWARD at +# waist height and the hands meet in front (no outward/upward spread). +TUB_ARMS = {"upperarm_r": (16, 0, 0), "lowerarm_r": (0, 0, -80), + "upperarm_l": (16, 0, 0), "lowerarm_l": (0, 0, 80), "spine_02": (6, 0, 0)} + +def _tub_grip(): + d = dict(TUB_ARMS) + d.update(_fingers_curl(30)) + for fn in ("index", "middle", "ring", "pinky"): + for seg in ("01", "02", "03"): + d["%s_%s_l" % (fn, seg)] = (0, 0, 30) + d["thumb_01_l"] = (0, 15, 15); d["thumb_02_l"] = (0, 15, 0) + return d + +def add_action_clips(arm, role): + if role in ("server", "cook"): + # standing carry: right forearm swings forward, elbow bends, fingers curl to grip. + # Server carries a TRAY on a supporting hand (fingers wrap the edge); cook grips a + # cylindrical TOOL in a closed fist (tight curl around a handle). + if role == "server": + grip = _fingers_curl(SERVE_GRIP) + specs = {"upperarm_r": [(1, SERVE_ARM["upperarm_r"]), (48, SERVE_ARM["upperarm_r"])], + "lowerarm_r": [(1, SERVE_ARM["lowerarm_r"]), (48, SERVE_ARM["lowerarm_r"])], + "spine_02": [(1, (3, 0, 0)), (48, (3, 0, 0))]} + else: + grip = _fingers_curl(48) # closed fist around a tool handle + specs = {"upperarm_r": [(1, (52, 0, 0)), (48, (52, 0, 0))], + "lowerarm_r": [(1, (0, 0, -84)), (48, (0, 0, -84))], + "spine_02": [(1, (5, 0, 0)), (48, (5, 0, 0))]} + for b, r in grip.items(): + specs[b] = [(1, r), (48, r)] + author_from(arm, "ServeCarry" if role == "server" else "CookHold", "Idle", specs) + if role == "server": + author_walkcarry(arm) + if role == "customer": + grip = _fingers_curl(34) + # Eat: seated; right hand rises all the way TO THE MOUTH, holds, returns. Peak early + # (frame 10) and held to 30 so an overview snapshot catches hand-at-mouth, fingers curled. + eat = {"upperarm_r": [(1, (0, 0, 0)), (10, (40, 0, -40)), (30, (40, 0, -40)), (40, (0, 0, 0))], + "lowerarm_r": [(1, (0, 0, -20)), (10, (0, 0, -142)), (30, (0, 0, -142)), (40, (0, 0, -20))], + "Head": [(1, (0, 0, 0)), (10, (20, 0, 0)), (30, (20, 0, 0)), (40, (0, 0, 0))]} + for b, r in grip.items(): + eat[b] = [(1, r), (10, r), (30, r), (40, r)] + author_from(arm, "Eat", "Sitting_Idle", eat) + # Impatient: seated, forearms FOLDED/CROSSED over the chest + head cants down — + # distinct from SeekAttention's single raised open arm (avoids the outward-arm collision). + author_from(arm, "Impatient", "Sitting_Idle", { + "upperarm_l": [(1, (0, 0, 0)), (24, (30, 0, 16)), (60, (30, 0, 16))], + "lowerarm_l": [(1, (0, 0, 22)), (24, (0, 62, 96)), (60, (0, 62, 96))], + "upperarm_r": [(1, (0, 0, 0)), (24, (30, 0, -16)), (60, (30, 0, -16))], + "lowerarm_r": [(1, (0, 0, -22)), (24, (0, -62, -96)), (60, (0, -62, -96))], + "Head": [(1, (0, 0, 0)), (30, (16, 0, 6)), (60, (16, 0, 6))]}) + # SeekAttention: seated, right arm raises high and holds (hail a server) + author_from(arm, "SeekAttention", "Sitting_Idle", { + "upperarm_r": [(1, (0, 0, 0)), (16, (128, 0, -10)), (44, (128, 0, -10)), (60, (0, 0, 0))], + "lowerarm_r": [(1, (0, 0, -20)), (16, (0, 0, -8)), (44, (0, 0, -8)), (60, (0, 0, -20))]}) + if role == "host": + # CheckList / podium-idle: both hands together in front holding a clipboard, head down + # CheckList / podium-idle: left forearm up holding a clipboard at chest, head down to read; + # right arm relaxed. One-handed hold reads cleanly and avoids the both-hands-center problem. + checklist = {"upperarm_l": [(1, (30, 0, 6)), (48, (30, 0, 6))], + "lowerarm_l": [(1, (0, 0, 96)), (48, (0, 0, 96))], + "Head": [(1, (26, 0, 0)), (48, (26, 0, 0))]} + for fn in ("index", "middle", "ring", "pinky"): + for seg in ("01", "02", "03"): + checklist["%s_%s_l" % (fn, seg)] = [(1, (0, 0, 26)), (48, (0, 0, 26))] + author_from(arm, "CheckList", "Idle", checklist) + # Greet: right arm opens outward/up in welcome, slight forward lean + author_from(arm, "Greet", "Idle", { + "upperarm_r": [(1, (0, 0, 0)), (14, (58, 0, -46)), (40, (58, 0, -46)), (54, (0, 0, 0))], + "lowerarm_r": [(1, (0, 0, -10)), (14, (0, 0, -34)), (40, (0, 0, -34)), (54, (0, 0, -10))], + "spine_02": [(1, (0, 0, 0)), (14, (8, 0, 0)), (54, (0, 0, 0))]}) + # GestureDining: right arm sweeps out to the side, presenting the dining room + author_from(arm, "GestureDining", "Idle", { + "upperarm_r": [(1, (0, 0, 0)), (18, (26, 0, -74)), (46, (26, 0, -74)), (60, (0, 0, 0))], + "lowerarm_r": [(1, (0, 0, -10)), (18, (0, 0, -14)), (46, (0, 0, -14)), (60, (0, 0, -10))], + "Head": [(1, (0, 0, 0)), (18, (0, 0, -12)), (46, (0, 0, -12)), (60, (0, 0, 0))]}) + # Apologetic: both forearms rise, palms out, head tilt (delay/queue acknowledgement) + author_from(arm, "Apologetic", "Idle", { + "upperarm_l": [(1, (0, 0, 0)), (24, (24, 0, 40)), (60, (24, 0, 40))], + "lowerarm_l": [(1, (0, 0, 10)), (24, (0, 0, 78)), (60, (0, 0, 78))], + "upperarm_r": [(1, (0, 0, 0)), (24, (24, 0, -40)), (60, (24, 0, -40))], + "lowerarm_r": [(1, (0, 0, -10)), (24, (0, 0, -78)), (60, (0, 0, -78))], + "Head": [(1, (0, 0, 0)), (24, (0, 0, 14)), (60, (0, 0, 14))]}) + if role == "busser": + # TubHold (standing) + TubCarry (walking) — both hands under a bus tub in front + author_from(arm, "TubHold", "Idle", {b: [(1, v), (48, v)] for b, v in _tub_grip().items()}) + author_pinned_walk(arm, "TubCarry", _tub_grip()) + # Wipe: lean over the table, right hand flat, sweeping side-to-side on the surface + wg = _fingers_curl(14) + wipe = {"spine_02": [(1, (26, 0, 0)), (16, (26, 0, 0)), (32, (26, 0, 0))], + "upperarm_r": [(1, (82, 0, 6)), (16, (82, 0, 22)), (32, (82, 0, 6))], + "lowerarm_r": [(1, (0, 0, -34)), (16, (0, 0, -30)), (32, (0, 0, -34))], + "Head": [(1, (24, 0, 0)), (16, (24, 0, 0)), (32, (24, 0, 0))]} + for b, r in wg.items(): + wipe[b] = [(1, r), (16, r), (32, r)] + author_from(arm, "Wipe", "Idle", wipe) + +def add_lod(body, ratio): + m = body.modifiers.new("dec", 'DECIMATE'); m.ratio = ratio + bpy.context.view_layer.objects.active = body + bpy.ops.object.modifier_apply(modifier=m.name) + +def build(role, body, skin_key, hair_key, palette, ual, outdir, lod, outfit, name_override="", trouser="auto"): + arm, meshes = import_ual(ual) + body_mesh = max(meshes, key=lambda o: len(o.data.vertices)) + headb = "Head" + for m in meshes: + set_mat(m, skin_key, 0.55) + scale = {"slim": 0.94, "average": 1.0, "broad": 1.10}[body] + for m in meshes: + m.scale = (scale, scale, 1.0) + top_key = PALETTES.get(palette, "server_blue") + # ---- garment (remediation): a body-HUGGING shirt that wraps shoulders + connects to the + # arms via short skinned sleeves, instead of a wide flat floating slab with bare shoulders. + garment_key = {"cook": "chef_white", "host": "plaster_cream", "busser": "busser_green"}.get(role, top_key) + gm = k.mat("MAT_Garment", garment_key, 0.6) + clothes = [] + # torso: hip(0.155) -> chest(0.185) taper, hugs the ~0.18 body; extends down to the + # trouser waist (0.92) so there is no bare-midriff gap; smooth-shaded + clothes.append(k.cyl("torso", 0.16, 0.62, (0, 0.005, 0.86), gm, base=True, verts=24, r2=0.185)) + # rounded shoulder caps + short sleeves (skinned -> deform with the arms) + for s in (-1, 1): + clothes.append(k.sphere("shoulder", 0.095, (s * 0.185, 0.03, 1.452), gm)) + sl = k.cyl("sleeve", 0.084, 0.20, (s * 0.27, 0.055, 1.44), gm, verts=14) + sl.rotation_euler = (0, math.radians(90), 0) # lay the sleeve along the T-pose arm (X) + clothes.append(sl) + # neckline / collar ring + ck = "chef_white" if role == "cook" else "plaster_cream" + clothes.append(k.cyl("collar", 0.115, 0.055, (0, 0.01, 1.45), k.mat("MAT_Collar", ck, 0.7), base=True, verts=20, r2=0.10)) + # crew aprons / role garments over the torso + if role == "server": + clothes.append(k.box("apron", (0.24, 0.03, 0.42), (0, 0.155, 0.98), k.mat("MAT_Apron", "apron_white", 0.85), bevel=0.02)) + elif role == "cook": + clothes.append(k.box("apron", (0.24, 0.03, 0.40), (0, 0.155, 0.96), k.mat("MAT_Apron", "apron_white", 0.85), bevel=0.02)) + elif role == "host": + # formal navy VEST over the white shirt (clearly larger than the shirt so it reads as a + # vest, chest->waist) + a contrasting placket/tie accent down the front + clothes.append(k.cyl("vest", 0.188, 0.44, (0, 0.006, 0.92), k.mat("MAT_Vest", "deep_blue", 0.5), base=True, verts=24, r2=0.202)) + clothes.append(k.box("placket", (0.034, 0.02, 0.34), (0, 0.205, 1.05), k.mat("MAT_Tie", "terracotta", 0.5))) + elif role == "busser": + # utility apron: darker + longer (waist to mid-thigh), distinct from crew white + clothes.append(k.box("apron", (0.27, 0.03, 0.54), (0, 0.165, 0.84), k.mat("MAT_Apron", "charcoal", 0.85), bevel=0.02)) + # rolled-sleeve cuffs (contrasting band at the sleeve ends) + for s in (-1, 1): + cuff = k.cyl("cuff", 0.089, 0.055, (s * 0.355, 0.055, 1.44), k.mat("MAT_Cuff", "plaster_cream", 0.6), verts=14) + cuff.rotation_euler = (0, math.radians(90), 0) + clothes.append(cuff) + # trousers per leg (skinned; bend with knees) — span ankle(0.1) to hip(0.92) + tkey = trouser if trouser != "auto" else ("charcoal" if role != "customer" else "wood_oak") + for s in (-1, 1): + clothes.append(k.cyl("trouser", 0.095, 0.82, (s * 0.11, 0, 0.1), k.mat("MAT_Trouser", tkey, 0.8), base=True, verts=12, r2=0.075)) + # shoes + for s in (-1, 1): + clothes.append(k.box("shoe", (0.13, 0.26, 0.09), (s * 0.11, 0.06, 0.045), k.mat("MAT_Shoe", "black_iron", 0.5), bevel=0.02)) + for c in clothes: + skin(c, arm) + # face: eyes + nose + mouth (rigid on head) + for s in (-1, 1): + e = k.sphere("eye", 0.022, (s * 0.05, 0.125, 1.66), k.mat("MAT_Eye", "charcoal", 0.4)); bone_parent(e, arm, headb) + nose = k.box("nose", (0.03, 0.05, 0.04), (0, 0.14, 1.63), k.mat("MAT_Skin_" + str(skin_key), skin_key, 0.55)); bone_parent(nose, arm, headb) + mw = 0.075 if outfit == "b" else 0.055 + mouth = k.box("mouth", (mw, 0.02, 0.015), (0, 0.13, 1.585), k.mat("MAT_Mouth", "terracotta", 0.6)); bone_parent(mouth, arm, headb) + if outfit == "b": # face variant B: visible brows + for s in (-1, 1): + br = k.box("brow", (0.045, 0.02, 0.014), (s * 0.05, 0.135, 1.70), k.mat("MAT_Hair", hair_key, 0.85)); bone_parent(br, arm, headb) + # hair / headwear + if role == "cook": + band = k.cyl("toqueband", 0.135, 0.09, (0, 0, 1.74), k.mat("MAT_Toque", "chef_white", 0.8), verts=20); bone_parent(band, arm, headb) + puff = k.sphere("toquepuff", 0.15, (0, 0, 1.84), k.mat("MAT_Toque", "chef_white", 0.8), squash=0.7); bone_parent(puff, arm, headb) + else: + hair = k.sphere("hair", 0.15, (0, -0.015, 1.70), k.mat("MAT_Hair", hair_key, 0.85), squash=0.72); bone_parent(hair, arm, headb) + if role == "customer" and hair_key == "hair_light": + cap = k.cyl("cap", 0.15, 0.07, (0, 0.02, 1.75), k.mat("MAT_Cap", top_key, 0.7), verts=16); bone_parent(cap, arm, headb) + # author grip + seated action clips + add_action_clips(arm, role) + # optional LOD + suffix = "" + if lod > 0: + add_lod(body_mesh, lod); suffix = "_LOD1" + bpy.ops.object.select_all(action='SELECT') + name = name_override if name_override else "CHAR_Hybrid_%s_%s%s" % (role.capitalize(), body.capitalize(), suffix) + out = os.path.join(outdir, name + ".glb"); os.makedirs(outdir, exist_ok=True) + bpy.ops.export_scene.gltf(filepath=out, export_format='GLB', export_animations=True, + export_animation_mode='ACTIONS', export_skins=True) + print("HYBRID", name, "-> ", os.path.basename(out)) + +if __name__ == "__main__": + ual = _arg("--ual", "visual/RestaurantVisualSandbox/placeholders/_assetlab/UAL1_Standard.glb") + outdir = _arg("--out", "visual/RestaurantVisualSandbox/assets/generated") + build(_arg("--role", "server"), _arg("--body", "average"), _arg("--skin", "skin_1"), + _arg("--hair", "hair_dark"), _arg("--palette", "blue"), ual, outdir, + float(_arg("--lod", "0")), _arg("--outfit", "a"), _arg("--name", ""), _arg("--trouser", "auto")) + print("DONE") diff --git a/tools/blender/build_rig.py b/tools/blender/build_rig.py new file mode 100644 index 0000000..28d25c6 --- /dev/null +++ b/tools/blender/build_rig.py @@ -0,0 +1,204 @@ +"""SUPERSEDED procedural foundation — retained for evidence, NOT production continuation. +Replaced by the Quaternius CC0 hybrid (build_hybrid.py) per the Loop-4 gate. Loops 1-3 kept as pipeline evidence. + +V0 riggable character base — shared skeleton, skinned continuous mesh, animation clips. +blender --background --python tools/blender/build_rig.py -- --out DIR [--clips Idle,Walk,...] [--profiles slim,average,broad] + +Deformation strategy: build a CONTINUOUS body from overlapping tapered cylinders, apply a +Subdivision Surface level so auto-weights land on smooth geometry (far better joints than +disjoint boxes), then a full humanoid skeleton + automatic weights. Clip set grows per loop. +""" +import bpy, sys, os, math +from mathutils import Vector + +def _arg(flag, default): + a = sys.argv + if "--" in a: + r = a[a.index("--") + 1:] + for i, x in enumerate(r): + if x == flag and i + 1 < len(r): + return r[i + 1] + if x.startswith(flag + "="): + return x.split("=", 1)[1] + return default + +def reset(): + bpy.ops.wm.read_factory_settings(use_empty=True) + +def _capsule(name, r_top, r_bot, z0, z1, x=0.0, y=0.0): + h = z1 - z0 + bpy.ops.mesh.primitive_cone_add(radius1=r_bot, radius2=r_top, depth=h, vertices=16, + location=(x, y, (z0 + z1) * 0.5)) + o = bpy.context.active_object; o.name = name + return o + +def build_mesh(profile): + w = {"slim": 0.85, "average": 1.0, "broad": 1.18}[profile] + parts = [] + # torso pelvis(0.9)->chest(1.45), tapered + parts.append(_capsule("torso", 0.20 * w, 0.17 * w, 0.86, 1.46)) + parts.append(_capsule("chest", 0.19 * w, 0.21 * w, 1.30, 1.48)) + # neck + head + parts.append(_capsule("neck", 0.06, 0.075, 1.46, 1.56)) + bpy.ops.mesh.primitive_uv_sphere_add(radius=0.135, segments=18, ring_count=12, location=(0, 0, 1.66)) + parts.append(bpy.context.active_object) + # arms (upper+lower+hand) overlapping at joints + for s in (-1, 1): + parts.append(_capsule("uarm", 0.062 * w, 0.055, 1.10, 1.44, x=s * 0.21)) + parts.append(_capsule("larm", 0.05, 0.045, 0.84, 1.14, x=s * 0.24)) + bpy.ops.mesh.primitive_uv_sphere_add(radius=0.06, location=(s * 0.245, 0, 0.80)); parts.append(bpy.context.active_object) + # legs (upper+lower+foot) + for s in (-1, 1): + parts.append(_capsule("uleg", 0.085 * w, 0.07, 0.48, 0.92, x=s * 0.11)) + parts.append(_capsule("lleg", 0.062, 0.05, 0.06, 0.52, x=s * 0.11)) + bpy.ops.mesh.primitive_cube_add(size=1, location=(s * 0.11, 0.05, 0.03)); f = bpy.context.active_object + f.scale = (0.11, 0.24, 0.07); parts.append(f) + bpy.ops.object.select_all(action='DESELECT') + for p in parts: + p.select_set(True) + bpy.context.view_layer.objects.active = parts[0] + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + bpy.ops.object.join() + m = bpy.context.active_object; m.name = "CHAR_Mesh" + # smooth for better deformation + sub = m.modifiers.new("sub", 'SUBSURF'); sub.levels = 1; sub.render_levels = 1 + bpy.context.view_layer.objects.active = m + bpy.ops.object.modifier_apply(modifier=sub.name) + bpy.ops.object.shade_smooth() + return m + +def build_rig(): + bpy.ops.object.armature_add(enter_editmode=True, location=(0, 0, 0)) + arm = bpy.context.active_object; arm.name = "Rig" + eb = arm.data.edit_bones + for b in list(eb): + eb.remove(b) + def bone(name, head, tail, parent=None): + b = eb.new(name); b.head = Vector(head); b.tail = Vector(tail) + if parent: + b.parent = parent + return b + pelvis = bone("pelvis", (0, 0, 0.9), (0, 0, 1.06)) + spine = bone("spine", (0, 0, 1.06), (0, 0, 1.3), pelvis) + chest = bone("chest", (0, 0, 1.3), (0, 0, 1.46), spine) + neck = bone("neck", (0, 0, 1.46), (0, 0, 1.56), chest) + bone("head", (0, 0, 1.56), (0, 0, 1.78), neck) + for s, side in ((-1, "L"), (1, "R")): + cl = bone("clavicle_" + side, (0, 0, 1.42), (s * 0.16, 0, 1.42), chest) + ua = bone("upperarm_" + side, (s * 0.18, 0, 1.42), (s * 0.24, 0, 1.12), cl) + la = bone("lowerarm_" + side, (s * 0.24, 0, 1.12), (s * 0.25, 0, 0.86), ua) + bone("hand_" + side, (s * 0.25, 0, 0.86), (s * 0.25, 0, 0.74), la) + ul = bone("upperleg_" + side, (s * 0.11, 0, 0.9), (s * 0.11, 0, 0.5), pelvis) + ll = bone("lowerleg_" + side, (s * 0.11, 0, 0.5), (s * 0.11, 0, 0.08), ul) + bone("foot_" + side, (s * 0.11, 0, 0.08), (s * 0.11, 0.18, 0.03), ll) + bpy.ops.object.mode_set(mode='OBJECT') + return arm + +def skin(mesh, arm): + bpy.ops.object.select_all(action='DESELECT') + mesh.select_set(True); arm.select_set(True) + bpy.context.view_layer.objects.active = arm + bpy.ops.object.parent_set(type='ARMATURE_AUTO') + +# ---- clip library (grows per loop) ---------------------------------------- +def _key(arm, bn, f, rot=None, loc=None): + pb = arm.pose.bones[bn]; pb.rotation_mode = 'XYZ' + if rot: + pb.rotation_euler = [math.radians(a) for a in rot]; pb.keyframe_insert("rotation_euler", frame=f) + if loc: + pb.location = loc; pb.keyframe_insert("location", frame=f) + +def _action(arm, name): + if arm.animation_data is None: + arm.animation_data_create() + act = bpy.data.actions.new(name); arm.animation_data.action = act; return act + +def clip_idle(arm): + _action(arm, "Idle") + for f, a in ((1, 0), (36, 2.5), (72, 0)): + _key(arm, "spine", f, rot=(a, 0, 0)); _key(arm, "chest", f, rot=(a * 0.5, 0, 0)) + _key(arm, "upperarm_L", f, rot=(0, 0, a * 0.6)); _key(arm, "upperarm_R", f, rot=(0, 0, -a * 0.6)) + +def clip_walk(arm): + # in-place walk, 24-frame loop; Godot translates the node along the path + _action(arm, "Walk") + seq = [(1, 28, 0), (7, 0, 22), (13, -28, 0), (19, 0, 22), (25, 28, 0)] + for f, sw, knee in seq: + _key(arm, "upperleg_L", f, rot=(sw, 0, 0)) + _key(arm, "upperleg_R", f, rot=(-sw, 0, 0)) + _key(arm, "lowerleg_L", f, rot=(knee if sw < 0 else 4, 0, 0)) + _key(arm, "lowerleg_R", f, rot=(knee if sw > 0 else 4, 0, 0)) + _key(arm, "upperarm_L", f, rot=(-sw * 0.7, 0, 8)) + _key(arm, "upperarm_R", f, rot=(sw * 0.7, 0, -8)) + _key(arm, "spine", f, rot=(3, 0, 0)) + _key(arm, "pelvis", f, loc=(0, 0, 0.02 if f in (7, 19) else 0.0)) + +def clip_walk_stop(arm): + # short settle from stride to neutral (start/stop transition) + _action(arm, "WalkStop") + _key(arm, "upperleg_L", 1, rot=(20, 0, 0)); _key(arm, "upperleg_R", 1, rot=(-20, 0, 0)) + _key(arm, "upperleg_L", 10, rot=(0, 0, 0)); _key(arm, "upperleg_R", 10, rot=(0, 0, 0)) + _key(arm, "upperarm_L", 1, rot=(-14, 0, 8)); _key(arm, "upperarm_R", 1, rot=(14, 0, -8)) + _key(arm, "upperarm_L", 10, rot=(0, 0, 6)); _key(arm, "upperarm_R", 10, rot=(0, 0, -6)) + +def clip_carry(arm): + # both forearms up/forward holding a tray, slight bob + _action(arm, "Carry") + for f, b in ((1, 0), (30, 3), (60, 0)): + _key(arm, "upperarm_L", f, rot=(-55, 0, 12)); _key(arm, "upperarm_R", f, rot=(-55, 0, -12)) + _key(arm, "lowerarm_L", f, rot=(-70, 0, 0)); _key(arm, "lowerarm_R", f, rot=(-70, 0, 0)) + _key(arm, "spine", f, rot=(b, 0, 0)) + +def clip_cook(arm): + # lean over the station, arms working forward with a repetitive motion + _action(arm, "Cook") + for f, work in ((1, -35), (15, -55), (30, -35)): + _key(arm, "spine", f, rot=(16, 0, 0)); _key(arm, "chest", f, rot=(8, 0, 0)) + _key(arm, "upperarm_L", f, rot=(work, 0, 14)); _key(arm, "upperarm_R", f, rot=(work + 8, 0, -14)) + _key(arm, "lowerarm_L", f, rot=(-40, 0, 0)); _key(arm, "lowerarm_R", f, rot=(-50, 0, 0)) + +def clip_sit(arm): + # hips down to seat height, thighs forward, shins down, seated idle bob + _action(arm, "Sit") + for f, b in ((1, 0), (40, 2), (80, 0)): + _key(arm, "pelvis", f, loc=(0, 0, -0.42)) + _key(arm, "upperleg_L", f, rot=(86, 0, 0)); _key(arm, "upperleg_R", f, rot=(86, 0, 0)) + _key(arm, "lowerleg_L", f, rot=(-84, 0, 0)); _key(arm, "lowerleg_R", f, rot=(-84, 0, 0)) + _key(arm, "spine", f, rot=(b, 0, 0)) + _key(arm, "upperarm_L", f, rot=(-10, 0, 6)); _key(arm, "upperarm_R", f, rot=(-10, 0, -6)) + +def clip_wipe(arm): + # bend over a table, one arm wiping in an arc + _action(arm, "Wipe") + for f, sweep in ((1, 10), (15, -20), (30, 10)): + _key(arm, "spine", f, rot=(34, 0, 0)); _key(arm, "chest", f, rot=(12, 0, 0)) + _key(arm, "upperarm_R", f, rot=(-60, 0, sweep)); _key(arm, "lowerarm_R", f, rot=(-40, 0, 0)) + _key(arm, "upperarm_L", f, rot=(-30, 0, 10)) + +CLIPS = {"Idle": clip_idle, "Walk": clip_walk, "WalkStop": clip_walk_stop, + "Carry": clip_carry, "Cook": clip_cook, "Sit": clip_sit, "Wipe": clip_wipe} + +def build_profile(profile, clip_names, outdir): + reset() + mesh = build_mesh(profile) + arm = build_rig() + skin(mesh, arm) + bpy.ops.object.mode_set(mode='POSE') + for c in clip_names: + if c in CLIPS: + CLIPS[c](arm) + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='SELECT') + out = os.path.join(outdir, "CHAR_Base_%s.glb" % profile.capitalize()) + os.makedirs(outdir, exist_ok=True) + bpy.ops.export_scene.gltf(filepath=out, export_format='GLB', export_animations=True, + export_animation_mode='ACTIONS', export_skins=True) + print("RIG", profile, "clips=", clip_names, "->", os.path.basename(out)) + +if __name__ == "__main__": + outdir = _arg("--out", "assets/generated") + clips = _arg("--clips", "Idle").split(",") + profiles = _arg("--profiles", "slim,average,broad").split(",") + for p in profiles: + build_profile(p, clips, outdir) + print("DONE profiles=", profiles) diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Base_Average.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Base_Average.glb new file mode 100644 index 0000000..32837c1 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Base_Average.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Base_Broad.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Base_Broad.glb new file mode 100644 index 0000000..78ec093 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Base_Broad.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Base_Slim.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Base_Slim.glb new file mode 100644 index 0000000..372a2f1 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Base_Slim.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Average.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Average.glb new file mode 100644 index 0000000..671b3db Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Average.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Average.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Average.glb.import new file mode 100644 index 0000000..164f7ac --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Average.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://cpyeokggiyde3" +path="res://.godot/imported/CHAR_Hybrid_Busser_Average.glb-2dd54c151e87308ebcd736754ce97d53.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Busser_Average.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Busser_Average.glb-2dd54c151e87308ebcd736754ce97d53.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Broad.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Broad.glb new file mode 100644 index 0000000..7386a46 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Broad.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Broad.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Broad.glb.import new file mode 100644 index 0000000..d8c6a73 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Broad.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bcbbhtwiitdjh" +path="res://.godot/imported/CHAR_Hybrid_Busser_Broad.glb-2555bcae8c84b09927bdeea4cf8a3437.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Busser_Broad.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Busser_Broad.glb-2555bcae8c84b09927bdeea4cf8a3437.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Slim.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Slim.glb new file mode 100644 index 0000000..434018a Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Slim.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Slim.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Slim.glb.import new file mode 100644 index 0000000..3efece1 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Busser_Slim.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bie25kkknyu73" +path="res://.godot/imported/CHAR_Hybrid_Busser_Slim.glb-34cd22942c0b41cb3e8ebe9d7cdc574c.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Busser_Slim.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Busser_Slim.glb-34cd22942c0b41cb3e8ebe9d7cdc574c.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Average.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Average.glb new file mode 100644 index 0000000..2306c60 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Average.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Average.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Average.glb.import new file mode 100644 index 0000000..81200a8 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Average.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://dfvimyyny2dmw" +path="res://.godot/imported/CHAR_Hybrid_Cook_Average.glb-b84d5bfc63ca489c31a5681a9b282af9.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Cook_Average.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Cook_Average.glb-b84d5bfc63ca489c31a5681a9b282af9.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Broad.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Broad.glb new file mode 100644 index 0000000..005a76f Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Broad.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Broad.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Broad.glb.import new file mode 100644 index 0000000..e58ef97 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Cook_Broad.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://cgfvfrqu4wnwh" +path="res://.godot/imported/CHAR_Hybrid_Cook_Broad.glb-0809f3fa531c0208d1545936106296f2.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Cook_Broad.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Cook_Broad.glb-0809f3fa531c0208d1545936106296f2.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1.glb new file mode 100644 index 0000000..e798ce0 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1.glb.import new file mode 100644 index 0000000..6ac606c --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://c72ox8rxwjln0" +path="res://.godot/imported/CHAR_Hybrid_Customer_V1.glb-284025fafb746324c334f6199fdda368.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Customer_V1.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Customer_V1.glb-284025fafb746324c334f6199fdda368.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1_LOD1.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1_LOD1.glb new file mode 100644 index 0000000..d6538e5 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1_LOD1.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1_LOD1.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1_LOD1.glb.import new file mode 100644 index 0000000..d34f510 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V1_LOD1.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bg0w8otfat1i3" +path="res://.godot/imported/CHAR_Hybrid_Customer_V1_LOD1.glb-008a286055453155aebdef97f660d803.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Customer_V1_LOD1.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Customer_V1_LOD1.glb-008a286055453155aebdef97f660d803.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V2.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V2.glb new file mode 100644 index 0000000..ee80b01 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V2.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V2.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V2.glb.import new file mode 100644 index 0000000..ed22535 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V2.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bgsjsedaoenoy" +path="res://.godot/imported/CHAR_Hybrid_Customer_V2.glb-5ab434768dde3f289928e877f4e1ee0a.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Customer_V2.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Customer_V2.glb-5ab434768dde3f289928e877f4e1ee0a.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V3.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V3.glb new file mode 100644 index 0000000..5b9733a Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V3.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V3.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V3.glb.import new file mode 100644 index 0000000..e14d2d0 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V3.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://dai2ptfkuxck4" +path="res://.godot/imported/CHAR_Hybrid_Customer_V3.glb-3464753fa3c4cd3f316ce62e6c4b4199.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Customer_V3.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Customer_V3.glb-3464753fa3c4cd3f316ce62e6c4b4199.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V4.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V4.glb new file mode 100644 index 0000000..1967f19 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V4.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V4.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V4.glb.import new file mode 100644 index 0000000..5907be8 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V4.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bocdw653ijs1n" +path="res://.godot/imported/CHAR_Hybrid_Customer_V4.glb-34c1af3f06bd8d9624be39e7e1965226.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Customer_V4.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Customer_V4.glb-34c1af3f06bd8d9624be39e7e1965226.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V5.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V5.glb new file mode 100644 index 0000000..36803d8 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V5.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V5.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V5.glb.import new file mode 100644 index 0000000..89518f9 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V5.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://mrq8cs1plke0" +path="res://.godot/imported/CHAR_Hybrid_Customer_V5.glb-5217bb962e4b82a4cf3d2ee312ec7c6d.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Customer_V5.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Customer_V5.glb-5217bb962e4b82a4cf3d2ee312ec7c6d.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V6.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V6.glb new file mode 100644 index 0000000..f614998 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V6.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V6.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V6.glb.import new file mode 100644 index 0000000..2062b6a --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Customer_V6.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://dic5alilgypov" +path="res://.godot/imported/CHAR_Hybrid_Customer_V6.glb-a4c85012445fe0b352f2d679e01a8500.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Customer_V6.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Customer_V6.glb-a4c85012445fe0b352f2d679e01a8500.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Average.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Average.glb new file mode 100644 index 0000000..e5ff719 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Average.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Average.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Average.glb.import new file mode 100644 index 0000000..6f1c3fe --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Average.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://21b8l3plwhul" +path="res://.godot/imported/CHAR_Hybrid_Host_Average.glb-dda46746cf366d598c20a20507956822.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Host_Average.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Host_Average.glb-dda46746cf366d598c20a20507956822.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Broad.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Broad.glb new file mode 100644 index 0000000..aa598dc Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Broad.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Broad.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Broad.glb.import new file mode 100644 index 0000000..d878c08 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Broad.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bwuyno16iem7y" +path="res://.godot/imported/CHAR_Hybrid_Host_Broad.glb-9181d1fe9953225ed855fb4b1625a756.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Host_Broad.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Host_Broad.glb-9181d1fe9953225ed855fb4b1625a756.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Slim.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Slim.glb new file mode 100644 index 0000000..f034224 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Slim.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Slim.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Slim.glb.import new file mode 100644 index 0000000..354bba7 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Host_Slim.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bx476rkxt1xtm" +path="res://.godot/imported/CHAR_Hybrid_Host_Slim.glb-57efe2eb361834345f0f4ca0a03020fd.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Host_Slim.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Host_Slim.glb-57efe2eb361834345f0f4ca0a03020fd.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Average.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Average.glb new file mode 100644 index 0000000..41f3a23 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Average.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Average.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Average.glb.import new file mode 100644 index 0000000..87f599c --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Average.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://c24lwedxucdrd" +path="res://.godot/imported/CHAR_Hybrid_Server_Average.glb-dae2b7e760c92f5507dfb1324c791af3.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Server_Average.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Server_Average.glb-dae2b7e760c92f5507dfb1324c791af3.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 diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Slim.glb b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Slim.glb new file mode 100644 index 0000000..cf8dee8 Binary files /dev/null and b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Slim.glb differ diff --git a/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Slim.glb.import b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Slim.glb.import new file mode 100644 index 0000000..61a03d6 --- /dev/null +++ b/visual/RestaurantVisualSandbox/assets/generated/CHAR_Hybrid_Server_Slim.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://cw16o7xsjvtbr" +path="res://.godot/imported/CHAR_Hybrid_Server_Slim.glb-df4fb6cd927fa40a375206a727da1131.scn" + +[deps] + +source_file="res://assets/generated/CHAR_Hybrid_Server_Slim.glb" +dest_files=["res://.godot/imported/CHAR_Hybrid_Server_Slim.glb-df4fb6cd927fa40a375206a727da1131.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 diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/AnimDemo.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/AnimDemo.tscn new file mode 100644 index 0000000..c747215 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/AnimDemo.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bv0animdemo01"] +[ext_resource type="Script" path="res://scripts/anim_demo.gd" id="1"] +[node name="AnimDemo" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/AnimPerf.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/AnimPerf.tscn new file mode 100644 index 0000000..1837ab5 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/AnimPerf.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bv0animperf01"] +[ext_resource type="Script" path="res://scripts/anim_perf.gd" id="1"] +[node name="AnimPerf" type="Node"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/AnimatedService.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/AnimatedService.tscn new file mode 100644 index 0000000..929faf8 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/AnimatedService.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bv0animsvc01"] +[ext_resource type="Script" path="res://scripts/animated_service.gd" id="1"] +[node name="AnimatedService" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/CarryRec.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/CarryRec.tscn new file mode 100644 index 0000000..797d216 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/CarryRec.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bcarryrec05"] +[ext_resource type="Script" path="res://scripts/carry_rec.gd" id="1"] +[node name="CarryRec" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/HostTurnover.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/HostTurnover.tscn new file mode 100644 index 0000000..97dd092 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/HostTurnover.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bhostturnover06"] +[ext_resource type="Script" path="res://scripts/host_turnover.gd" id="1"] +[node name="HostTurnover" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/HybridPerf.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/HybridPerf.tscn new file mode 100644 index 0000000..6132cf9 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/HybridPerf.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bv0hp01"] +[ext_resource type="Script" path="res://scripts/hybrid_perf.gd" id="1"] +[node name="HybridPerf" type="Node"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/HybridReview.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/HybridReview.tscn new file mode 100644 index 0000000..2d7f133 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/HybridReview.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bv0hybrev01"] +[ext_resource type="Script" path="res://scripts/hybrid_review.gd" id="1"] +[node name="HybridReview" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/HybridRuntime.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/HybridRuntime.tscn new file mode 100644 index 0000000..702722a --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/HybridRuntime.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bv0hybrun01"] +[ext_resource type="Script" path="res://scripts/hybrid_runtime.gd" id="1"] +[node name="HybridRuntime" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/MiniPerf.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/MiniPerf.tscn new file mode 100644 index 0000000..55a25ae --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/MiniPerf.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bminiperf05"] +[ext_resource type="Script" path="res://scripts/mini_perf.gd" id="1"] +[node name="MiniPerf" type="Node"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/MiniService.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/MiniService.tscn new file mode 100644 index 0000000..ce1b806 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/MiniService.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bminiservice05"] +[ext_resource type="Script" path="res://scripts/mini_service.gd" id="1"] +[node name="MiniService" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/PropSocket.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/PropSocket.tscn new file mode 100644 index 0000000..375f2ed --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/PropSocket.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bv0propsock01"] +[ext_resource type="Script" path="res://scripts/prop_socket.gd" id="1"] +[node name="PropSocket" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/RigReview.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/RigReview.tscn new file mode 100644 index 0000000..f2c5fdd --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/RigReview.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://bv0rigreview01"] +[ext_resource type="Script" path="res://scripts/rig_review.gd" id="1"] +[node name="RigReview" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scenes/diagnostics/TableStates.tscn b/visual/RestaurantVisualSandbox/scenes/diagnostics/TableStates.tscn new file mode 100644 index 0000000..e0629ff --- /dev/null +++ b/visual/RestaurantVisualSandbox/scenes/diagnostics/TableStates.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3 uid="uid://btablestates06"] +[ext_resource type="Script" path="res://scripts/table_states.gd" id="1"] +[node name="TableStates" type="Node3D"] +script = ExtResource("1") diff --git a/visual/RestaurantVisualSandbox/scripts/anim_demo.gd b/visual/RestaurantVisualSandbox/scripts/anim_demo.gd new file mode 100644 index 0000000..ea1deb1 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/anim_demo.gd @@ -0,0 +1,73 @@ +extends Node3D +## Locomotion demo: a rigged character walks a waypoint loop at the management camera, +## facing movement, feet on the floor, playing the Walk clip. VisualOnly. +## Args: --clip=Walk --speed=1.4 --rec_orbit=0 + +var actor: Node3D +var ap: AnimationPlayer +var path := [Vector3(-2.5, 0, -1.5), Vector3(2.5, 0, -1.5), Vector3(2.5, 0, 1.5), Vector3(-2.5, 0, 1.5)] +var wp := 0 +var speed := 1.4 +var rig: CameraRig + +func _ready() -> void: + _warm() + var floor := Placeholder.floor_tile(Vector2(9, 7), Color("2f2620")) + add_child(floor) + _grid() + var packed = load("res://assets/generated/CHAR_Base_Average.glb") + actor = packed.instantiate() + actor.position = path[0] + add_child(actor) + ap = _find(actor, "AnimationPlayer") + var clip := _arg("clip", "Walk") + speed = _arg("speed", "1.4").to_float() + if ap and ap.has_animation(clip): + var a := ap.get_animation(clip) + a.loop_mode = Animation.LOOP_LINEAR + ap.play(clip) + rig = CameraRig.new(); add_child(rig); rig.interactive = false + rig.set_mode(CameraRig.Mode.ORBIT_B) + rig.frame(Vector3(0, 0.6, 0), 24.0, 24.0, 8.5) + +func _process(dt: float) -> void: + if not actor: + return + var target: Vector3 = path[wp] + var to := target - actor.position + to.y = 0.0 + if to.length() < 0.15: + wp = (wp + 1) % path.size() + else: + var dir := to.normalized() + actor.position += dir * speed * dt + actor.position.y = 0.0 + actor.rotation.y = atan2(dir.x, dir.z) + +func _grid() -> void: + var mat := StandardMaterial3D.new(); mat.albedo_color = Color("3a2f26"); mat.flags_unshaded = true + for i in range(-4, 5): + var m := MeshInstance3D.new(); var b := BoxMesh.new(); b.size = Vector3(0.02, 0.001, 7) + m.mesh = b; m.material_override = mat; m.position = Vector3(i, 0.01, 0); add_child(m) + +func _warm() -> void: + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("1e222a") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR + e.ambient_light_color = Color("b4bccb"); e.ambient_light_energy = 0.6 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; e.ssao_enabled = true + we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-45, -40, 0) + sun.light_energy = 1.3; sun.shadow_enabled = true; add_child(sun) + +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null + +func _arg(k: String, d: String) -> String: + for a in OS.get_cmdline_user_args(): + if a.begins_with("--%s=" % k): return a.split("=", 1)[1] + return d diff --git a/visual/RestaurantVisualSandbox/scripts/anim_demo.gd.uid b/visual/RestaurantVisualSandbox/scripts/anim_demo.gd.uid new file mode 100644 index 0000000..cc2cb16 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/anim_demo.gd.uid @@ -0,0 +1 @@ +uid://bwkredbwrgy2s diff --git a/visual/RestaurantVisualSandbox/scripts/anim_perf.gd b/visual/RestaurantVisualSandbox/scripts/anim_perf.gd new file mode 100644 index 0000000..725e6bb --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/anim_perf.gd @@ -0,0 +1,44 @@ +extends Node +## GPU-timed perf probe for the animated scene (fixes the PR#6 review's CPU-loop-timing issue). +## Reports measured CPU + GPU render time (RenderingServer), not 1/process_delta. +const SETTLE := 30 +const SAMPLE := 120 +func _ready() -> void: + DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED) + Engine.max_fps = 0 + get_window().size = Vector2i(1920, 1080) + var out := "/tmp/anim_perf.csv" + for a in OS.get_cmdline_user_args(): + if a.begins_with("--out="): out = a.substr(6) + var vp := get_viewport().get_viewport_rid() + RenderingServer.viewport_set_measure_render_time(vp, true) + var packed: PackedScene = load("res://scenes/diagnostics/AnimatedService.tscn") + var rows := ["scenario,skeletons,anim_players,mesh_instances,draw_calls,primitives,gpu_ms_avg,gpu_ms_worst,cpu_render_ms_avg,vram_mb"] + for sc in [0, 1]: + var inst := packed.instantiate() + # set scenario via arg is per-process; instead re-instance reads default. Use property. + inst.set("scenario", sc) + add_child(inst) + for i in SETTLE: await get_tree().process_frame + var gsum := 0.0; var gworst := 0.0; var csum := 0.0 + for i in SAMPLE: + await get_tree().process_frame + var g := RenderingServer.viewport_get_measured_render_time_gpu(vp) + var c := RenderingServer.viewport_get_measured_render_time_cpu(vp) + gsum += g; gworst = maxf(gworst, g); csum += c + var draw := RenderingServer.get_rendering_info(RenderingServer.RENDERING_INFO_TOTAL_DRAW_CALLS_IN_FRAME) + var prims := RenderingServer.get_rendering_info(RenderingServer.RENDERING_INFO_TOTAL_PRIMITIVES_IN_FRAME) + var vmem := RenderingServer.get_rendering_info(RenderingServer.RENDERING_INFO_VIDEO_MEM_USED) + var sk := _count(inst, "Skeleton3D"); var apn := _count(inst, "AnimationPlayer"); var mi := _count(inst, "MeshInstance3D") + rows.append("%s,%d,%d,%d,%d,%d,%.2f,%.2f,%.2f,%.1f" % [ + ("healthy" if sc == 0 else "kitchen"), sk, apn, mi, draw, prims, + gsum / SAMPLE, gworst, csum / SAMPLE, vmem / 1048576.0]) + print("[aperf] ", rows[-1]) + inst.queue_free(); await get_tree().process_frame + var f := FileAccess.open(out, FileAccess.WRITE) + if f: f.store_string("\n".join(rows) + "\n"); f.close(); print("[aperf] wrote ", out) + get_tree().quit() +func _count(n, cls) -> int: + var c := 1 if n.get_class() == cls else 0 + for ch in n.get_children(): c += _count(ch, cls) + return c diff --git a/visual/RestaurantVisualSandbox/scripts/anim_perf.gd.uid b/visual/RestaurantVisualSandbox/scripts/anim_perf.gd.uid new file mode 100644 index 0000000..e5d0134 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/anim_perf.gd.uid @@ -0,0 +1 @@ +uid://1hhu1l3p4gqd diff --git a/visual/RestaurantVisualSandbox/scripts/animated_service.gd b/visual/RestaurantVisualSandbox/scripts/animated_service.gd new file mode 100644 index 0000000..27a77d6 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/animated_service.gd @@ -0,0 +1,142 @@ +extends Node3D +## Loop 3 integration: animated rigged actors in the restaurant, playing role clips per a +## Mock snapshot, so the service reads through MOTION. Healthy vs kitchen-bottleneck. +## VisualOnly; owns no gameplay truth. Args: --scenario=1 --rec_orbit=0 + +const GEN := "res://assets/generated/%s.glb" +var restaurant: CorrectedRestaurant +var rig: CameraRig +var scenario := 1 +var _walkers := [] # {node, path, wp, speed} + +const ROLE := { + "customer": Color("d9c7a3"), "cook": Color("f2f2ee"), "server": Color("3a86ff"), + "host": Color("2ec4b6"), "busser": Color("52b788"), +} + +func _ready() -> void: + scenario = int(_arg("scenario", "1")) + _warm() + restaurant = CorrectedRestaurant.new(); add_child(restaurant); restaurant.build() + rig = CameraRig.new(); add_child(rig); rig.interactive = false + rig.set_mode(CameraRig.Mode.ORBIT_B) + rig.frame(Vector3(-0.6, 0.7, 0.4), 26.0, 33.0, 12.8) + _populate(MockService.snapshot(scenario, MockService.peak_time(scenario))) + +func _char(clip: String, pos: Vector3, yaw_deg: float, role: String, prop := "") -> Node3D: + var packed = load(GEN % "CHAR_Base_Average") + var a = packed.instantiate() + a.position = pos; a.rotation.y = deg_to_rad(yaw_deg) + add_child(a) + _tint(a, ROLE.get(role, Color.WHITE)) + var ap := _find(a, "AnimationPlayer") as AnimationPlayer + if ap and ap.has_animation(clip): + ap.get_animation(clip).loop_mode = Animation.LOOP_LINEAR + ap.play(clip); ap.seek(randf_from(pos) * 2.0, true) + if prop == "toque": + var t := _box(Vector3(0.28, 0.22, 0.28), Color.WHITE); t.position = Vector3(0, 1.9, 0); a.add_child(t) + elif prop == "tray": + var tr := _box(Vector3(0.34, 0.04, 0.28), Color("cfd8e3")); tr.position = Vector3(0, 1.15, -0.34); a.add_child(tr) + elif prop == "clipboard": + var c := _box(Vector3(0.18, 0.02, 0.24), Color("2b3a44")); c.position = Vector3(0.18, 1.15, -0.2); a.add_child(c) + return a + +func _populate(snap: Dictionary) -> void: + var S := V0.TableState + var states: Dictionary = snap["tables"] + # seated customers (Sit) at occupied tables + for t in restaurant.tables: + var st: int = states.get(t["id"], S.AVAILABLE) + if st in [S.OCCUPIED, S.WAITING_ORDER, S.WAITING_FOOD, S.EATING, S.NEEDS_PAYMENT]: + var seats: Array = t["seats_world"] + for seat in seats: + var yaw := rad_to_deg(atan2(t["pos"].x - seat["pos"].x, t["pos"].z - seat["pos"].z)) + _char("Sit", seat["pos"], yaw, "customer") + # cooks (Cook) at active stations; a cluster at a congested grill + var arr: Array = snap["stations"] + for i in restaurant.stations.size(): + var s: Dictionary = restaurant.stations[i] + var sstate: int = arr[i][0]; var q: int = arr[i][1] + if q > 0 or sstate in [V0.StationState.NORMAL, V0.StationState.CONGESTED, V0.StationState.FOOD_READY]: + _char("Cook", s["pos"] + Vector3(0, 0, 0.55), 0, "cook", "toque") + if sstate == V0.StationState.CONGESTED: + _char("Cook", s["pos"] + Vector3(0.55, 0, 0.55), 0, "cook", "toque") + # host + _char("Idle", restaurant.host_pos, 200, "host", "clipboard") + # servers: walkers in the dining aisle + one at the pass + var srv: Array = snap["servers"] + var waiting_at_pass := 0 + for tgt in srv: + if tgt == "pass" or tgt == "overloaded": + waiting_at_pass += 1 + for w in max(1, waiting_at_pass): + _char("Carry" if waiting_at_pass == 0 else "Idle", restaurant.pass_pos + Vector3(0.6 * w - 0.6, -0.9, 0.8), 180, "server", "tray") + # a couple of walking servers to give the room motion (fewer if kitchen-starved) + var n_walk := 1 if waiting_at_pass >= 2 else 2 + for i in n_walk: + var s := _char("Walk", Vector3(-3.0 + i * 2.5, 0, 1.2), 90, "server") + _walkers.append({"node": s, "path": [Vector3(-3.5, 0, 1.2), Vector3(2.5, 0, 1.2), Vector3(2.5, 0, -1.0), Vector3(-3.5, 0, -1.0)], "wp": i, "speed": 1.3}) + # busser (Wipe) at a dirty table, else walking + var bt = snap.get("busser", "idle") + if bt != "idle": + _char("Wipe", _tpos(bt) + Vector3(0.7, 0, 0.5), 200, "busser") + else: + var b := _char("Walk", Vector3(3.0, 0, -0.5), 180, "busser") + _walkers.append({"node": b, "path": [Vector3(3.2, 0, -1.0), Vector3(3.2, 0, -3.6), Vector3(-4.0, 0, -1.0)], "wp": 0, "speed": 1.1}) + +func _process(dt: float) -> void: + for w in _walkers: + var node: Node3D = w["node"] + var target: Vector3 = w["path"][w["wp"]] + var to := target - node.position; to.y = 0 + if to.length() < 0.2: + w["wp"] = (w["wp"] + 1) % w["path"].size() + else: + var d := to.normalized() + node.position += d * w["speed"] * dt; node.position.y = 0 + node.rotation.y = atan2(d.x, d.z) + +func _tint(n: Node, c: Color) -> void: + if n is MeshInstance3D: + var m := StandardMaterial3D.new(); m.albedo_color = c; m.roughness = 0.7 + n.material_override = m + for ch in n.get_children(): + _tint(ch, c) + +func _box(size: Vector3, c: Color) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var b := BoxMesh.new(); b.size = size; mi.mesh = b + var m := StandardMaterial3D.new(); m.albedo_color = c; mi.material_override = m + return mi + +func _tpos(id) -> Vector3: + for t in restaurant.tables: + if t["id"] == id: return t["pos"] + return restaurant.pass_pos + +func randf_from(v: Vector3) -> float: + var h := int(abs(v.x * 73.1 + v.z * 19.7)) % 100 + return float(h) / 100.0 + +func _warm() -> void: + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("1c1712") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR + e.ambient_light_color = Color("caa981"); e.ambient_light_energy = 0.45 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; e.ssao_enabled = true + we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-48, -35, 0) + sun.light_color = Color("ffdcae"); sun.light_energy = 1.15; sun.shadow_enabled = true; add_child(sun) + var kit := SpotLight3D.new(); kit.position = Vector3(-1, 3.4, -3.6); kit.rotation_degrees = Vector3(-90, 0, 0) + kit.light_color = Color("cfe0ff"); kit.light_energy = 6.0; kit.spot_range = 8.0; kit.spot_angle = 60.0; add_child(kit) + +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null + +func _arg(k: String, d: String) -> String: + for a in OS.get_cmdline_user_args(): + if a.begins_with("--%s=" % k): return a.split("=", 1)[1] + return d diff --git a/visual/RestaurantVisualSandbox/scripts/animated_service.gd.uid b/visual/RestaurantVisualSandbox/scripts/animated_service.gd.uid new file mode 100644 index 0000000..4341a22 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/animated_service.gd.uid @@ -0,0 +1 @@ +uid://b83c63kvbojty diff --git a/visual/RestaurantVisualSandbox/scripts/carry_rec.gd b/visual/RestaurantVisualSandbox/scripts/carry_rec.gd new file mode 100644 index 0000000..2f74bb2 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/carry_rec.gd @@ -0,0 +1,39 @@ +extends Node3D +## Loop-5 remediation: continuous tray-carry recording. Server plays WalkCarry (legs walk, +## right arm pinned in carry pose) with a tray socketed on hand_r so it stays gripped across +## the whole walk cycle. Run with: --write-movie --fixed-fps 30 (auto-quits ~2.4s). +func _ready() -> void: + _warm() + add_child(Placeholder.floor_tile(Vector2(8, 5), Color("2c2620"))) + var n = load("res://assets/generated/CHAR_Hybrid_Server_Average.glb").instantiate() + add_child(n) + var sk := _find(n, "Skeleton3D") as Skeleton3D + if sk: + var socket := BoneAttachment3D.new(); sk.add_child(socket); socket.bone_name = "hand_r" + var g := Node3D.new(); g.position = Vector3(0.0, 0.03, 0.14); g.rotation_degrees = Vector3(-20, 0, 0) + var t := MeshInstance3D.new(); var bm := BoxMesh.new(); bm.size = Vector3(0.30, 0.02, 0.24); t.mesh = bm + var m := StandardMaterial3D.new(); m.albedo_color = Color("cfd8e3"); t.material_override = m; g.add_child(t) + var plate := MeshInstance3D.new(); var cm := CylinderMesh.new(); cm.top_radius = 0.06; cm.bottom_radius = 0.06; cm.height = 0.02; plate.mesh = cm + plate.position = Vector3(-0.05, 0.03, 0); var pmm := StandardMaterial3D.new(); pmm.albedo_color = Color("f0efe9"); plate.material_override = pmm; g.add_child(plate) + var glass := MeshInstance3D.new(); var gm := CylinderMesh.new(); gm.top_radius = 0.028; gm.bottom_radius = 0.028; gm.height = 0.09; glass.mesh = gm + glass.position = Vector3(0.07, 0.06, 0.03); var gmm := StandardMaterial3D.new(); gmm.albedo_color = Color("bfe0e8"); glass.material_override = gmm; g.add_child(glass) + socket.add_child(g) + var ap := _find(n, "AnimationPlayer") as AnimationPlayer + if ap and ap.has_animation("WalkCarry"): + ap.get_animation("WalkCarry").loop_mode = Animation.LOOP_LINEAR; ap.play("WalkCarry") + var cam := Camera3D.new(); add_child(cam); cam.current = true + cam.position = Vector3(2.3, 1.25, 2.5); cam.look_at(Vector3(0, 1.0, 0)) + get_tree().create_timer(2.4).timeout.connect(func(): get_tree().quit()) +func _warm() -> void: + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("20242b") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR; e.ambient_light_color = Color("b4bccb"); e.ambient_light_energy = 0.6 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; e.ssao_enabled = true; we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-42, -38, 0); sun.light_energy = 1.35; sun.shadow_enabled = true; add_child(sun) + var fill := DirectionalLight3D.new(); fill.rotation_degrees = Vector3(-20, 150, 0); fill.light_energy = 0.35; add_child(fill) +func _find(nd, t): + if nd.get_class() == t: return nd + for c in nd.get_children(): + var r = _find(c, t) + if r: return r + return null diff --git a/visual/RestaurantVisualSandbox/scripts/carry_rec.gd.uid b/visual/RestaurantVisualSandbox/scripts/carry_rec.gd.uid new file mode 100644 index 0000000..c0309f3 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/carry_rec.gd.uid @@ -0,0 +1 @@ +uid://bkjgim1cpd0ou diff --git a/visual/RestaurantVisualSandbox/scripts/host_turnover.gd b/visual/RestaurantVisualSandbox/scripts/host_turnover.gd new file mode 100644 index 0000000..7762327 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/host_turnover.gd @@ -0,0 +1,304 @@ +extends Node3D +## Loop 6 — Host + Busser operational language. Scripted VisualOnly choreography. +## NO production AI / pathfinding / seating logic / authoritative table availability. +## The scripted snapshot controls every visual state. +## Args: +## --scenario=healthy|overload which choreography to stage +## --hud=off|presentation|readability HUD layer (default off — physical scene must read alone) +## --cam=overview|host|turn camera framing (default overview = the management camera) +## --t=SECONDS pre-advance the small looping motions +const GEN := "res://assets/generated/%s.glb" +var restaurant: CorrectedRestaurant +var rig: CameraRig +var _movers := [] # {node, path, wp, speed} +var _elapsed := 0.0 +var _hud: CanvasLayer +var _cust := ["CHAR_Hybrid_Customer_V1", "CHAR_Hybrid_Customer_V2", "CHAR_Hybrid_Customer_V3", + "CHAR_Hybrid_Customer_V4", "CHAR_Hybrid_Customer_V5", "CHAR_Hybrid_Customer_V6"] + +# per-table state plan for each scenario: id -> state +# states: available / occupied / dirty / cleaning +const PLAN := { + "healthy": {"T1": "occupied", "T2": "occupied", "T3": "available", "T4": "occupied", + "T5": "cleaning", "T6": "available", "T7": "occupied", "T8": "available", + "T9": "occupied", "T10": "available"}, + "overload": {"T1": "dirty", "T2": "dirty", "T3": "occupied", "T4": "dirty", + "T5": "cleaning", "T6": "dirty", "T7": "occupied", "T8": "dirty", + "T9": "occupied", "T10": "dirty"}, +} + +func _ready() -> void: + var scenario := _arg("scenario", "healthy") + var hud_mode := _arg("hud", "off") + restaurant = CorrectedRestaurant.new(); add_child(restaurant); restaurant.build() + _elapsed = float(_arg("t", "2.0")) + _dim_ambient() + var plan: Dictionary = PLAN[scenario] + var ci := 0 + for t in restaurant.tables: + var st: String = plan.get(t["id"], "available") + ci = _apply_table_state(t, st, ci) + _setup_host(scenario) + _setup_busser(scenario) + _setup_queue(scenario) + _setup_kitchen_context() + if hud_mode != "off": + _build_hud(hud_mode, scenario, plan) + _setup_camera(_arg("cam", "overview")) + if _arg("rec", "") != "": + get_tree().create_timer(3.0).timeout.connect(func(): get_tree().quit()) + +# ---- table physical state ------------------------------------------------- +func _apply_table_state(t: Dictionary, state: String, ci: int) -> int: + var c: Vector3 = t["pos"] + var seats: Array = t["seats_world"] + if state == "occupied": + for sw in seats: + _spawn(_cust[ci % _cust.size()], sw["pos"], rad_to_deg(sw["yaw"]) + 180.0, ["Sitting_Idle", "Eat"][ci % 2]); ci += 1 + _plates(c, seats, "food") + elif state == "available": + _plates(c, seats, "clean") # env chairs stay tucked (aligned) — no proxy doubling + elif state == "dirty": + _plates(c, seats, "dirty") + _displaced_chair(t) # one chair clearly left pulled out + elif state == "cleaning": + _plates(c, seats, "dirty_partial") + _displaced_chair(t) + return ci + +func _plates(c: Vector3, seats: Array, kind: String) -> void: + var top := 0.76 + if kind == "clean": + for sw in seats: + var d: Vector3 = c - sw["pos"]; d.y = 0; var p: Vector3 = sw["pos"] + d.normalized() * 0.34 + _disc(Vector3(p.x, top, p.z), 0.09, Color("efe9dc")) # neat plate + _boxp(Vector3(p.x + 0.12, top, p.z), Vector3(0.05, 0.02, 0.07), Color("d8d2c2")) # folded napkin + elif kind == "food": + for sw in seats: + var d2: Vector3 = c - sw["pos"]; d2.y = 0; var p2: Vector3 = sw["pos"] + d2.normalized() * 0.34 + _disc(Vector3(p2.x, top, p2.z), 0.09, Color("e6ddc8")) + _cylp(Vector3(p2.x, top + 0.03, p2.z), 0.03, 0.06, Color("b5651d")) # food + _cylp(Vector3(p2.x + 0.13, top + 0.04, p2.z), 0.028, 0.09, Color("9cc4d6")) # drink + elif kind == "dirty" or kind == "dirty_partial": + # a DARK, TALL, cluttered pile so "dirty" reads at overview and survives greyscale + var n: int = seats.size() if kind == "dirty" else max(1, seats.size() - 2) + var stack := _cylp(Vector3(c.x - 0.06, top + 0.05, c.z + 0.04), 0.10, 0.10, Color("6f6250")) # stacked used plates + for i in n: + var a := TAU * float(i) / float(max(1, n)) + 0.6 + var off := Vector3(cos(a) * 0.30, 0, sin(a) * 0.22) + _disc(Vector3(c.x + off.x, top, c.z + off.z), 0.09, Color("7d6f5a"), 16.0 + i * 9) # tilted dark used plate + _cylp(Vector3(c.x + off.x * 1.15, top + 0.06, c.z + off.z * 1.15), 0.03, 0.12, Color("5f7078")) # tall dirty glass + _boxp(Vector3(c.x - off.x * 0.6, top + 0.02, c.z - off.z * 0.6), Vector3(0.08, 0.03, 0.06), Color("9c9484"), 24.0) # crumpled napkin + +func _displaced_chair(t: Dictionary) -> void: + # one chair clearly pulled away from the table + angled: "someone just left" + var sw: Dictionary = t["seats_world"][0] + var out: Vector3 = sw["pos"] - t["pos"]; out.y = 0; out = out.normalized() + var pos: Vector3 = sw["pos"] + out * 0.34 + var yaw: float = sw["yaw"] + deg_to_rad(38) + var seat := _boxp(Vector3(pos.x, 0.45, pos.z), Vector3(0.36, 0.06, 0.36), Color("6b4a30")) + seat.rotation.y = yaw + var back := _boxp(Vector3(pos.x - sin(yaw) * 0.16, 0.66, pos.z - cos(yaw) * 0.16), Vector3(0.36, 0.38, 0.05), Color("5c3f28")) + back.rotation.y = yaw + +func _align_chairs(t: Dictionary, aligned: bool) -> void: + # add a state chair-proxy per seat: tucked (aligned) or pushed-out+angled (dirty/occupied-left) + for i in t["seats_world"].size(): + var sw: Dictionary = t["seats_world"][i] + var out: Vector3 = sw["pos"] - t["pos"]; out.y = 0; out = out.normalized() + var pos: Vector3 = sw["pos"] + (Vector3.ZERO if aligned else out * 0.22) + var yaw: float = sw["yaw"] + (0.0 if aligned else deg_to_rad(28 + i * 9)) + var seat := _boxp(Vector3(pos.x, 0.45, pos.z), Vector3(0.34, 0.05, 0.34), Color("7a5638")) + seat.rotation.y = yaw + var back := _boxp(Vector3(pos.x, 0.66, pos.z), Vector3(0.34, 0.36, 0.05), Color("6b4a30")) + back.rotation.y = yaw + back.position += Vector3(-sin(yaw), 0, -cos(yaw)) * 0.15 + +# ---- actors --------------------------------------------------------------- +func _spawn(name: String, pos: Vector3, yaw: float, clip: String) -> Node3D: + var packed = load(GEN % name) + if packed == null: return Node3D.new() + var n = packed.instantiate(); n.position = pos; n.rotation.y = deg_to_rad(yaw); add_child(n) + _play(n, clip) + return n + +func _play(n: Node3D, clip: String) -> void: + var ap := _find(n, "AnimationPlayer") as AnimationPlayer + if ap == null: return + var real := clip + if not ap.has_animation(real): + for a in ap.get_animation_list(): + if String(a).findn(clip) >= 0: real = a; break + if ap.has_animation(real): + ap.get_animation(real).loop_mode = Animation.LOOP_LINEAR + ap.play(real); ap.seek(fmod(_elapsed, 1.4) + 0.2, true) + +func _attach_tub(n: Node3D, loaded: bool, in_hands: bool) -> void: + var sk := _find(n, "Skeleton3D") as Skeleton3D + if sk == null: return + if in_hands: + var socket := BoneAttachment3D.new(); sk.add_child(socket); socket.bone_name = "spine_02" + var g := _tub_mesh(loaded); g.position = Vector3(0, -0.12, 0.34); socket.add_child(g) + else: + var g2 := _tub_mesh(loaded); g2.position = n.position + Vector3(0.0, 0.42, 0.0); add_child(g2) # on a stand/floor by the table + +func _setup_host(scenario: String) -> void: + var hp: Vector3 = restaurant.host_pos + var clip := "CheckList" if scenario == "healthy" else "Apologetic" + var host := _spawn("CHAR_Hybrid_Host_Average", hp + Vector3(-0.1, 0, 0.35), 205, clip) + # clipboard in the left hand + var sk := _find(host, "Skeleton3D") as Skeleton3D + if sk: + var socket := BoneAttachment3D.new(); sk.add_child(socket); socket.bone_name = "hand_l" + var cb := _clipboard(); cb.position = Vector3(0.0, 0.05, 0.08); cb.rotation_degrees = Vector3(-45, 0, 0); socket.add_child(cb) + if scenario == "healthy": + # a second host gesture pass could go here; healthy host greets the arriving party + _play(host, "Greet") + +func _setup_busser(scenario: String) -> void: + # the table being cleaned (T5) — busser wiping, tub beside the table + var t5: Dictionary = _table("T5") + var bpos: Vector3 = t5["pos"] + Vector3(-0.1, 0, 0.95) + var clip := "Wipe" if scenario == "healthy" else "PickUp_Table" + var busser := _spawn("CHAR_Hybrid_Busser_Average", bpos, 0, clip) + # cleaning cloth in the right hand while wiping + if clip == "Wipe": + var sk := _find(busser, "Skeleton3D") as Skeleton3D + if sk: + var socket := BoneAttachment3D.new(); sk.add_child(socket); socket.bone_name = "hand_r" + var cloth := _cloth(); cloth.position = Vector3(0, 0.02, 0.06); socket.add_child(cloth) + _attach_tub(busser, scenario == "overload", false) # tub beside table; loaded in overload + if scenario == "overload": + # a second busser walking a loaded tub away (turnover under pressure) + var b2 := _spawn("CHAR_Hybrid_Busser_Average", _table("T2")["pos"] + Vector3(0, 0, 1.2), 120, "TubCarry") + _attach_tub(b2, true, true) + _movers.append({"node": b2, "path": [b2.position, restaurant.dish_pos + Vector3(0, 0, 1.0)], "wp": 1, "speed": 0.7}) + +func _setup_queue(scenario: String) -> void: + var slots: Array = restaurant.waiting_slots + var n: int = 2 if scenario == "healthy" else 4 + for i in n: + var clip := "Idle" if i % 2 == 0 else "Idle_Talking" + _spawn(_cust[(i + 2) % _cust.size()], slots[i], 180, clip) + if scenario == "overload": + # overflow beyond the defined slots + one party leaving after a scripted delay + for j in 2: + _spawn(_cust[(j + 4) % _cust.size()], slots[3] + Vector3(-0.55 * (j + 1), 0, 0.2), 180, "Idle") + var leaver := _spawn(_cust[1], slots[3] + Vector3(-1.6, 0, 0.4), 250, "Walk") + _movers.append({"node": leaver, "path": [leaver.position, Vector3(4.6, 0, 5.2)], "wp": 1, "speed": 1.1}) + +func _setup_kitchen_context() -> void: + # kitchen stays comparatively CALM so it never reads as the bottleneck + _spawn("CHAR_Hybrid_Cook_Average", restaurant.stations[0]["pos"] + Vector3(0, 0, 0.55), 0, "CookHold") + _spawn("CHAR_Hybrid_Cook_Broad", restaurant.stations[2]["pos"] + Vector3(0, 0, 0.55), 0, "PickUp_Table") + var srv := _spawn("CHAR_Hybrid_Server_Average", Vector3(-1.0, 0, -1.6), 90, "WalkCarry") + _attach_serve_tray(srv) + _movers.append({"node": srv, "path": [Vector3(-3.0, 0, -1.6), Vector3(0.8, 0, -1.6)], "wp": 1, "speed": 0.9}) + +func _attach_serve_tray(n: Node3D) -> void: + var sk := _find(n, "Skeleton3D") as Skeleton3D + if sk == null: return + var socket := BoneAttachment3D.new(); sk.add_child(socket); socket.bone_name = "hand_r" + var g := Node3D.new(); g.position = Vector3(0.0, 0.03, 0.14); g.rotation_degrees = Vector3(-20, 0, 0) + var t := _boxp(Vector3.ZERO, Vector3(0.28, 0.02, 0.22), Color("cfd8e3")); t.position = Vector3.ZERO; g.add_child(t) + socket.add_child(g) + +func _process(dt: float) -> void: + _elapsed += dt + for m in _movers: + var node: Node3D = m["node"]; var tgt: Vector3 = m["path"][m["wp"]] + var to := tgt - node.position; to.y = 0 + if to.length() < 0.2: + m["wp"] = min(m["wp"] + 1, m["path"].size() - 1) + else: + var d := to.normalized(); node.position += d * float(m["speed"]) * dt; node.position.y = 0 + node.rotation.y = atan2(d.x, d.z) + +# ---- HUD (kept SEPARATE from the physical scene) -------------------------- +func _build_hud(mode: String, scenario: String, plan: Dictionary) -> void: + _hud = CanvasLayer.new(); add_child(_hud) + if mode == "presentation": + # aggregate PRESSURE only (never the scenario name): dirty tables + queue length + var dirty := 0 + for v in plan.values(): + if v == "dirty": dirty += 1 + var qn: float = 2.0 if scenario == "healthy" else 6.0 + var pressure: float = clampf((float(dirty) / 6.0) * 0.6 + (qn / 6.0) * 0.4, 0.0, 1.0) + var bg := ColorRect.new(); bg.color = Color(0, 0, 0, 0.35); bg.size = Vector2(360, 26); bg.position = Vector2(24, 24); _hud.add_child(bg) + var bar := ColorRect.new(); bar.size = Vector2(352 * pressure, 18); bar.position = Vector2(28, 28) + bar.color = V0.C_CALM.lerp(V0.C_ALERT, pressure); _hud.add_child(bar) + var cap := Label.new(); cap.text = "Front-of-house pressure"; cap.position = Vector2(28, 54); cap.add_theme_font_size_override("font_size", 13); _hud.add_child(cap) + elif mode == "readability": + # floating emissive table-state pips + a queue-pressure marker (the readability overlay) + for t in restaurant.tables: + var st: String = plan.get(t["id"], "available") + var col: Color = {"available": V0.C_CALM, "occupied": V0.C_ACTIVE, "dirty": V0.C_DIRTY, "cleaning": V0.C_READY}[st] + _pip(t["pos"] + Vector3(0, 1.65, 0), 0.16, col) + var qn2: int = 2 if scenario == "healthy" else 6 + _pip(restaurant.host_pos + Vector3(-0.7, 2.0, 0), 0.22, V0.C_WAIT_HARD if qn2 > 3 else V0.C_WAIT_SOFT) + +func _pip(pos: Vector3, r: float, c: Color) -> void: + var mi := MeshInstance3D.new(); var sm := SphereMesh.new(); sm.radius = r; sm.height = r * 2.0; mi.mesh = sm + var m := StandardMaterial3D.new(); m.albedo_color = c; m.emission_enabled = true; m.emission = c; m.emission_energy_multiplier = 1.6 + mi.material_override = m; mi.position = pos; add_child(mi) + +# ---- camera --------------------------------------------------------------- +func _setup_camera(cam: String) -> void: + rig = CameraRig.new(); add_child(rig); rig.interactive = false; rig.set_mode(CameraRig.Mode.ORBIT_B) + if cam == "host": + rig.frame(restaurant.host_pos + Vector3(-0.6, 0.4, -0.4), 20.0, 22.0, 6.5) + elif cam == "turn": + rig.frame(_table("T5")["pos"] + Vector3(0, 0.5, 0), 28.0, 20.0, 6.0) + else: # overview — the default management camera + rig.frame(Vector3(-0.2, 0.55, 0.9), 23.0, 34.0, 12.2) + +# ---- small prop meshes ---------------------------------------------------- +func _disc(pos: Vector3, r: float, c: Color, tilt := 0.0) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var cm := CylinderMesh.new(); cm.top_radius = r; cm.bottom_radius = r; cm.height = 0.02; mi.mesh = cm + mi.material_override = _mat(c); mi.position = pos; mi.rotation_degrees.z = tilt; add_child(mi); return mi +func _cylp(pos: Vector3, r: float, h: float, c: Color) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var cm := CylinderMesh.new(); cm.top_radius = r; cm.bottom_radius = r; cm.height = h; mi.mesh = cm + mi.material_override = _mat(c); mi.position = pos; add_child(mi); return mi +func _boxp(pos: Vector3, s: Vector3, c: Color, tilt := 0.0) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var b := BoxMesh.new(); b.size = s; mi.mesh = b + mi.material_override = _mat(c); mi.position = pos; mi.rotation_degrees.y = tilt; add_child(mi); return mi +func _mat(c: Color) -> StandardMaterial3D: + var m := StandardMaterial3D.new(); m.albedo_color = c; m.roughness = 0.8; return m +func _tub_mesh(loaded: bool) -> Node3D: + var g := Node3D.new(); var col := Color("41454b") + var fl := MeshInstance3D.new(); var fb := BoxMesh.new(); fb.size = Vector3(0.50, 0.02, 0.27); fl.mesh = fb; fl.material_override = _mat(col); fl.position = Vector3(0, -0.09, 0); g.add_child(fl) + for sx in [-1, 1]: + var w := MeshInstance3D.new(); var wb := BoxMesh.new(); wb.size = Vector3(0.02, 0.18, 0.27); w.mesh = wb; w.material_override = _mat(col); w.position = Vector3(sx * 0.25, 0, 0); g.add_child(w) + for sz in [-1, 1]: + var w2 := MeshInstance3D.new(); var wb2 := BoxMesh.new(); wb2.size = Vector3(0.50, 0.18, 0.02); w2.mesh = wb2; w2.material_override = _mat(col); w2.position = Vector3(0, 0, sz * 0.135); g.add_child(w2) + if loaded: + for i in 3: + var p := MeshInstance3D.new(); var pc := CylinderMesh.new(); pc.top_radius = 0.07; pc.bottom_radius = 0.07; pc.height = 0.02; p.mesh = pc; p.material_override = _mat(Color("e2ddce")); p.position = Vector3(-0.13 + i * 0.12, 0.05 + i * 0.01, 0.02 * i); g.add_child(p) + return g +func _clipboard() -> Node3D: + var g := Node3D.new() + var board := MeshInstance3D.new(); var bb := BoxMesh.new(); bb.size = Vector3(0.19, 0.012, 0.25); board.mesh = bb; board.material_override = _mat(Color("b98a52")); g.add_child(board) + var paper := MeshInstance3D.new(); var pb := BoxMesh.new(); pb.size = Vector3(0.15, 0.004, 0.20); paper.mesh = pb; paper.material_override = _mat(Color("f4f1e8")); paper.position = Vector3(0, 0.009, 0.005); g.add_child(paper) + return g +func _cloth() -> Node3D: + var g := Node3D.new() + var c := MeshInstance3D.new(); var cb := BoxMesh.new(); cb.size = Vector3(0.12, 0.018, 0.12); c.mesh = cb; c.material_override = _mat(Color("dfe2e8")); g.add_child(c) + return g + +# ---- misc ----------------------------------------------------------------- +func _table(id: String) -> Dictionary: + for t in restaurant.tables: + if t["id"] == id: return t + return restaurant.tables[0] +func _dim_ambient() -> void: + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-52, -40, 0); sun.light_color = Color("ffe6c4"); sun.light_energy = 1.15; sun.shadow_enabled = true; add_child(sun) +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null +func _arg(key: String, d: String) -> String: + for a in OS.get_cmdline_user_args(): + if a.begins_with("--%s=" % key): return a.split("=", 1)[1] + return d diff --git a/visual/RestaurantVisualSandbox/scripts/host_turnover.gd.uid b/visual/RestaurantVisualSandbox/scripts/host_turnover.gd.uid new file mode 100644 index 0000000..ce86417 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/host_turnover.gd.uid @@ -0,0 +1 @@ +uid://dn635u7bq01in diff --git a/visual/RestaurantVisualSandbox/scripts/hybrid_perf.gd b/visual/RestaurantVisualSandbox/scripts/hybrid_perf.gd new file mode 100644 index 0000000..f55067c --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/hybrid_perf.gd @@ -0,0 +1,21 @@ +extends Node +func _ready(): + DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED); Engine.max_fps=0 + get_window().size=Vector2i(1920,1080) + var out="/tmp/hp.csv" + for a in OS.get_cmdline_user_args(): + if a.begins_with("--out="): out=a.substr(6) + var inst=load("res://scenes/diagnostics/HybridRuntime.tscn").instantiate(); add_child(inst) + for i in 40: await get_tree().process_frame + var draw=RenderingServer.get_rendering_info(RenderingServer.RENDERING_INFO_TOTAL_DRAW_CALLS_IN_FRAME) + var prims=RenderingServer.get_rendering_info(RenderingServer.RENDERING_INFO_TOTAL_PRIMITIVES_IN_FRAME) + var vram=RenderingServer.get_rendering_info(RenderingServer.RENDERING_INFO_VIDEO_MEM_USED)/1048576.0 + var sk=_c(inst,"Skeleton3D") + var row="hybrid_runtime,skeletons=%d,draw_calls=%d,primitives=%d,vram_mb=%.1f" % [sk,draw,prims,vram] + print("[hp] ",row) + var f=FileAccess.open(out,FileAccess.WRITE); if f: f.store_string(row+"\n"); f.close() + get_tree().quit() +func _c(n,t): + var k=1 if n.get_class()==t else 0 + for ch in n.get_children(): k+=_c(ch,t) + return k diff --git a/visual/RestaurantVisualSandbox/scripts/hybrid_perf.gd.uid b/visual/RestaurantVisualSandbox/scripts/hybrid_perf.gd.uid new file mode 100644 index 0000000..ed3d34f --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/hybrid_perf.gd.uid @@ -0,0 +1 @@ +uid://ixieiv5sujq6 diff --git a/visual/RestaurantVisualSandbox/scripts/hybrid_review.gd b/visual/RestaurantVisualSandbox/scripts/hybrid_review.gd new file mode 100644 index 0000000..e905966 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/hybrid_review.gd @@ -0,0 +1,105 @@ +extends Node3D +## Loop-4 hybrid review. Args: --mode=lineup|clip|compare --char=NAME --clip=idle|walk|sit|reach --closeup=1 +const GEN := "res://assets/generated/%s.glb" +const CLIP := {"idle": "Idle", "walk": "Walk", "sit": "Sitting_Idle", "reach": "PickUp_Table", + "eat": "Eat", "impatient": "Impatient", "seek": "SeekAttention", "sitenter": "Sitting_Enter", + "sitexit": "Sitting_Exit", "queue": "Idle", "carry": "ServeCarry", "hold": "CookHold", + "checklist": "CheckList", "greet": "Greet", "gesture": "GestureDining", "apologetic": "Apologetic", + "tubhold": "TubHold", "tubcarry": "TubCarry", "wipe": "Wipe", "walkformal": "Walk_Formal", + "talk": "Idle_Talking", "jog": "Jog_Fwd", "kneel": "Fixing_Kneeling"} + +func _ready() -> void: + _warm() + add_child(Placeholder.floor_tile(Vector2(8, 5), Color("2c2620"))) + var mode := _arg("mode", "lineup") + var clip := _arg("clip", "idle") + var cam := Camera3D.new(); add_child(cam); cam.current = true + if mode == "reel": + var ch := _arg("char", "CHAR_Hybrid_Host_Average") + var clips := _arg("clips", "idle,walk").split(",") + var x := -(clips.size() - 1) * 0.8 + for cl in clips: + _spawn(ch, Vector3(x, 0, 0), CLIP.get(cl, "Idle")); _lab(cl, Vector3(x, 2.0, 0), Color("e8dcc2")); x += 1.6 + cam.position = Vector3(0, 1.35, 1.6 + clips.size() * 0.9); cam.look_at(Vector3(0, 1.0, 0)) + return + if mode == "family": + var fam := [["CHAR_Hybrid_Host_Average", "Host"], ["CHAR_Hybrid_Server_Average", "Server"], + ["CHAR_Hybrid_Cook_Average", "Cook"], ["CHAR_Hybrid_Busser_Average", "Busser"], + ["CHAR_Hybrid_Customer_V1", "Customer"]] + var fx := -3.2 + for e in fam: + _spawn(e[0], Vector3(fx, 0, 0), "Idle"); _lab(e[1], Vector3(fx, 2.05, 0), Color("e8dcc2")); fx += 1.6 + cam.position = Vector3(0, 1.4, 5.6); cam.look_at(Vector3(0, 0.95, 0)) + return + if mode == "bodyline": + var pre := _arg("prefix", "CHAR_Hybrid_Host") + var bx := -1.8 + for suf in ["_Average", "_Slim", "_Broad"]: + _spawn(pre + suf, Vector3(bx, 0, 0), CLIP.get(clip, "Idle")); _lab(suf.substr(1), Vector3(bx, 2.05, 0), Color("e8dcc2")); bx += 1.6 + cam.position = Vector3(0, 1.35, 4.4); cam.look_at(Vector3(0, 1.0, 0)) + return + if mode == "pop": + var chars := ["CHAR_Hybrid_Customer_V1", "CHAR_Hybrid_Customer_V2", "CHAR_Hybrid_Customer_V3", + "CHAR_Hybrid_Customer_V4", "CHAR_Hybrid_Customer_V5", "CHAR_Hybrid_Customer_V6"] + var poses := (["Idle", "Idle", "Idle", "Idle", "Idle", "Idle"] if clip == "idle" + else ["Sitting_Idle", "Eat", "SeekAttention", "Impatient", "Sitting_Idle", "Eat"]) + var x := -3.1 + for i in chars.size(): + _spawn(chars[i], Vector3(x, 0, 0), poses[i]); _lab("V%d" % (i + 1), Vector3(x, 2.0, 0), Color("e8dcc2")); x += 1.24 + cam.position = Vector3(0, 1.5, 5.4); cam.look_at(Vector3(0, 0.9, 0)) + elif mode == "lineup": + var chars := ["CHAR_Hybrid_Server_Average", "CHAR_Hybrid_Server_Slim", "CHAR_Hybrid_Cook_Average", "CHAR_Hybrid_Cook_Broad"] + var x := -2.4 + for c in chars: + _spawn(c, Vector3(x, 0, 0), "Idle"); _lab(c.replace("CHAR_Hybrid_", ""), Vector3(x, 2.1, 0), Color("e8dcc2")); x += 1.6 + cam.position = Vector3(0, 1.35, 4.6); cam.look_at(Vector3(0, 1.0, 0)) + elif mode == "compare": + _spawn("CHAR_Base_Average", Vector3(-0.9, 0, 0), CLIP.get(clip, "Idle")); _lab("A procedural", Vector3(-0.9, 2.05, 0), Color("ffd166")) + _spawn(_arg("char", "CHAR_Hybrid_Server_Average"), Vector3(0.9, 0, 0), CLIP.get(clip, "Idle")); _lab("C hybrid", Vector3(0.9, 2.05, 0), Color("52b788")) + cam.position = Vector3(0, 1.15, 3.7); cam.look_at(Vector3(0, 0.95, 0)) + elif mode == "twoup": + _spawn(_arg("a", "CHAR_Hybrid_Customer_V1"), Vector3(-0.85, 0, 0), CLIP.get(clip, "Idle")); _lab(_arg("la", "LOD0"), Vector3(-0.85, 2.05, 0), Color("ffd166")) + _spawn(_arg("b", "CHAR_Hybrid_Customer_V1_LOD1"), Vector3(0.85, 0, 0), CLIP.get(clip, "Idle")); _lab(_arg("lb", "LOD1"), Vector3(0.85, 2.05, 0), Color("52b788")) + cam.position = Vector3(0, 1.05, 3.4); cam.look_at(Vector3(0, 0.85, 0)) + else: + var ch := _arg("char", "CHAR_Hybrid_Cook_Average") + _spawn(ch, Vector3.ZERO, CLIP.get(clip, "Idle")) + if _arg("closeup", "") != "": + cam.position = Vector3(0.85, 1.3, 1.5); cam.look_at(Vector3(0, 1.1, 0)) + else: + cam.position = Vector3(0, 1.1, 3.2); cam.look_at(Vector3(0, 0.95, 0)) + +func _spawn(name: String, pos: Vector3, clip: String) -> Node3D: + var packed = load(GEN % name) + if packed == null: + _lab("MISSING " + name, pos + Vector3(0, 1, 0), Color.RED); return Node3D.new() + var n = packed.instantiate(); n.position = pos; add_child(n) + var ap := _find(n, "AnimationPlayer") as AnimationPlayer + if ap: + var real := clip + if not ap.has_animation(real): + for a in ap.get_animation_list(): + if String(a).findn(clip) >= 0: real = a; break + if ap.has_animation(real): + ap.get_animation(real).loop_mode = Animation.LOOP_LINEAR + ap.play(real); ap.seek(0.6, true) + return n + +func _lab(t, pos, col): var l := Placeholder.label(t, 0.13, col); l.position = pos; add_child(l) +func _warm(): + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("20242b") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR; e.ambient_light_color = Color("b4bccb"); e.ambient_light_energy = 0.6 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; e.ssao_enabled = true; we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-42, -38, 0); sun.light_energy = 1.35; sun.shadow_enabled = true; add_child(sun) + var fill := DirectionalLight3D.new(); fill.rotation_degrees = Vector3(-20, 150, 0); fill.light_energy = 0.35; add_child(fill) +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null +func _arg(key: String, d: String) -> String: + for a in OS.get_cmdline_user_args(): + if a.begins_with("--%s=" % key): return a.split("=", 1)[1] + return d diff --git a/visual/RestaurantVisualSandbox/scripts/hybrid_review.gd.uid b/visual/RestaurantVisualSandbox/scripts/hybrid_review.gd.uid new file mode 100644 index 0000000..04b2e41 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/hybrid_review.gd.uid @@ -0,0 +1 @@ +uid://cwaqkxggy28b5 diff --git a/visual/RestaurantVisualSandbox/scripts/hybrid_runtime.gd b/visual/RestaurantVisualSandbox/scripts/hybrid_runtime.gd new file mode 100644 index 0000000..6402667 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/hybrid_runtime.gd @@ -0,0 +1,61 @@ +extends Node3D +## Loop-4 runtime: hybrid characters playing UAL clips INSIDE the restaurant. VisualOnly. +## Args: --rec_orbit=0 +const GEN := "res://assets/generated/%s.glb" +var restaurant: CorrectedRestaurant +var rig: CameraRig +var _walkers := [] + +func _ready() -> void: + _warm() + restaurant = CorrectedRestaurant.new(); add_child(restaurant); restaurant.build() + rig = CameraRig.new(); add_child(rig); rig.interactive = false + rig.set_mode(CameraRig.Mode.ORBIT_B) + rig.frame(Vector3(-2.5, 0.7, -3.2), 16.0, 26.0, 9.0) # over the kitchen/pass + # cooks at the two front stations (reach/cook), broad + average + _actor("CHAR_Hybrid_Cook_Average", restaurant.stations[0]["pos"] + Vector3(0, 0, 0.55), 0, "PickUp_Table") + _actor("CHAR_Hybrid_Cook_Broad", restaurant.stations[2]["pos"] + Vector3(0, 0, 0.55), 0, "PickUp_Table") + # a server walking the pass line + var s := _actor("CHAR_Hybrid_Server_Average", Vector3(-3.0, 0, -2.2), 90, "Walk") + _walkers.append({"node": s, "path": [Vector3(-3.0, 0, -2.2), Vector3(1.5, 0, -2.2)], "wp": 1, "speed": 1.2}) + # a seated guest at a nearby table + _actor("CHAR_Hybrid_Server_Slim", restaurant.tables[6]["seats_world"][0]["pos"], 90, "Sitting_Idle") + +func _actor(name: String, pos: Vector3, yaw: float, clip: String) -> Node3D: + var packed = load(GEN % name) + if packed == null: return Node3D.new() + var n = packed.instantiate(); n.position = pos; n.rotation.y = deg_to_rad(yaw); add_child(n) + var ap := _find(n, "AnimationPlayer") as AnimationPlayer + if ap: + var real := clip + if not ap.has_animation(real): + for a in ap.get_animation_list(): + if String(a).findn(clip) >= 0: real = a; break + if ap.has_animation(real): + ap.get_animation(real).loop_mode = Animation.LOOP_LINEAR + ap.play(real); ap.seek(0.5, true) + return n + +func _process(dt: float) -> void: + for w in _walkers: + var node: Node3D = w["node"]; var t: Vector3 = w["path"][w["wp"]] + var to := t - node.position; to.y = 0 + if to.length() < 0.2: w["wp"] = (w["wp"] + 1) % w["path"].size() + else: + var d := to.normalized(); node.position += d * w["speed"] * dt; node.position.y = 0 + node.rotation.y = atan2(d.x, d.z) + +func _warm() -> void: + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("1c1712") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR; e.ambient_light_color = Color("caa981"); e.ambient_light_energy = 0.45 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; e.ssao_enabled = true; we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-48, -35, 0); sun.light_color = Color("ffdcae"); sun.light_energy = 1.15; sun.shadow_enabled = true; add_child(sun) + var kit := SpotLight3D.new(); kit.position = Vector3(-1, 3.4, -3.6); kit.rotation_degrees = Vector3(-90, 0, 0); kit.light_color = Color("cfe0ff"); kit.light_energy = 6.0; kit.spot_range = 8.0; kit.spot_angle = 60.0; add_child(kit) + +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null diff --git a/visual/RestaurantVisualSandbox/scripts/hybrid_runtime.gd.uid b/visual/RestaurantVisualSandbox/scripts/hybrid_runtime.gd.uid new file mode 100644 index 0000000..03728e0 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/hybrid_runtime.gd.uid @@ -0,0 +1 @@ +uid://bkidrqlqkg32j diff --git a/visual/RestaurantVisualSandbox/scripts/mini_perf.gd b/visual/RestaurantVisualSandbox/scripts/mini_perf.gd new file mode 100644 index 0000000..b64d280 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/mini_perf.gd @@ -0,0 +1,97 @@ +extends Node +## Loop-5 performance probe (WINDOWED so RenderingServer counters + measured GPU time are +## real, not the headless-0 path). Spawns a fixed cast: 1 server + 1 cook + 4 customers, all +## playing clips, and reports draw calls / primitives / vram / measured GPU ms / fps. +## Args: --lod=0|1 --out=PATH +const GEN := "res://assets/generated/%s.glb" + +func _ready() -> void: + DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED); Engine.max_fps = 0 + get_window().size = Vector2i(1600, 900) + var lod := "0"; var out := "/tmp/mp.csv" + for a in OS.get_cmdline_user_args(): + if a.begins_with("--lod="): lod = a.substr(6) + if a.begins_with("--out="): out = a.substr(6) + _warm() + var floor := MeshInstance3D.new(); var pm := PlaneMesh.new(); pm.size = Vector2(12, 8); floor.mesh = pm + var fmat := StandardMaterial3D.new(); fmat.albedo_color = Color("2c2620"); floor.material_override = fmat; add_child(floor) + var cam := Camera3D.new(); add_child(cam); cam.current = true + cam.position = Vector3(0, 1.6, 5.2); cam.look_at(Vector3(0, 0.9, 0)) + var cast := "loop5" + for a in OS.get_cmdline_user_args(): + if a.begins_with("--cast="): cast = a.substr(7) + var custv := "CHAR_Hybrid_Customer_V1_LOD1" if lod == "1" else "CHAR_Hybrid_Customer_V1" + _spawn("CHAR_Hybrid_Server_Average", Vector3(-2.4, 0, 0), "ServeCarry") + _spawn("CHAR_Hybrid_Cook_Average", Vector3(-1.2, 0, 0), "CookHold") + if cast == "loop6": + # Loop 6 cast: + host + busser + 6 customers (vs Loop 5's server+cook+4cust) + _spawn("CHAR_Hybrid_Host_Average", Vector3(-3.6, 0, 0), "CheckList") + _spawn("CHAR_Hybrid_Busser_Average", Vector3(-4.8, 0, 0), "TubCarry") + var cx6 := [0.2, 1.1, 2.0, 2.9, 3.8, 4.7] + var cc6 := ["Sitting_Idle", "Eat", "SeekAttention", "Impatient", "Idle", "Sitting_Idle"] + for i in 6: + _spawn(custv, Vector3(cx6[i], 0, 0), cc6[i]) + else: + var cx := [0.2, 1.1, 2.0, 2.9] + var cclip := ["Sitting_Idle", "Eat", "SeekAttention", "Impatient"] + for i in 4: + _spawn(custv, Vector3(cx[i], 0, 0), cclip[i]) + var vp := get_viewport() + RenderingServer.viewport_set_measure_render_time(vp.get_viewport_rid(), true) + for i in 90: await get_tree().process_frame + var draw := RenderingServer.get_rendering_info(RenderingServer.RENDERING_INFO_TOTAL_DRAW_CALLS_IN_FRAME) + var prims := RenderingServer.get_rendering_info(RenderingServer.RENDERING_INFO_TOTAL_PRIMITIVES_IN_FRAME) + var vram := RenderingServer.get_rendering_info(RenderingServer.RENDERING_INFO_VIDEO_MEM_USED) / 1048576.0 + var gpu := RenderingServer.viewport_get_measured_render_time_gpu(vp.get_viewport_rid()) + var cpu := RenderingServer.viewport_get_measured_render_time_cpu(vp.get_viewport_rid()) + var fps := Engine.get_frames_per_second() + var tris := _count_tris(self) # authoritative in-engine geometry cost (render-independent) + var skels := _count_nodes(self, "Skeleton3D") + var players := _count_nodes(self, "AnimationPlayer") + var castname := "host+busser+server+cook+6cust" if cast == "loop6" else "server+cook+4cust" + var row := "lod=%s,cast=%s,characters=%d,skeletons=%d,anim_players=%d,scene_tris=%d,draw_calls=%d,primitives=%d,vram_mb=%.1f,gpu_ms=%.3f,cpu_ms=%.3f,fps=%d" % [lod, castname, skels, skels, players, tris, draw, prims, vram, gpu, cpu, fps] + print("[mp] ", row) + var f := FileAccess.open(out, FileAccess.WRITE) + if f: f.store_string(row + "\n"); f.close() + get_tree().quit() + +func _spawn(name: String, pos: Vector3, clip: String) -> void: + var packed = load(GEN % name) + if packed == null: return + var n = packed.instantiate(); n.position = pos; add_child(n) + var ap := _find(n, "AnimationPlayer") as AnimationPlayer + if ap: + var real := clip + if not ap.has_animation(real): + for a in ap.get_animation_list(): + if String(a).findn(clip) >= 0: real = a; break + if ap.has_animation(real): + ap.get_animation(real).loop_mode = Animation.LOOP_LINEAR; ap.play(real) + +func _warm() -> void: + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("20242b") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR; e.ambient_light_color = Color("b4bccb"); e.ambient_light_energy = 0.6 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-45, -35, 0); sun.light_energy = 1.2; sun.shadow_enabled = true; add_child(sun) + +func _count_nodes(n: Node, cls: String) -> int: + var k := 1 if n.get_class() == cls else 0 + for c in n.get_children(): + k += _count_nodes(c, cls) + return k + +func _count_tris(n: Node) -> int: + var total := 0 + if n is MeshInstance3D and n.mesh != null: + total += n.mesh.get_faces().size() / 3 + for c in n.get_children(): + total += _count_tris(c) + return total + +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null diff --git a/visual/RestaurantVisualSandbox/scripts/mini_perf.gd.uid b/visual/RestaurantVisualSandbox/scripts/mini_perf.gd.uid new file mode 100644 index 0000000..7a6b53a --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/mini_perf.gd.uid @@ -0,0 +1 @@ +uid://iwibdhbj4cp2 diff --git a/visual/RestaurantVisualSandbox/scripts/mini_service.gd b/visual/RestaurantVisualSandbox/scripts/mini_service.gd new file mode 100644 index 0000000..30a1127 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/mini_service.gd @@ -0,0 +1,143 @@ +extends Node3D +## Loop-5 mini healthy-service scene. VisualOnly, NO AI / NO navigation — a purely +## scripted, deterministic timeline. Customers enter -> sit -> eat -> seek attention -> +## idle -> stand -> leave along straight paths; a cook holds at a station and a server +## carries a tray along the pass line. Staggered offsets so one frame shows every phase. +## Args: --t=SECONDS (pre-advance the timeline), --cam=front|top +const GEN := "res://assets/generated/%s.glb" +const CYCLE := 22.0 +# phase table: [t_end, clip]; movement handled per-phase in _drive +const PHASES := [ + [4.0, "Walk"], # ENTER (door -> seat) + [5.2, "Sitting_Enter"], # SIT + [9.0, "Eat"], # EAT + [11.0, "SeekAttention"], # SEEK attention + [13.5, "Sitting_Idle"], # dine/idle + [14.6, "Sitting_Exit"], # STAND + [18.6, "Walk"], # LEAVE (seat -> door) + [22.0, "Idle"], # offscreen gap +] +var restaurant: CorrectedRestaurant +var rig: CameraRig +var _cust := [] +var _elapsed := 0.0 + +func _ready() -> void: + _warm() + restaurant = CorrectedRestaurant.new(); add_child(restaurant); restaurant.build() + _elapsed = float(_arg("t", "3.0")) + rig = CameraRig.new(); add_child(rig); rig.interactive = false + rig.set_mode(CameraRig.Mode.ORBIT_B) + var camv := _arg("cam", "front") + if camv == "top": + rig.frame(Vector3(0, 0.5, -1.0), 20.0, 62.0, 13.0) + elif camv == "hero": + rig.frame(Vector3(-0.2, 0.55, 0.2), 22.0, 13.0, 7.2) + else: + rig.frame(Vector3(0, 0.6, -1.0), 14.0, 24.0, 11.0) + # cook holding at a station + _actor("CHAR_Hybrid_Cook_Average", restaurant.stations[0]["pos"] + Vector3(0, 0, 0.55), 0, "CookHold") + # server carrying a tray along the pass + var srv := _actor("CHAR_Hybrid_Server_Average", Vector3(-2.6, 0, -2.2), 90, "Walk") + _socket_tray(srv) + _cust.append({"node": srv, "server": true, "path": [Vector3(-2.6, 0, -2.2), Vector3(1.4, 0, -2.2)], "wp": 1, "speed": 1.0}) + # six customers, staggered across the service cycle so all phases show at once + var chars := ["CHAR_Hybrid_Customer_V1", "CHAR_Hybrid_Customer_V2", "CHAR_Hybrid_Customer_V3", + "CHAR_Hybrid_Customer_V4", "CHAR_Hybrid_Customer_V5", "CHAR_Hybrid_Customer_V6"] + var door := Vector3(2.6, 0, 4.2) + for i in chars.size(): + var tbl: Dictionary = restaurant.tables[[1, 3, 5, 7, 9, 11][i] % restaurant.tables.size()] + var seat: Vector3 = tbl["seats_world"][0]["pos"] + var n := _actor(chars[i], door, 180, "Idle") + _cust.append({"node": n, "server": false, "seat": seat, "door": door, + "offset": (CYCLE / chars.size()) * i, "state": -1, + "approach": seat + (door - seat).normalized() * 0.9}) + _drive(0.0) + +func _actor(name: String, pos: Vector3, yaw: float, clip: String) -> Node3D: + var packed = load(GEN % name) + if packed == null: return Node3D.new() + var n = packed.instantiate(); n.position = pos; n.rotation.y = deg_to_rad(yaw); add_child(n) + _play(n, clip) + return n + +func _play(n: Node3D, clip: String) -> void: + var ap := _find(n, "AnimationPlayer") as AnimationPlayer + if ap == null: return + var real := clip + if not ap.has_animation(real): + for a in ap.get_animation_list(): + if String(a).findn(clip) >= 0: real = a; break + if ap.has_animation(real): + ap.get_animation(real).loop_mode = Animation.LOOP_LINEAR + ap.play(real); ap.seek(0.4, true) + +func _phase_of(lt: float) -> int: + for i in PHASES.size(): + if lt < PHASES[i][0]: return i + return PHASES.size() - 1 + +func _drive(dt: float) -> void: + _elapsed += dt + for c in _cust: + if c["server"]: + var node: Node3D = c["node"]; var tgt: Vector3 = c["path"][c["wp"]] + var to: Vector3 = tgt - node.position; to.y = 0 + if to.length() < 0.25: c["wp"] = (c["wp"] + 1) % c["path"].size() + else: + var d: Vector3 = to.normalized(); node.position += d * float(c["speed"]) * dt; node.position.y = 0 + node.rotation.y = atan2(d.x, d.z) + continue + var lt: float = fmod(_elapsed + c["offset"], CYCLE) + var ph := _phase_of(lt) + var node2: Node3D = c["node"] + if ph != c["state"]: + c["state"] = ph; _play(node2, PHASES[ph][1]) + # movement per phase + if ph == 0: # ENTER: door -> approach + var f: float = clamp(lt / float(PHASES[0][0]), 0.0, 1.0) + node2.position = c["door"].lerp(c["approach"], f); node2.position.y = 0 + var dd: Vector3 = c["approach"] - c["door"]; node2.rotation.y = atan2(dd.x, dd.z) + elif ph >= 1 and ph <= 5: # seated at the seat, facing table + node2.position = c["seat"]; node2.position.y = 0 + node2.rotation.y = deg_to_rad(180) + elif ph == 6: # LEAVE: approach -> door + var f2: float = clamp((lt - float(PHASES[5][0])) / (float(PHASES[6][0]) - float(PHASES[5][0])), 0.0, 1.0) + node2.position = c["approach"].lerp(c["door"], f2); node2.position.y = 0 + var dd2: Vector3 = c["door"] - c["approach"]; node2.rotation.y = atan2(dd2.x, dd2.z) + else: # gap: park at door + node2.position = c["door"]; node2.position.y = 0 + +func _process(dt: float) -> void: + _drive(dt) + +func _socket_tray(n: Node3D) -> void: + var sk := _find(n, "Skeleton3D") as Skeleton3D + if sk == null: return + var socket := BoneAttachment3D.new(); sk.add_child(socket); socket.bone_name = "hand_r" + var g := Node3D.new(); g.position = Vector3(0.02, 0.02, 0.0) + var t := MeshInstance3D.new(); var bm := BoxMesh.new(); bm.size = Vector3(0.28, 0.03, 0.22); t.mesh = bm + var m := StandardMaterial3D.new(); m.albedo_color = Color("cfd8e3"); t.material_override = m; g.add_child(t) + var plate := MeshInstance3D.new(); var cm := CylinderMesh.new(); cm.top_radius = 0.06; cm.bottom_radius = 0.06; cm.height = 0.02; plate.mesh = cm + plate.position = Vector3(-0.05, 0.03, 0); var pm := StandardMaterial3D.new(); pm.albedo_color = Color("f0efe9"); plate.material_override = pm; g.add_child(plate) + socket.add_child(g) + +func _warm() -> void: + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("1c1712") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR; e.ambient_light_color = Color("caa981"); e.ambient_light_energy = 0.5 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; e.ssao_enabled = true; we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-48, -35, 0); sun.light_color = Color("ffdcae"); sun.light_energy = 1.2; sun.shadow_enabled = true; add_child(sun) + var kit := SpotLight3D.new(); kit.position = Vector3(-1, 3.4, -3.6); kit.rotation_degrees = Vector3(-90, 0, 0); kit.light_color = Color("cfe0ff"); kit.light_energy = 5.0; kit.spot_range = 8.0; kit.spot_angle = 60.0; add_child(kit) + +func _arg(key: String, d: String) -> String: + for a in OS.get_cmdline_user_args(): + if a.begins_with("--%s=" % key): return a.split("=", 1)[1] + return d + +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null diff --git a/visual/RestaurantVisualSandbox/scripts/mini_service.gd.uid b/visual/RestaurantVisualSandbox/scripts/mini_service.gd.uid new file mode 100644 index 0000000..ceeba85 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/mini_service.gd.uid @@ -0,0 +1 @@ +uid://vby5rqc5kvcl diff --git a/visual/RestaurantVisualSandbox/scripts/prop_socket.gd b/visual/RestaurantVisualSandbox/scripts/prop_socket.gd new file mode 100644 index 0000000..d9e128a --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/prop_socket.gd @@ -0,0 +1,136 @@ +extends Node3D +## Loop-5 prop sockets: attach a tray (server, cupped hand) or a tool/utensil (cook, closed +## fist) to hand_r via BoneAttachment3D so the prop tracks the hand and sits in the grip. +## Args: --char --clip=carry|hold --prop=tray|tool --closeup=1 --ox/--oy/--oz --rx/--ry/--rz +const GEN := "res://assets/generated/%s.glb" +const CLIP := {"idle": "Idle", "walk": "Walk", "reach": "PickUp_Table", "sit": "Sitting_Idle", + "carry": "ServeCarry", "hold": "CookHold", "walkcarry": "WalkCarry", "eat": "Eat", + "checklist": "CheckList", "greet": "Greet", "gesture": "GestureDining", "apologetic": "Apologetic", + "tubhold": "TubHold", "tubcarry": "TubCarry", "wipe": "Wipe"} + +func _ready() -> void: + _warm() + add_child(Placeholder.floor_tile(Vector2(6, 4), Color("2c2620"))) + var char: String = _arg("char", "CHAR_Hybrid_Server_Average") + var clip: String = CLIP.get(_arg("clip", "carry"), "ServeCarry") + var prop: String = _arg("prop", "tray") + var packed = load(GEN % char) + var n = packed.instantiate(); add_child(n) + var sk := _find(n, "Skeleton3D") as Skeleton3D + var ap := _find(n, "AnimationPlayer") as AnimationPlayer + if sk: + var bone := _arg("bone", "hand_r") + var socket := BoneAttachment3D.new(); socket.name = "SOCKET_" + bone + sk.add_child(socket); socket.bone_name = bone + var item: Node3D + var dox := "0.0"; var doy := "0.02"; var doz := "0.0"; var drx := "0"; var dry := "0" + if prop == "tool": + item = _tool(); doz = "0.06" + elif prop == "food": + item = _food(); dox = "0.0"; doy = "0.04"; doz = "0.10" + elif prop == "clipboard": + item = _clipboard(); doy = "0.06"; doz = "0.10"; drx = "-55" + elif prop == "cloth": + item = _cloth(); doy = "0.02"; doz = "0.06" + elif prop == "tub": + item = _tub(true) # socketed to a torso bone (spine_02) via --bone=spine_02 + dox = "0.0"; doy = "-0.18"; doz = "0.34" + else: + item = _tray(); doy = "0.03"; doz = "0.14"; drx = "-20" # rest tray level, forward on the hand + item.position = Vector3(_f("ox", dox), _f("oy", doy), _f("oz", doz)) + item.rotation_degrees = Vector3(_f("rx", drx), _f("ry", dry), _f("rz", "0")) + socket.add_child(item) + if ap: + var real := clip + if not ap.has_animation(real): + for a in ap.get_animation_list(): + if String(a).findn(clip) >= 0: real = a; break + if ap.has_animation(real): + ap.get_animation(real).loop_mode = Animation.LOOP_LINEAR + ap.play(real); ap.seek(0.55, true) + var cam := Camera3D.new(); add_child(cam); cam.current = true + if _arg("closeup", "") != "": + cam.position = Vector3(0.62, 1.2, 1.15); cam.look_at(Vector3(0.0, 1.02, 0.28)) + else: + cam.position = Vector3(0.4, 1.15, 3.0); cam.look_at(Vector3(0, 0.95, 0)) + +func _tray() -> Node3D: + var g := Node3D.new() + var t := _box(Vector3(0.30, 0.02, 0.24), Color("cfd8e3")); g.add_child(t) + var plate := _cyl(0.07, 0.02, Color("f0efe9")); plate.position = Vector3(-0.06, 0.03, 0); g.add_child(plate) + var glass := _cyl(0.03, 0.09, Color("bfe0e8")); glass.position = Vector3(0.08, 0.06, 0.03); g.add_child(glass) + return g + +func _clipboard() -> Node3D: + var g := Node3D.new() + var board := _box(Vector3(0.19, 0.012, 0.25), Color("b98a52")); g.add_child(board) + var paper := _box(Vector3(0.15, 0.004, 0.20), Color("f4f1e8")); paper.position = Vector3(0, 0.009, 0.005); g.add_child(paper) + var clasp := _box(Vector3(0.07, 0.018, 0.025), Color("9aa0a6")); clasp.position = Vector3(0, 0.013, 0.10); g.add_child(clasp) + return g + +func _cloth() -> Node3D: + var g := Node3D.new() + var c := _box(Vector3(0.12, 0.018, 0.12), Color("dfe2e8")); g.add_child(c) + var fold := _box(Vector3(0.12, 0.012, 0.05), Color("cfd3da")); fold.position = Vector3(0, 0.014, 0.02); g.add_child(fold) + return g + +func _tub(loaded: bool) -> Node3D: + # open bus tub carried two-handed by its side handles; loaded => dirty dishes above the rim. + # ~0.5 m wide so the hands grip the end handles where the arms naturally hang. + var g := Node3D.new(); var col := Color("41454b") + var fl := _box(Vector3(0.50, 0.02, 0.27), col); fl.position = Vector3(0, -0.09, 0); g.add_child(fl) + for sx in [-1, 1]: + var w := _box(Vector3(0.02, 0.18, 0.27), col); w.position = Vector3(sx * 0.25, 0, 0); g.add_child(w) + var handle := _box(Vector3(0.05, 0.03, 0.12), Color("2b2e33")); handle.position = Vector3(sx * 0.275, 0.05, 0); g.add_child(handle) + for sz in [-1, 1]: + var w2 := _box(Vector3(0.50, 0.18, 0.02), col); w2.position = Vector3(0, 0, sz * 0.135); g.add_child(w2) + if loaded: + var p1 := _cyl(0.075, 0.02, Color("e8e4d8")); p1.position = Vector3(-0.12, 0.06, 0.02); g.add_child(p1) + var p2 := _cyl(0.065, 0.02, Color("ded9cc")); p2.position = Vector3(0.04, 0.05, -0.03); g.add_child(p2) + var gl := _cyl(0.028, 0.09, Color("bfe0e8")); gl.position = Vector3(0.14, 0.10, 0.05); g.add_child(gl) + return g + +func _food() -> Node3D: + # a fork held in the hand with a food morsel on the tines — makes "eating" unambiguous + var g := Node3D.new() + var handle := _cyl(0.008, 0.16, Color("d8d8dc")); handle.rotation_degrees = Vector3(90, 0, 0); g.add_child(handle) + var tines := _box(Vector3(0.03, 0.006, 0.05), Color("d8d8dc")); tines.position = Vector3(0, 0, 0.10); g.add_child(tines) + var morsel := _sphere(0.022, Color("b5651d")); morsel.position = Vector3(0, 0.02, 0.11); g.add_child(morsel) + return g + +func _sphere(r: float, c: Color) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var sm := SphereMesh.new(); sm.radius = r; sm.height = r * 2.0; mi.mesh = sm + var m := StandardMaterial3D.new(); m.albedo_color = c; mi.material_override = m; return mi + +func _tool() -> Node3D: + # a spatula/ladle: a thin handle held THROUGH the fist + a head at the end + var g := Node3D.new() + var handle := _cyl(0.014, 0.34, Color("6b4a2a")); handle.rotation_degrees = Vector3(90, 0, 0); g.add_child(handle) + var head := _box(Vector3(0.07, 0.012, 0.10), Color("b8b8bc")); head.position = Vector3(0, 0, 0.22); g.add_child(head) + return g + +func _box(s: Vector3, c: Color) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var b := BoxMesh.new(); b.size = s; mi.mesh = b + var m := StandardMaterial3D.new(); m.albedo_color = c; mi.material_override = m; return mi +func _cyl(r: float, h: float, c: Color) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var cm := CylinderMesh.new(); cm.top_radius = r; cm.bottom_radius = r; cm.height = h; mi.mesh = cm + var m := StandardMaterial3D.new(); m.albedo_color = c; mi.material_override = m; return mi +func _warm(): + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("20242b") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR; e.ambient_light_color = Color("b4bccb"); e.ambient_light_energy = 0.6 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; e.ssao_enabled = true; we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-42, -38, 0); sun.light_energy = 1.35; sun.shadow_enabled = true; add_child(sun) + var fill := DirectionalLight3D.new(); fill.rotation_degrees = Vector3(-20, 150, 0); fill.light_energy = 0.35; add_child(fill) +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null +func _arg(k: String, d: String) -> String: + for a in OS.get_cmdline_user_args(): + if a.begins_with("--%s=" % k): return a.split("=", 1)[1] + return d +func _f(k: String, d: String) -> float: + return float(_arg(k, d)) diff --git a/visual/RestaurantVisualSandbox/scripts/prop_socket.gd.uid b/visual/RestaurantVisualSandbox/scripts/prop_socket.gd.uid new file mode 100644 index 0000000..65a3c04 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/prop_socket.gd.uid @@ -0,0 +1 @@ +uid://dsf3t8olgjyxm diff --git a/visual/RestaurantVisualSandbox/scripts/rig_review.gd b/visual/RestaurantVisualSandbox/scripts/rig_review.gd new file mode 100644 index 0000000..4fa5f23 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/rig_review.gd @@ -0,0 +1,62 @@ +extends Node3D +## Rig/animation review: loads CHAR_Base profiles, plays a clip, renders for deformation +## inspection. Args (after --): --clip=Idle --seek=0.3 --closeup=1 --char=CHAR_Base_Average +## VisualOnly. + +func _ready() -> void: + _warm() + var clip := _arg("clip", "Idle") + var closeup := _arg("closeup", "") != "" + var single := _arg("char", "") + var chars := [] + if single != "": + chars = [single] + else: + chars = ["CHAR_Base_Slim", "CHAR_Base_Average", "CHAR_Base_Broad"] + var x := -(chars.size() - 1) * 0.9 + var i := 0 + for c in chars: + var packed = load("res://assets/generated/%s.glb" % c) + if packed: + var inst = packed.instantiate() + inst.position = Vector3(x + i * 1.8, 0, 0) + add_child(inst) + var ap := _find(inst, "AnimationPlayer") as AnimationPlayer + if ap and ap.has_animation(clip): + ap.play(clip) + ap.seek(_arg("seek", "0.4").to_float() + i * 0.3, true) + var lbl := Placeholder.label(c.replace("CHAR_Base_", ""), 0.14, Color("e8dcc2")) + lbl.position = inst.position + Vector3(0, 1.95, 0) + add_child(lbl) + i += 1 + var floor := Placeholder.floor_tile(Vector2(8, 4), Color("2c2620")) + add_child(floor) + var cam := Camera3D.new(); add_child(cam); cam.current = true + if closeup: + cam.position = Vector3(0.9, 1.25, 1.4); cam.look_at(Vector3(0, 1.15, 0)) + else: + cam.position = Vector3(0, 1.3, 4.2); cam.look_at(Vector3(0, 0.95, 0)) + +func _warm() -> void: + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("20242b") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR + e.ambient_light_color = Color("b4bccb"); e.ambient_light_energy = 0.6 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; e.ssao_enabled = true + we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-42, -38, 0) + sun.light_energy = 1.35; sun.shadow_enabled = true; add_child(sun) + var fill := DirectionalLight3D.new(); fill.rotation_degrees = Vector3(-20, 150, 0) + fill.light_energy = 0.35; add_child(fill) + +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null + +func _arg(k: String, d: String) -> String: + for a in OS.get_cmdline_user_args(): + if a.begins_with("--%s=" % k): return a.split("=", 1)[1] + return d diff --git a/visual/RestaurantVisualSandbox/scripts/rig_review.gd.uid b/visual/RestaurantVisualSandbox/scripts/rig_review.gd.uid new file mode 100644 index 0000000..882fe70 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/rig_review.gd.uid @@ -0,0 +1 @@ +uid://dl2wohtg1gjtg diff --git a/visual/RestaurantVisualSandbox/scripts/table_states.gd b/visual/RestaurantVisualSandbox/scripts/table_states.gd new file mode 100644 index 0000000..8422c60 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/table_states.gd @@ -0,0 +1,113 @@ +extends Node3D +## Loop 6 — physical table-state lineup: five tables in a row, one per lifecycle state, so the +## non-colour (silhouette/prop) language can be checked and greyscaled. VisualOnly, scripted. +## States L->R: AVAILABLE, OCCUPIED, DIRTY, BEING-CLEANED, RESET/READY. +const GEN := "res://assets/generated/%s.glb" +const STATES := ["available", "occupied", "dirty", "cleaning", "reset"] +const LABELS := ["AVAILABLE", "OCCUPIED", "DIRTY", "BEING CLEANED", "RESET / READY"] + +func _ready() -> void: + _warm() + var floor := MeshInstance3D.new(); var pm := PlaneMesh.new(); pm.size = Vector2(16, 6); floor.mesh = pm + floor.material_override = _mat(Color("6b4a30")); add_child(floor) + var x := -6.0 + for i in STATES.size(): + _table(Vector3(x, 0, 0), STATES[i], i) + _lab(LABELS[i], Vector3(x, 2.1, 0)) + x += 3.0 + var cam := Camera3D.new(); add_child(cam); cam.current = true + cam.position = Vector3(0, 2.7, 5.2); cam.look_at(Vector3(0, 0.55, -0.15)) + +func _table(c: Vector3, state: String, idx: int) -> void: + var top := 0.75 + # table top + a pedestal + _boxp(Vector3(c.x, top, c.z), Vector3(1.0, 0.06, 0.8), Color("8a6a44")) + _boxp(Vector3(c.x, top * 0.5, c.z), Vector3(0.12, top, 0.12), Color("6b4a30")) + var seats: Array[Vector3] = [c + Vector3(-0.62, 0, 0), c + Vector3(0.62, 0, 0)] + # chairs (tucked unless dirty/cleaning) + for s in seats: + var pushed := state == "dirty" or state == "cleaning" + var dir := (s - c); dir.y = 0; dir = dir.normalized() + var cp: Vector3 = s + (dir * 0.30 if pushed else Vector3.ZERO) + var yaw := (0.6 if pushed else 0.0) + var seat := _boxp(Vector3(cp.x, 0.45, cp.z), Vector3(0.34, 0.05, 0.34), Color("6b4a30")); seat.rotation.y = yaw + var back := _boxp(Vector3(cp.x - dir.x * 0.16, 0.66, cp.z - dir.z * 0.16), Vector3(0.34, 0.36, 0.05), Color("5c3f28")); back.rotation.y = yaw + if state == "available" or state == "reset": + for s in seats: + var d := (c - s).normalized() * 0.28 + _disc(Vector3(s.x + d.x, top + 0.04, s.z + d.z), 0.10, Color("efe9dc")) + _boxp(Vector3(s.x + d.x + 0.14, top + 0.04, s.z + d.z), Vector3(0.05, 0.02, 0.08), Color("d8d2c2")) + if state == "reset": + var pip := _disc(Vector3(c.x, top + 0.16, c.z), 0.05, Color("00b4d8")); pip.rotation_degrees.x = 90 # restrained ready marker + elif state == "occupied": + for i in seats.size(): + _spawn(["CHAR_Hybrid_Customer_V1", "CHAR_Hybrid_Customer_V4"][i], seats[i], (90 if i == 0 else -90) + 180, ["Sitting_Idle", "Eat"][i]) + var d2 := (c - seats[i]).normalized() * 0.28 + _disc(Vector3(seats[i].x + d2.x, top + 0.04, seats[i].z + d2.z), 0.10, Color("e6ddc8")) + _cylp(Vector3(seats[i].x + d2.x, top + 0.08, seats[i].z + d2.z), 0.03, 0.06, Color("b5651d")) + elif state == "dirty" or state == "cleaning": + _cylp(Vector3(c.x - 0.06, top + 0.10, c.z + 0.04), 0.11, 0.12, Color("6f6250")) # stacked plates + for i in 3: + var a := TAU * float(i) / 3.0 + 0.6 + var off := Vector3(cos(a) * 0.28, 0, sin(a) * 0.2) + _disc(Vector3(c.x + off.x, top + 0.04, c.z + off.z), 0.09, Color("7d6f5a"), 18.0 + i * 9) + _cylp(Vector3(c.x + off.x * 1.1, top + 0.10, c.z + off.z * 1.1), 0.03, 0.12, Color("5f7078")) + if state == "cleaning": + var busser := _spawn("CHAR_Hybrid_Busser_Average", c + Vector3(0, 0, 0.95), 0, "Wipe") + var sk := _find(busser, "Skeleton3D") as Skeleton3D + if sk: + var socket := BoneAttachment3D.new(); sk.add_child(socket); socket.bone_name = "hand_r" + var cloth := _cloth(); cloth.position = Vector3(0, 0.02, 0.06); socket.add_child(cloth) + _tub_beside(c + Vector3(0.75, 0.42, 0.5), true) + +func _spawn(name: String, pos: Vector3, yaw: float, clip: String) -> Node3D: + var packed = load(GEN % name) + if packed == null: return Node3D.new() + var n = packed.instantiate(); n.position = pos; n.rotation.y = deg_to_rad(yaw); add_child(n) + var ap := _find(n, "AnimationPlayer") as AnimationPlayer + if ap: + var real := clip + if not ap.has_animation(real): + for a in ap.get_animation_list(): + if String(a).findn(clip) >= 0: real = a; break + if ap.has_animation(real): ap.get_animation(real).loop_mode = Animation.LOOP_LINEAR; ap.play(real); ap.seek(0.4, true) + return n + +func _tub_beside(pos: Vector3, loaded: bool) -> void: + var col := Color("41454b") + _boxp(pos + Vector3(0, -0.09, 0), Vector3(0.5, 0.02, 0.27), col) + for sx in [-1, 1]: + _boxp(pos + Vector3(sx * 0.25, 0, 0), Vector3(0.02, 0.18, 0.27), col) + for sz in [-1, 1]: + _boxp(pos + Vector3(0, 0, sz * 0.135), Vector3(0.5, 0.18, 0.02), col) + if loaded: + for i in 3: + _cylp(pos + Vector3(-0.13 + i * 0.12, 0.06, 0), 0.07, 0.02, Color("e2ddce")) + +func _disc(pos: Vector3, r: float, c: Color, tilt := 0.0) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var cm := CylinderMesh.new(); cm.top_radius = r; cm.bottom_radius = r; cm.height = 0.02; mi.mesh = cm + mi.material_override = _mat(c); mi.position = pos; mi.rotation_degrees.z = tilt; add_child(mi); return mi +func _cylp(pos: Vector3, r: float, h: float, c: Color) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var cm := CylinderMesh.new(); cm.top_radius = r; cm.bottom_radius = r; cm.height = h; mi.mesh = cm + mi.material_override = _mat(c); mi.position = pos; add_child(mi); return mi +func _boxp(pos: Vector3, s: Vector3, c: Color, tilt := 0.0) -> MeshInstance3D: + var mi := MeshInstance3D.new(); var b := BoxMesh.new(); b.size = s; mi.mesh = b + mi.material_override = _mat(c); mi.position = pos; mi.rotation_degrees.y = tilt; add_child(mi); return mi +func _cloth() -> Node3D: + var g := Node3D.new(); var c := MeshInstance3D.new(); var cb := BoxMesh.new(); cb.size = Vector3(0.12, 0.018, 0.12); c.mesh = cb; c.material_override = _mat(Color("dfe2e8")); g.add_child(c); return g +func _mat(c: Color) -> StandardMaterial3D: + var m := StandardMaterial3D.new(); m.albedo_color = c; m.roughness = 0.85; return m +func _lab(t: String, pos: Vector3) -> void: + var l := Placeholder.label(t, 0.16, Color("f0e8d6")); l.position = pos; add_child(l) +func _warm() -> void: + var we := WorldEnvironment.new(); var e := Environment.new() + e.background_mode = Environment.BG_COLOR; e.background_color = Color("20242b") + e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR; e.ambient_light_color = Color("b4bccb"); e.ambient_light_energy = 0.65 + e.tonemap_mode = Environment.TONE_MAPPER_ACES; we.environment = e; add_child(we) + var sun := DirectionalLight3D.new(); sun.rotation_degrees = Vector3(-50, -40, 0); sun.light_energy = 1.2; sun.shadow_enabled = true; add_child(sun) +func _find(n, t): + if n.get_class() == t: return n + for c in n.get_children(): + var r = _find(c, t) + if r: return r + return null diff --git a/visual/RestaurantVisualSandbox/scripts/table_states.gd.uid b/visual/RestaurantVisualSandbox/scripts/table_states.gd.uid new file mode 100644 index 0000000..932baa0 --- /dev/null +++ b/visual/RestaurantVisualSandbox/scripts/table_states.gd.uid @@ -0,0 +1 @@ +uid://dibljf5h16whk